Quellcode durchsuchen

implement bigint multiply

Z Nguyen-Huu vor 7 Jahren
Ursprung
Commit
25af1a93e1

+ 88 - 13
lib/Runtime/Library/JavascriptBigInt.cpp

@@ -12,14 +12,17 @@ namespace Js
     }
 
     JavascriptBigInt * JavascriptBigInt::CreateZero(ScriptContext * scriptContext)
+    {
+        return JavascriptBigInt::CreateZeroWithLength(1, scriptContext);
+    }
+
+    JavascriptBigInt * JavascriptBigInt::CreateZeroWithLength(digit_t length, ScriptContext * scriptContext)
     {
         JavascriptBigInt * bigintNew = RecyclerNew(scriptContext->GetRecycler(), JavascriptBigInt, scriptContext->GetLibrary()->GetBigIntTypeStatic());
-        bigintNew->m_length = 1;
+        bigintNew->m_length = length;
         bigintNew->m_isNegative = false;
-        bigintNew->m_maxLength = 1;
-        bigintNew->m_digits = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), digit_t, bigintNew->m_length);
-        bigintNew->m_digits[0] = 0;
-        
+        bigintNew->m_maxLength = length;
+        bigintNew->m_digits = RecyclerNewArrayLeafZ(scriptContext->GetRecycler(), digit_t, bigintNew->m_length);
         return bigintNew;
     }
 
@@ -32,11 +35,12 @@ namespace Js
 
     JavascriptBigInt * JavascriptBigInt::New(JavascriptBigInt * pbi, ScriptContext * scriptContext)
     {
+        Assert(pbi->m_maxLength >= pbi->m_length);
         JavascriptBigInt * bigintNew = RecyclerNew(scriptContext->GetRecycler(), JavascriptBigInt, scriptContext->GetLibrary()->GetBigIntTypeStatic());
         bigintNew->m_length = pbi->m_length;
         bigintNew->m_maxLength = pbi->m_maxLength;
         bigintNew->m_isNegative = pbi->m_isNegative;
-        bigintNew->m_digits = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), digit_t, pbi->m_length);
+        bigintNew->m_digits = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), digit_t, pbi->m_maxLength);
         js_memcpy_s(bigintNew->m_digits, bigintNew->m_length * sizeof(digit_t), pbi->m_digits, bigintNew->m_length * sizeof(digit_t));
  
         return bigintNew;
@@ -151,14 +155,12 @@ namespace Js
 
         digit_t digitMul = 1;
         digit_t digitAdd = 0;
-        bool check = true;
         for (; pChar < pCharLimit; pChar++)
         {
             Assert(NumberUtilities::IsDigit(*pChar));
             if (digitMul == 1e9)
             {
-                check = MulThenAdd(digitMul, digitAdd);
-                Assert(check);
+                MulThenAdd(digitMul, digitAdd);
                 digitMul = 1;
                 digitAdd = 0;
             }
@@ -166,8 +168,7 @@ namespace Js
             digitAdd = digitAdd * 10 + *pChar - '0';
         }
         Assert(1 < digitMul);
-        check = MulThenAdd(digitMul, digitAdd);
-        Assert(check);
+        MulThenAdd(digitMul, digitAdd);
 
         // make sure this is no negative zero
         if (m_length == 0)
@@ -352,7 +353,7 @@ namespace Js
         return resultLow;
     }
 
-    bool JavascriptBigInt::MulThenAdd(digit_t digitMul, digit_t digitAdd)
+    void JavascriptBigInt::MulThenAdd(digit_t digitMul, digit_t digitAdd)
     {
         Assert(digitMul != 0);
 
@@ -377,7 +378,6 @@ namespace Js
             }
             m_digits[m_length++] = digitAdd;
         }
-        return true;
     }
 
     int JavascriptBigInt::Compare(JavascriptBigInt *pbi)
@@ -419,7 +419,9 @@ namespace Js
         for (index = m_length - 1; m_digits[index] == pbi->m_digits[index]; index--)
         {
             if (0 == index)
+            {
                 return 0;
+            }
         }
         Assert(m_digits[index] != pbi->m_digits[index]);
 
