diff --git a/runtime/base/number_helper.cpp b/runtime/base/number_helper.cpp index 009b775eb4f6430364f824143070a61f320a3a51..991862cc35d92d9aac16c74df1fe46494b0207f1 100644 --- a/runtime/base/number_helper.cpp +++ b/runtime/base/number_helper.cpp @@ -34,6 +34,7 @@ enum class Sign { NONE, NEG, POS }; constexpr char CHARS[] = "0123456789abcdefghijklmnopqrstuvwxyz"; // NOLINT (modernize-avoid-c-arrays) constexpr uint64_t MAX_MANTISSA = 0x1ULL << 52U; +constexpr uint8_t JS_DTOA_BUF_SIZE = 128; static inline uint8_t ToDigit(uint8_t c) { @@ -337,6 +338,55 @@ JSTaggedValue NumberHelper::Pow(double base, double exponent) return JSTaggedValue(std::pow(base, exponent)); } +// NOLINTBEGIN(cppcoreguidelines-pro-bounds-pointer-arithmetic) +static inline void GetBase(double d, uint8_t digits, int &decpt, std::array &buf, + std::array &bufTmp, int size) +{ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + [[maybe_unused]] int result1 = snprintf_s(bufTmp.data(), size, size - 1, "%+.*e", digits - 1, d); + ASSERT(result1 != -1); + // mantissa + buf[0] = bufTmp[1]; + if (digits > 1) { + [[maybe_unused]] int result2 = + memcpy_s(buf.data() + 1, digits, bufTmp.data() + 2, digits); // 2 means add the point char to buf + ASSERT(result2 == EOK); + } + buf[digits + 1] = '\0'; + // exponent + constexpr uint8_t base = 10; + decpt = strtol(bufTmp.data() + digits + 2 + static_cast(digits > 1), nullptr, base) + + 1; // 2 means ignore the integer and point +} + +static inline int GetMinmumDigits(double d, int &decpt, std::array &buf) +{ + uint8_t digits = 0; + std::array bufTmp = {0}; + + // find the minimum amount of digits + uint8_t MinDigits = 1; + uint8_t MaxDigits = DOUBLE_MAX_PRECISION; + while (MinDigits < MaxDigits) { + digits = (MinDigits + MaxDigits) / 2; + GetBase(d, digits, decpt, buf, bufTmp, bufTmp.size()); + if (std::strtod(bufTmp.data(), nullptr) == d) { + // no need to keep the trailing zeros + while (digits >= 2 && buf[digits] == '0') { // 2 means ignore the integer and point + digits--; + } + MaxDigits = digits; + } else { + MinDigits = digits + 1; + } + } + digits = MaxDigits; + GetBase(d, digits, decpt, buf, bufTmp, bufTmp.size()); + + return digits; +} +// NOLINTEND(cppcoreguidelines-pro-bounds-pointer-arithmetic) + // 7.1.12.1 ToString Applied to the Number Type JSHandle NumberHelper::NumberToString(const JSThread *thread, JSTaggedValue number) { @@ -374,29 +424,18 @@ JSHandle NumberHelper::NumberToString(const JSThread *thread, JSTagg // and k is as small as possible. If there are multiple possibilities for s, choose the value of s for which s × // 10n−k is closest in value to m. If there are two such possible values of s, choose the one that is even. Note // that k is the number of digits in the decimal representation of s and that s is not divisible by 10. - PandaStringStream str; - str << std::scientific << std::showpoint << std::setprecision(DOUBLE_MAX_PRECISION) << d; - if (strtod(str.str().c_str(), nullptr) != d) { - str.clear(); - str.str(""); - str << std::scientific << std::showpoint << std::setprecision(DOUBLE_MAX_PRECISION + 1) << d; - } - std::string scientificStr(str.str()); - - auto indexOfE = scientificStr.find_last_of('e'); - ASSERT(indexOfE != std::string::npos); - std::string base = scientificStr.substr(0, indexOfE); - // skip trailing zeros, and base must not be empty. - base = base.substr(0, base.find_last_not_of('0') + 1); - int k = static_cast(base.size()) - 1; - int n = std::stoi(scientificStr.substr(indexOfE + 1)) + 1; + std::array buffer = {0}; + int n = 0; + int k = GetMinmumDigits(d, n, buffer); + PandaString base(buffer.data()); + if (n > 0 && n <= 21) { // NOLINT(readability-magic-numbers) base.erase(1, 1); if (k <= n) { // 6. If k ≤ n ≤ 21, return the String consisting of the code units of the k digits of the decimal // representation of s (in order, with no leading zeroes), followed by n−k occurrences of the code unit // 0x0030 (DIGIT ZERO). - base += std::string(n - k, '0'); + base += PandaString(n - k, '0'); } else { // 7. If 0 < n ≤ 21, return the String consisting of the code units of the most significant n digits of the // decimal representation of s, followed by the code unit 0x002E (FULL STOP), followed by the code units of @@ -408,7 +447,7 @@ JSHandle NumberHelper::NumberToString(const JSThread *thread, JSTagg // unit 0x002E (FULL STOP), followed by −n occurrences of the code unit 0x0030 (DIGIT ZERO), followed by the // code units of the k digits of the decimal representation of s. base.erase(1, 1); - base = std::string("0.") + std::string(-n, '0') + base; + base = PandaString("0.") + PandaString(-n, '0') + base; } else { if (k == 1) { // 9. Otherwise, if k = 1, return the String consisting of the code unit of the single digit of s diff --git a/tests/runtime/builtins/builtins_number_test.cpp b/tests/runtime/builtins/builtins_number_test.cpp index 4adb48fada324636f50721c4b04d793dbedca989..0780b5ccc0f9732cd1de958900cbb24fac039300 100644 --- a/tests/runtime/builtins/builtins_number_test.cpp +++ b/tests/runtime/builtins/builtins_number_test.cpp @@ -595,5 +595,17 @@ TEST_F(BuiltinsNumberTest, NumberToString) ASSERT_EQ(base::NumberHelper::NumberToString(thread, JSTaggedValue(double(1234567890.0006125)))->Compare(*res), 0); res = factory->NewFromCanBeCompressString("11234567890.000612"); ASSERT_EQ(base::NumberHelper::NumberToString(thread, JSTaggedValue(double(11234567890.0006125)))->Compare(*res), 0); + res = factory->NewFromCanBeCompressString("4.185580496821356"); + ASSERT_EQ(base::NumberHelper::NumberToString(thread, JSTaggedValue(double(4.1855804968213567)))->Compare(*res), 0); + res = factory->NewFromCanBeCompressString("3.929201589819414"); + ASSERT_EQ( + base::NumberHelper::NumberToString(thread, JSTaggedValue(double(3.9292015898194142585311918)))->Compare(*res), + 0); + res = factory->NewFromCanBeCompressString("0.9999999999999999"); + ASSERT_EQ(base::NumberHelper::NumberToString(thread, JSTaggedValue(double(0.9999999999999999)))->Compare(*res), 0); + res = factory->NewFromCanBeCompressString("1"); + ASSERT_EQ(base::NumberHelper::NumberToString(thread, JSTaggedValue(double(0.99999999999999999)))->Compare(*res), 0); + res = factory->NewFromCanBeCompressString("0.7777777777777778"); + ASSERT_EQ(base::NumberHelper::NumberToString(thread, JSTaggedValue(double(0.77777777777777777)))->Compare(*res), 0); } } // namespace panda::test