From ad8f9f55ea79a4d587aecc69698808da82de3d62 Mon Sep 17 00:00:00 2001 From: somhairle Date: Tue, 21 Apr 2026 20:40:27 +0800 Subject: 二〇二六年四月二十一日 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../v8\344\270\255\347\232\204Number-toString.md" | 162 +++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 "source/_posts/v8\344\270\255\347\232\204Number-toString.md" (limited to 'source/_posts/v8中的Number-toString.md') diff --git "a/source/_posts/v8\344\270\255\347\232\204Number-toString.md" "b/source/_posts/v8\344\270\255\347\232\204Number-toString.md" new file mode 100644 index 00000000..d1a14efc --- /dev/null +++ "b/source/_posts/v8\344\270\255\347\232\204Number-toString.md" @@ -0,0 +1,162 @@ +--- +title: v8中的Number.toString() +date: 2023-10-05 14:49:18 +tags: Technique +--- + +这里讲一下JavaScript中`Number.toString()`的实现, 以V8为例。 + +- [The official mirror of the V8 Git repository](https://github.com/v8/v8/) + +在很多地方都能看到: +```c++ +*isolate->factory()->NumberToString(value); +``` +例如 [/src/builtins/builtins-number.cc](https://github.com/v8/v8/blob/df99ca37a9d02a3318d3e5487c7803f6230b8f5f/src/builtins/builtins-number.cc#L140) 中。 + +下面看看`NumberToString`的定义, 应该是在 [src/heap/factory-base.cc](https://github.com/v8/v8/blob/829e8d18d73e21807c78223b0cc107c76c80da27/src/heap/factory-base.cc#L963-L978) 中: +```c++ +template +Handle FactoryBase::NumberToString(Handle number, + NumberCacheMode mode) { + SLOW_DCHECK(IsNumber(*number)); + if (IsSmi(*number)) return SmiToString(Smi::cast(*number), mode); + + double double_value = Handle::cast(number)->value(); + // Try to canonicalize doubles. + int smi_value; + if (DoubleToSmiInteger(double_value, &smi_value)) { + return SmiToString(Smi::FromInt(smi_value), mode); + } + return HeapNumberToString(Handle::cast(number), double_value, + mode); +} +``` + +可以看到这里调用了 `SmiToString`, 这里不往下翻这个函数的定义, 只需要知道Smi是什么即可。 Smi 是一种特殊的整数类型,它被用于表示较小的整数值,通常在 32 位系统中是 31 位有符号整数。Smi 类型的值存储在指针的低位,而指针的高位用于标记该值是一个 Smi 类型。`IsSmi` 函数会检查给定的值是否为 Smi 类型,如果是,则返回 true,否则返回 false。这个函数通常用于 V8 引擎内部的优化和性能优化。 + +所以`NumberToString`会判断number是否是一个smi, 如果是的话就调用SmiToString, 否则会尝试将其转换为double再去调用`DoubleToSmiInteger`, 将`DoubleToSmiInteger`的调用结果存在`smi_value`里面, 再通过调用`SmiToString`将`smi_value`转换为字符串。 + +如果这两条路都行不通的话,就直接调用`HeapNumberToString`了。 + +`HeapNumberToString`的定义如下: +```c++ +template +Handle FactoryBase::HeapNumberToString(Handle number, + double value, + NumberCacheMode mode) { + int hash = mode == NumberCacheMode::kIgnore + ? 0 + : impl()->NumberToStringCacheHash(value); + + if (mode == NumberCacheMode::kBoth) { + Handle cached = impl()->NumberToStringCacheGet(*number, hash); + if (!IsUndefined(*cached, isolate())) return Handle::cast(cached); + } + + Handle result; + if (value == 0) { + result = zero_string(); + } else if (std::isnan(value)) { + result = NaN_string(); + } else { + char arr[kNumberToStringBufferSize]; + base::Vector buffer(arr, arraysize(arr)); + const char* string = DoubleToCString(value, buffer); + result = CharToString(this, string, mode); + } + if (mode != NumberCacheMode::kIgnore) { + impl()->NumberToStringCacheSet(number, hash, result); + } + return result; +} +``` + +就是熟知的NaN, Undefined处理,重点在: +```c++ +char arr[kNumberToStringBufferSize]; +base::Vector buffer(arr, arraysize(arr)); +const char* string = DoubleToCString(value, buffer); +result = CharToString(this, string, mode); +``` + +这里调用了`DoubleToCString`, 其定义在 [/src/numbers/conversions.cc](https://github.com/v8/v8/blob/829e8d18d73e21807c78223b0cc107c76c80da27/src/numbers/conversions.cc#L1064-L1126) 中: +```c++ +const char* DoubleToCString(double v, base::Vector buffer) { + switch (FPCLASSIFY_NAMESPACE::fpclassify(v)) { + case FP_NAN: + return "NaN"; + case FP_INFINITE: + return (v < 0.0 ? "-Infinity" : "Infinity"); + case FP_ZERO: + return "0"; + default: { + if (IsInt32Double(v)) { + // This will trigger if v is -0 and -0.0 is stringified to "0". + // (see ES section 7.1.12.1 #sec-tostring-applied-to-the-number-type) + return IntToCString(FastD2I(v), buffer); + } + SimpleStringBuilder builder(buffer.begin(), buffer.length()); + int decimal_point; + int sign; + const int kV8DtoaBufferCapacity = base::kBase10MaximalLength + 1; + char decimal_rep[kV8DtoaBufferCapacity]; + int length; + + base::DoubleToAscii( + v, base::DTOA_SHORTEST, 0, + base::Vector(decimal_rep, kV8DtoaBufferCapacity), &sign, + &length, &decimal_point); + + if (sign) builder.AddCharacter('-'); + + if (length <= decimal_point && decimal_point <= 21) { + // ECMA-262 section 9.8.1 step 6. + builder.AddString(decimal_rep); + builder.AddPadding('0', decimal_point - length); + + } else if (0 < decimal_point && decimal_point <= 21) { + // ECMA-262 section 9.8.1 step 7. + builder.AddSubstring(decimal_rep, decimal_point); + builder.AddCharacter('.'); + builder.AddString(decimal_rep + decimal_point); + + } else if (decimal_point <= 0 && decimal_point > -6) { + // ECMA-262 section 9.8.1 step 8. + builder.AddString("0."); + builder.AddPadding('0', -decimal_point); + builder.AddString(decimal_rep); + + } else { + // ECMA-262 section 9.8.1 step 9 and 10 combined. + builder.AddCharacter(decimal_rep[0]); + if (length != 1) { + builder.AddCharacter('.'); + builder.AddString(decimal_rep + 1); + } + builder.AddCharacter('e'); + builder.AddCharacter((decimal_point >= 0) ? '+' : '-'); + int exponent = decimal_point - 1; + if (exponent < 0) exponent = -exponent; + builder.AddDecimalInteger(exponent); + } + return builder.Finalize(); + } + } +} +``` + +不用过多解释, 已经很清晰了, `FastD2I` 就是 Fast Double to Integer的意思, 定义如下, 注释也很详尽: +```c++ +// The fast double-to-(unsigned-)int conversion routine does not guarantee +// rounding towards zero. +// The result is undefined if x is infinite or NaN, or if the rounded +// integer value is outside the range of type int. +inline int FastD2I(double x) { + DCHECK(x <= INT_MAX); + DCHECK(x >= INT_MIN); + return static_cast(x); +} +``` + +以上 \ No newline at end of file -- cgit v1.2.3