@@ -554,6 +556,72 @@ namespace Js
         }
     }
 
+    // return |pbi1| * |pbi2|
+    JavascriptBigInt * JavascriptBigInt::MulAbsolute(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2)
+    {
+        // Start with maximum length possible in pbi3
+        digit_t length = pbi1->m_length + pbi2->m_length;
+        if (SIZE_MAX / sizeof(digit_t) < length) // overflow 
+        {
+            JavascriptError::ThrowRangeError(pbi1->GetScriptContext(), VBSERR_TypeMismatch, _u("Multiply BigInt"));
+        }
+        JavascriptBigInt * pbi3 = JavascriptBigInt::CreateZeroWithLength(length, pbi1->GetScriptContext());
+
+        // Compute pbi3 = pbi1 * pbi2 as follow:
+        // e.g. A1 A0 * B1 B0 = C3 C2 C1 C0
+        // C0 = A0 * B0 (take the digit and carry)
+        // C1 = carry + A0 * B1 + A1 * B0 (take the digit and carry)
+        // C2 = carry + A1 * B1 (take the digit and carry)
+        // C3 = carry
+        digit_t carryDigit = 0;
+        digit_t i3 = 0;
+
+        for (digit_t i1 = 0; i1 < pbi1->m_length; i1++)
+        {
+            carryDigit = 0;
+            for (digit_t i2 = 0; i2 < pbi2->m_length; i2++)
+            {
+                i3 = i1 + i2;
+                digit_t tempCarryDigit1 = 0;
+                digit_t tempCarryDigit2 = 0;
+                pbi3->m_digits[i3] = JavascriptBigInt::AddDigit(pbi3->m_digits[i3], carryDigit, &tempCarryDigit1);
+                digit_t mulDigitResult = JavascriptBigInt::MulDigit(pbi1->m_digits[i1], pbi2->m_digits[i2], &carryDigit);
+                pbi3->m_digits[i3] = JavascriptBigInt::AddDigit(pbi3->m_digits[i3], mulDigitResult, &tempCarryDigit2);
+                digit_t overflow = 0;
+                carryDigit = JavascriptBigInt::AddDigit(carryDigit, tempCarryDigit1, &overflow);
+                Assert(overflow == 0); // [i1] * [i2] can not carry through [i1+i2+2]
+                carryDigit = JavascriptBigInt::AddDigit(carryDigit, tempCarryDigit2, &overflow);
+                Assert(overflow == 0); // [i1] * [i2] can not carry through [i1+i2+2]
+            }
+            if (carryDigit > 0)
+            {
+                pbi3->m_digits[i3 + 1] = carryDigit;
+            }
+        }
+        
+        // adjust length
+        while ((pbi3->m_length > 0) && (pbi3->m_digits[pbi3->m_length - 1] == 0))
+        {
+            pbi3->m_length--;
+        }
+        Assert(pbi3->m_length > 0);
+        return pbi3;
+    }
+
+    JavascriptBigInt * JavascriptBigInt::Mul(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2)
+    {
+        if (JavascriptBigInt::IsZero(pbi1) || JavascriptBigInt::IsZero(pbi2))
+        {
+            return JavascriptBigInt::CreateZero(pbi1->GetScriptContext());
+        }
+        JavascriptBigInt * result = JavascriptBigInt::MulAbsolute(pbi1, pbi2);
+        if (pbi1->m_isNegative != pbi2->m_isNegative) 
+        {
+            result->m_isNegative = true;
+        }
+        return result;
+    }
+
     JavascriptBigInt * JavascriptBigInt::Add(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2)
     {
         if (JavascriptBigInt::IsZero(pbi1)) 
@@ -607,4 +675,11 @@ namespace Js
         // TODO: Consider deferring creation of new instances until we need them
     }
 
+    Var JavascriptBigInt::Mul(Var aLeft, Var aRight)
+    {
+        JavascriptBigInt *leftBigInt = VarTo<JavascriptBigInt>(aLeft);
+        JavascriptBigInt *rightBigInt = VarTo<JavascriptBigInt>(aRight);
+        return JavascriptBigInt::Mul(leftBigInt, rightBigInt);
+    }
+
 } // namespace Js

