261103.1 · 9f2fdfa2a923784a73cc97da9b369edaf4c6c243
Fix BigInt::toString for values larger than 64 bits (#18)
BigInt values wider than 64 bits (for example 2^64, 10^40 or 2^128-1) gave wrong strings from bigintToString. Rework the division loop so it divides in place, resets the remainder on every pass and trims leading zeros. Also correct the per-word character estimate used to reserve the output string.
Add a BigIntToStringMultiWord test that compares the result against the engine's BigInt.prototype.toString for radixes 2-36.
Co-authored-by: Anukrati Agrawal <anuagra@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6571d0a2-c95e-4c83-8fb7-82debbd8de73
reading declarations
@@ -1513,8 +1513,8 @@ inline uint32_t constexpr maxCharsPerDigitInRadix(int32_t radix) { // With minNumBitsPerChar being the lower bound estimate of how many bits each // char can represent, the upper bound of how many chars "fit" in a bigint // digit is ceil(sizeofInBits(bigint digit) / minNumBitsPerChar).- uint32_t numCharsPerDigits =- static_cast<uint32_t>(sizeof(uint64_t)) / (1 << minNumBitsPerChar);+ constexpr uint32_t bitsPerDigit = sizeof(uint64_t) * 8;+ uint32_t numCharsPerDigits = (bitsPerDigit + minNumBitsPerChar - 1) / minNumBitsPerChar; return numCharsPerDigits; }@@ -1572,45 +1572,31 @@ jsi::String NodeApiJsiRuntime::bigintToString( // TODO: Add support for big-endian. uint32_t *halfWords = reinterpret_cast<uint32_t *>(words); size_t count = wordCount * 2;- for (size_t i = count; i > 0 && halfWords[i - 1] == 0; --i) {+ while (count > 0 && halfWords[count - 1] == 0) { --count; } uint32_t divisor = static_cast<uint32_t>(radix);- uint32_t remainder = 0; uint64_t word0 = words[0]; do {- // We rewrite the halfWords array as we divide it by radix.+ uint32_t remainder = 0; if (count <= 2) {- remainder = word0 % divisor;- word0 = word0 / divisor;+ remainder = static_cast<uint32_t>(word0 % divisor);+ word0 /= divisor; } else {+ // Divide the whole number in place by radix, from the most significant+ // half-word to the least significant one. for (size_t i = count; i > 0; --i) { uint64_t partialDividend = Make_64(remainder, halfWords[i - 1]);- if (partialDividend == 0) {- halfWords[i] = 0;- remainder = 0;- if (i == count) {- if (--count == 2) {- word0 = words[0];- }- }- } else if (partialDividend < divisor) {- halfWords[i] = 0;- remainder = Lo_32(partialDividend);- if (i == count) {- if (--count == 2) {- word0 = words[0];- }- }- } else if (partialDividend == divisor) {- halfWords[i] = 1;- remainder = 0;- } else {- halfWords[i] = Lo_32(partialDividend / divisor);- remainder = Lo_32(partialDividend % divisor);- }+ halfWords[i - 1] = Lo_32(partialDividend / divisor);+ remainder = Lo_32(partialDividend % divisor);+ }+ while (count > 0 && halfWords[count - 1] == 0) {+ --count;+ }+ if (count <= 2) {+ word0 = words[0]; } }
@@ -5,6 +5,7 @@ #include <ApiLoaders/V8Api.h> #include <NodeApiJsiRuntime.h> #include <jsi/test/testlib.h>+#include <string> #include <vector> using namespace Microsoft::NodeApiJsi;@@ -51,3 +52,41 @@ std::vector<RuntimeFactory> runtimeGenerators() { } } // namespace facebook::jsi++#if JSI_VERSION >= 8+namespace facebook::jsi {++class NodeApiJsiRuntimeTest : public JSITestBase {};++// BigInt::toString must match the engine's BigInt.prototype.toString for values that span several 64-bit words.+TEST_P(NodeApiJsiRuntimeTest, BigIntToStringMultiWord) {+ const char *values[] = {+ "0x10000000000000000n",+ "0x100000000000000000000000n",+ "0xffffffffffffffffffffffffffffffffn",+ "0x100000000000000000000000000000000n",+ "10n ** 40n",+ "36n ** 30n",+ "0x123456789abcdef0fedcba98765432100123456789abcdefn",+ "(1n << 512n) - 1n",+ "(1n << 1000n) + 12345n",+ "0xdeadbeef00000000000000000000000000000000cafebaben",+ };++ for (const char *value : values) {+ for (const char *sign : {"", "-"}) {+ std::string expr = std::string{sign} + "(" + value + ")";+ BigInt bigint = eval(expr.c_str()).asBigInt(rt);+ Function toString = eval("(function(v, r) { return v.toString(r); })").getObject(rt).getFunction(rt);+ for (int radix = 2; radix <= 36; ++radix) {+ std::string expected = toString.call(rt, bigint, radix).getString(rt).utf8(rt);+ EXPECT_EQ(bigint.toString(rt, radix).utf8(rt), expected) << expr << " radix " << radix;+ }+ }+ }+}++INSTANTIATE_TEST_SUITE_P(Runtimes, NodeApiJsiRuntimeTest, ::testing::ValuesIn(runtimeGenerators()));++} // namespace facebook::jsi+#endif