@ 261103.1

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

src/NodeApiJsiRuntime.cpp

@@ -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];       }     } 

tests/JsiRuntimeTests.cpp

@@ -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