+ 5 - 1
lib/Runtime/Library/JavascriptBigInt.h

@@ -39,6 +39,7 @@ namespace Js
         static Var Increment(Var aRight);
         static Var Add(Var aLeft, Var aRight);
         static Var Sub(Var aLeft, Var aRight);
+        static Var Mul(Var aLeft, Var aRight);
         static Var Decrement(Var aRight);
         static Var Not(Var aRight);
         static Var Negate(Var aRight);
@@ -46,6 +47,7 @@ namespace Js
         inline BOOL isNegative() { return m_isNegative; }
 
         static JavascriptBigInt * CreateZero(ScriptContext * scriptContext);
+        static JavascriptBigInt * CreateZeroWithLength(digit_t length, ScriptContext * scriptContext);
         static JavascriptBigInt * CreateOne(ScriptContext * scriptContext);
         static JavascriptBigInt * Create(const char16 * content, charcount_t cchUseLength, bool isNegative, ScriptContext * scriptContext);
         virtual RecyclableObject * CloneToScriptContext(ScriptContext* requestContext) override;
@@ -70,7 +72,7 @@ namespace Js
         template <typename EncodedChar>
         void InitFromCharDigits(const EncodedChar *prgch, uint32 cch, bool isNegative); // init from char of digits
 
-        bool MulThenAdd(digit_t luMul, digit_t luAdd);
+        void MulThenAdd(digit_t luMul, digit_t luAdd);
         static bool IsZero(JavascriptBigInt * pbi);
         static void AbsoluteIncrement(JavascriptBigInt * pbi);
         static void AbsoluteDecrement(JavascriptBigInt * pbi);
@@ -81,6 +83,8 @@ namespace Js
         static void AddAbsolute(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2);
         static JavascriptBigInt * Sub(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2);
         static void SubAbsolute(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2);
+        static JavascriptBigInt * Mul(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2);
+        static JavascriptBigInt * MulAbsolute(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2);
         int Compare(JavascriptBigInt * pbi);
         int CompareAbsolute(JavascriptBigInt * pbi);
         static BOOL Equals(JavascriptBigInt* left, Var right, BOOL* value, ScriptContext * requestContext);

+ 23 - 1
lib/Runtime/Math/JavascriptMath.cpp

@@ -945,6 +945,17 @@ StringCommon:
             Assert(aRight != nullptr);
             Assert(scriptContext != nullptr);
 
+            Js::TypeId typeLeft = JavascriptOperators::GetTypeId(aLeft);
+            Js::TypeId typeRight = JavascriptOperators::GetTypeId(aRight);
+            if (typeLeft == TypeIds_BigInt || typeRight == TypeIds_BigInt)
+            {
+                if (typeRight != typeLeft)
+                {
+                    JavascriptError::ThrowTypeError(scriptContext, VBSERR_TypeMismatch, _u("Multiply BigInt"));
+                }
+                return JavascriptBigInt::Mul(aLeft, aRight);
+            }
+
             if(JavascriptNumber::Is(aLeft))
             {
                 if(JavascriptNumber::Is(aRight))
@@ -979,6 +990,18 @@ StringCommon:
         Var JavascriptMath::Multiply_InPlace(Var aLeft, Var aRight, ScriptContext* scriptContext, JavascriptNumber* result)
         {
             JIT_HELPER_REENTRANT_HEADER(Op_MultiplyInPlace);
+
+            Js::TypeId typeLeft = JavascriptOperators::GetTypeId(aLeft);
+            Js::TypeId typeRight = JavascriptOperators::GetTypeId(aRight);
+            if (typeLeft == TypeIds_BigInt || typeRight == TypeIds_BigInt)
+            {
+                if (typeRight != typeLeft)
+                {
+                    JavascriptError::ThrowTypeError(scriptContext, VBSERR_TypeMismatch, _u("Multiply BigInt"));
+                }
+                return JavascriptBigInt::Mul(aLeft, aRight);
+            }
+
             if(JavascriptNumber::Is(aLeft))
             {
                 if(JavascriptNumber::Is(aRight))
@@ -1004,7 +1027,6 @@ StringCommon:
             {
                 return TaggedInt::MultiplyInPlace(aLeft, aRight, scriptContext, result);
             }
-
             double product = Multiply_Helper(aLeft, aRight, scriptContext);
             return JavascriptNumber::InPlaceNew(product, scriptContext, result);
             JIT_HELPER_END(Op_MultiplyInPlace);

+ 3 - 1
test/BigInt/exception.js

@@ -10,12 +10,14 @@ if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch
 
 var tests = [
     {
-        name: "With add, sub",
+        name: "With add, sub, mul",
         body: function () {
             assert.throws(() => {var x = 2n + 3;}, TypeError);
             assert.throws(() => {var x = 2 + 3n;}, TypeError);
             assert.throws(() => {var x = 2n - 3;}, TypeError);
             assert.throws(() => {var x = 2 - 3n;}, TypeError);
+            assert.throws(() => {var x = 2n * 3;}, TypeError);
+            assert.throws(() => {var x = 2 * 3n;}, TypeError);
         }
     },
 ];

+ 76 - 0
test/BigInt/multiply.js

@@ -0,0 +1,76 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+
+if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch
+    this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
+}
+
+var tests = [
+    {
+        name: "BigInt literal",
+        body: function () {
+            assert.isTrue(1n*2n == 2n);
+            assert.isTrue(2n*1n == 2n);
+        }
+    },
+    {
+        name: "change length",
+        body: function () {
+            assert.isTrue(4294967295n * 2n == 8589934590n);
+            assert.isTrue(123n * 18446744073709551615n == 2268949521066274848645n);
+        }
+    },
+    {
+        name: "Out of 64 bit range",
+        body: function () {
+            var x = 1234567890123456789012345678901234567890n;
+            var y = BigInt(6172839450617283945061728394506172839450n);
+            assert.isTrue(x * 5n == y);
+        }
+    },
+    {
+        name: "Very big",
+        body: function () {
+            var x = eval('1234567890'.repeat(20)+'0n');
+            var y = BigInt(eval('1234567890'.repeat(20)+'7n'));
+            assert.isTrue(x*y == 15241578753238836750495351562566681945008382873376009755225118122311263526910001524158887669562677518670946627038562550221003043773814983252552966212772443410028959019878067369875323883776284103056504141139485896967159837982037108626735823345526793582838000483112332160794086427327693963857597928498242646061072549927232083524835691205694817405890606569121173139765328562261853981054717510588324962300n);
+        }
+    },
+    {
+        name: "With signed number",
+        body: function () {
+            assert.isTrue(-3n * 4n == -12n);
+            assert.isTrue(3n * -4n == -12n);
+            assert.isTrue(-3n * -4n == 12n);
+            assert.isTrue(-1n * 1n == -1n);
+        }
+    },
+    {
+        name: "With zero",
+        body: function () {
+            assert.isTrue(-4n * 0n == 0n);
+            assert.isTrue(4n * 0n == 0n);
+            assert.isTrue(0n * 4n == 0n);
+            assert.isTrue(0n * -4n == 0n);
+        }
+    },
+    {
+        name: "With assign",
+        body: function () {
+            var x = 3n;
+            var y = 2n;
+            y *= x;
+            assert.isTrue(x == 3n);
+            assert.isTrue(y == 6n);
+            y = x * 4n;
+            assert.isTrue(y == 12n);
+            y = 8n * x;
+            assert.isTrue(y == 24n);
+        }
+    },
+];
+
+testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

+ 7 - 1
test/BigInt/rlexe.xml

@@ -54,10 +54,16 @@
       <compile-flags>-args summary -endargs -ESBigInt</compile-flags>
     </default>
   </test>
-    <test>
+  <test>
     <default>
       <files>bitwise_not.js</files>
       <compile-flags>-args summary -endargs -ESBigInt</compile-flags>
     </default>
   </test>
+  <test>
+    <default>
+      <files>multiply.js</files>
+      <compile-flags>-args summary -endargs -ESBigInt</compile-flags>
+    </default>
+  </test>
 </regress-exe>