Skip to content

Commit aa5d7d3

Browse files
committed
[MERGE #5835 @duongnhn] BigInt: implement multiply
Merge pull request #5835 from duongnhn:user/duongn/bigint_multiply - Implement Bigint multiply according to https://tc39.github.io/proposal-bigint/#sec-numeric-types-bigint-multiply - Refactor existing code, e.g. MulThenAdd does not need to return anything - Add tests
2 parents c7786bf + 25af1a9 commit aa5d7d3

File tree

6 files changed

+202
-17
lines changed

6 files changed

+202
-17
lines changed

lib/Runtime/Library/JavascriptBigInt.cpp

Lines changed: 88 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,17 @@ namespace Js
1212
}
1313

1414
JavascriptBigInt * JavascriptBigInt::CreateZero(ScriptContext * scriptContext)
15+
{
16+
return JavascriptBigInt::CreateZeroWithLength(1, scriptContext);
17+
}
18+
19+
JavascriptBigInt * JavascriptBigInt::CreateZeroWithLength(digit_t length, ScriptContext * scriptContext)
1520
{
1621
JavascriptBigInt * bigintNew = RecyclerNew(scriptContext->GetRecycler(), JavascriptBigInt, scriptContext->GetLibrary()->GetBigIntTypeStatic());
17-
bigintNew->m_length = 1;
22+
bigintNew->m_length = length;
1823
bigintNew->m_isNegative = false;
19-
bigintNew->m_maxLength = 1;
20-
bigintNew->m_digits = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), digit_t, bigintNew->m_length);
21-
bigintNew->m_digits[0] = 0;
22-
24+
bigintNew->m_maxLength = length;
25+
bigintNew->m_digits = RecyclerNewArrayLeafZ(scriptContext->GetRecycler(), digit_t, bigintNew->m_length);
2326
return bigintNew;
2427
}
2528

@@ -32,11 +35,12 @@ namespace Js
3235

3336
JavascriptBigInt * JavascriptBigInt::New(JavascriptBigInt * pbi, ScriptContext * scriptContext)
3437
{
38+
Assert(pbi->m_maxLength >= pbi->m_length);
3539
JavascriptBigInt * bigintNew = RecyclerNew(scriptContext->GetRecycler(), JavascriptBigInt, scriptContext->GetLibrary()->GetBigIntTypeStatic());
3640
bigintNew->m_length = pbi->m_length;
3741
bigintNew->m_maxLength = pbi->m_maxLength;
3842
bigintNew->m_isNegative = pbi->m_isNegative;
39-
bigintNew->m_digits = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), digit_t, pbi->m_length);
43+
bigintNew->m_digits = RecyclerNewArrayLeaf(scriptContext->GetRecycler(), digit_t, pbi->m_maxLength);
4044
js_memcpy_s(bigintNew->m_digits, bigintNew->m_length * sizeof(digit_t), pbi->m_digits, bigintNew->m_length * sizeof(digit_t));
4145

4246
return bigintNew;
@@ -151,23 +155,20 @@ namespace Js
151155

152156
digit_t digitMul = 1;
153157
digit_t digitAdd = 0;
154-
bool check = true;
155158
for (; pChar < pCharLimit; pChar++)
156159
{
157160
Assert(NumberUtilities::IsDigit(*pChar));
158161
if (digitMul == 1e9)
159162
{
160-
check = MulThenAdd(digitMul, digitAdd);
161-
Assert(check);
163+
MulThenAdd(digitMul, digitAdd);
162164
digitMul = 1;
163165
digitAdd = 0;
164166
}
165167
digitMul *= 10;
166168
digitAdd = digitAdd * 10 + *pChar - '0';
167169
}
168170
Assert(1 < digitMul);
169-
check = MulThenAdd(digitMul, digitAdd);
170-
Assert(check);
171+
MulThenAdd(digitMul, digitAdd);
171172

172173
// make sure this is no negative zero
173174
if (m_length == 0)
@@ -352,7 +353,7 @@ namespace Js
352353
return resultLow;
353354
}
354355

355-
bool JavascriptBigInt::MulThenAdd(digit_t digitMul, digit_t digitAdd)
356+
void JavascriptBigInt::MulThenAdd(digit_t digitMul, digit_t digitAdd)
356357
{
357358
Assert(digitMul != 0);
358359

@@ -377,7 +378,6 @@ namespace Js
377378
}
378379
m_digits[m_length++] = digitAdd;
379380
}
380-
return true;
381381
}
382382

383383
int JavascriptBigInt::Compare(JavascriptBigInt *pbi)
@@ -419,7 +419,9 @@ namespace Js
419419
for (index = m_length - 1; m_digits[index] == pbi->m_digits[index]; index--)
420420
{
421421
if (0 == index)
422+
{
422423
return 0;
424+
}
423425
}
424426
Assert(m_digits[index] != pbi->m_digits[index]);
425427

@@ -554,6 +556,72 @@ namespace Js
554556
}
555557
}
556558

559+
// return |pbi1| * |pbi2|
560+
JavascriptBigInt * JavascriptBigInt::MulAbsolute(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2)
561+
{
562+
// Start with maximum length possible in pbi3
563+
digit_t length = pbi1->m_length + pbi2->m_length;
564+
if (SIZE_MAX / sizeof(digit_t) < length) // overflow
565+
{
566+
JavascriptError::ThrowRangeError(pbi1->GetScriptContext(), VBSERR_TypeMismatch, _u("Multiply BigInt"));
567+
}
568+
JavascriptBigInt * pbi3 = JavascriptBigInt::CreateZeroWithLength(length, pbi1->GetScriptContext());
569+
570+
// Compute pbi3 = pbi1 * pbi2 as follow:
571+
// e.g. A1 A0 * B1 B0 = C3 C2 C1 C0
572+
// C0 = A0 * B0 (take the digit and carry)
573+
// C1 = carry + A0 * B1 + A1 * B0 (take the digit and carry)
574+
// C2 = carry + A1 * B1 (take the digit and carry)
575+
// C3 = carry
576+
digit_t carryDigit = 0;
577+
digit_t i3 = 0;
578+
579+
for (digit_t i1 = 0; i1 < pbi1->m_length; i1++)
580+
{
581+
carryDigit = 0;
582+
for (digit_t i2 = 0; i2 < pbi2->m_length; i2++)
583+
{
584+
i3 = i1 + i2;
585+
digit_t tempCarryDigit1 = 0;
586+
digit_t tempCarryDigit2 = 0;
587+
pbi3->m_digits[i3] = JavascriptBigInt::AddDigit(pbi3->m_digits[i3], carryDigit, &tempCarryDigit1);
588+
digit_t mulDigitResult = JavascriptBigInt::MulDigit(pbi1->m_digits[i1], pbi2->m_digits[i2], &carryDigit);
589+
pbi3->m_digits[i3] = JavascriptBigInt::AddDigit(pbi3->m_digits[i3], mulDigitResult, &tempCarryDigit2);
590+
digit_t overflow = 0;
591+
carryDigit = JavascriptBigInt::AddDigit(carryDigit, tempCarryDigit1, &overflow);
592+
Assert(overflow == 0); // [i1] * [i2] can not carry through [i1+i2+2]
593+
carryDigit = JavascriptBigInt::AddDigit(carryDigit, tempCarryDigit2, &overflow);
594+
Assert(overflow == 0); // [i1] * [i2] can not carry through [i1+i2+2]
595+
}
596+
if (carryDigit > 0)
597+
{
598+
pbi3->m_digits[i3 + 1] = carryDigit;
599+
}
600+
}
601+
602+
// adjust length
603+
while ((pbi3->m_length > 0) && (pbi3->m_digits[pbi3->m_length - 1] == 0))
604+
{
605+
pbi3->m_length--;
606+
}
607+
Assert(pbi3->m_length > 0);
608+
return pbi3;
609+
}
610+
611+
JavascriptBigInt * JavascriptBigInt::Mul(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2)
612+
{
613+
if (JavascriptBigInt::IsZero(pbi1) || JavascriptBigInt::IsZero(pbi2))
614+
{
615+
return JavascriptBigInt::CreateZero(pbi1->GetScriptContext());
616+
}
617+
JavascriptBigInt * result = JavascriptBigInt::MulAbsolute(pbi1, pbi2);
618+
if (pbi1->m_isNegative != pbi2->m_isNegative)
619+
{
620+
result->m_isNegative = true;
621+
}
622+
return result;
623+
}
624+
557625
JavascriptBigInt * JavascriptBigInt::Add(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2)
558626
{
559627
if (JavascriptBigInt::IsZero(pbi1))
@@ -607,4 +675,11 @@ namespace Js
607675
// TODO: Consider deferring creation of new instances until we need them
608676
}
609677

678+
Var JavascriptBigInt::Mul(Var aLeft, Var aRight)
679+
{
680+
JavascriptBigInt *leftBigInt = VarTo<JavascriptBigInt>(aLeft);
681+
JavascriptBigInt *rightBigInt = VarTo<JavascriptBigInt>(aRight);
682+
return JavascriptBigInt::Mul(leftBigInt, rightBigInt);
683+
}
684+
610685
} // namespace Js

lib/Runtime/Library/JavascriptBigInt.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@ namespace Js
3939
static Var Increment(Var aRight);
4040
static Var Add(Var aLeft, Var aRight);
4141
static Var Sub(Var aLeft, Var aRight);
42+
static Var Mul(Var aLeft, Var aRight);
4243
static Var Decrement(Var aRight);
4344
static Var Not(Var aRight);
4445
static Var Negate(Var aRight);
4546

4647
inline BOOL isNegative() { return m_isNegative; }
4748

4849
static JavascriptBigInt * CreateZero(ScriptContext * scriptContext);
50+
static JavascriptBigInt * CreateZeroWithLength(digit_t length, ScriptContext * scriptContext);
4951
static JavascriptBigInt * CreateOne(ScriptContext * scriptContext);
5052
static JavascriptBigInt * Create(const char16 * content, charcount_t cchUseLength, bool isNegative, ScriptContext * scriptContext);
5153
virtual RecyclableObject * CloneToScriptContext(ScriptContext* requestContext) override;
@@ -70,7 +72,7 @@ namespace Js
7072
template <typename EncodedChar>
7173
void InitFromCharDigits(const EncodedChar *prgch, uint32 cch, bool isNegative); // init from char of digits
7274

73-
bool MulThenAdd(digit_t luMul, digit_t luAdd);
75+
void MulThenAdd(digit_t luMul, digit_t luAdd);
7476
static bool IsZero(JavascriptBigInt * pbi);
7577
static void AbsoluteIncrement(JavascriptBigInt * pbi);
7678
static void AbsoluteDecrement(JavascriptBigInt * pbi);
@@ -81,6 +83,8 @@ namespace Js
8183
static void AddAbsolute(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2);
8284
static JavascriptBigInt * Sub(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2);
8385
static void SubAbsolute(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2);
86+
static JavascriptBigInt * Mul(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2);
87+
static JavascriptBigInt * MulAbsolute(JavascriptBigInt * pbi1, JavascriptBigInt * pbi2);
8488
int Compare(JavascriptBigInt * pbi);
8589
int CompareAbsolute(JavascriptBigInt * pbi);
8690
static BOOL Equals(JavascriptBigInt* left, Var right, BOOL* value, ScriptContext * requestContext);

lib/Runtime/Math/JavascriptMath.cpp

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,17 @@ using namespace Js;
945945
Assert(aRight != nullptr);
946946
Assert(scriptContext != nullptr);
947947

948+
Js::TypeId typeLeft = JavascriptOperators::GetTypeId(aLeft);
949+
Js::TypeId typeRight = JavascriptOperators::GetTypeId(aRight);
950+
if (typeLeft == TypeIds_BigInt || typeRight == TypeIds_BigInt)
951+
{
952+
if (typeRight != typeLeft)
953+
{
954+
JavascriptError::ThrowTypeError(scriptContext, VBSERR_TypeMismatch, _u("Multiply BigInt"));
955+
}
956+
return JavascriptBigInt::Mul(aLeft, aRight);
957+
}
958+
948959
if(JavascriptNumber::Is(aLeft))
949960
{
950961
if(JavascriptNumber::Is(aRight))
@@ -979,6 +990,18 @@ using namespace Js;
979990
Var JavascriptMath::Multiply_InPlace(Var aLeft, Var aRight, ScriptContext* scriptContext, JavascriptNumber* result)
980991
{
981992
JIT_HELPER_REENTRANT_HEADER(Op_MultiplyInPlace);
993+
994+
Js::TypeId typeLeft = JavascriptOperators::GetTypeId(aLeft);
995+
Js::TypeId typeRight = JavascriptOperators::GetTypeId(aRight);
996+
if (typeLeft == TypeIds_BigInt || typeRight == TypeIds_BigInt)
997+
{
998+
if (typeRight != typeLeft)
999+
{
1000+
JavascriptError::ThrowTypeError(scriptContext, VBSERR_TypeMismatch, _u("Multiply BigInt"));
1001+
}
1002+
return JavascriptBigInt::Mul(aLeft, aRight);
1003+
}
1004+
9821005
if(JavascriptNumber::Is(aLeft))
9831006
{
9841007
if(JavascriptNumber::Is(aRight))
@@ -1004,7 +1027,6 @@ using namespace Js;
10041027
{
10051028
return TaggedInt::MultiplyInPlace(aLeft, aRight, scriptContext, result);
10061029
}
1007-
10081030
double product = Multiply_Helper(aLeft, aRight, scriptContext);
10091031
return JavascriptNumber::InPlaceNew(product, scriptContext, result);
10101032
JIT_HELPER_END(Op_MultiplyInPlace);

test/BigInt/exception.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch
1010

1111
var tests = [
1212
{
13-
name: "With add, sub",
13+
name: "With add, sub, mul",
1414
body: function () {
1515
assert.throws(() => {var x = 2n + 3;}, TypeError);
1616
assert.throws(() => {var x = 2 + 3n;}, TypeError);
1717
assert.throws(() => {var x = 2n - 3;}, TypeError);
1818
assert.throws(() => {var x = 2 - 3n;}, TypeError);
19+
assert.throws(() => {var x = 2n * 3;}, TypeError);
20+
assert.throws(() => {var x = 2 * 3n;}, TypeError);
1921
}
2022
},
2123
];

test/BigInt/multiply.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4+
//-------------------------------------------------------------------------------------------------------
5+
6+
7+
if (this.WScript && this.WScript.LoadScriptFile) { // Check for running in ch
8+
this.WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
9+
}
10+
11+
var tests = [
12+
{
13+
name: "BigInt literal",
14+
body: function () {
15+
assert.isTrue(1n*2n == 2n);
16+
assert.isTrue(2n*1n == 2n);
17+
}
18+
},
19+
{
20+
name: "change length",
21+
body: function () {
22+
assert.isTrue(4294967295n * 2n == 8589934590n);
23+
assert.isTrue(123n * 18446744073709551615n == 2268949521066274848645n);
24+
}
25+
},
26+
{
27+
name: "Out of 64 bit range",
28+
body: function () {
29+
var x = 1234567890123456789012345678901234567890n;
30+
var y = BigInt(6172839450617283945061728394506172839450n);
31+
assert.isTrue(x * 5n == y);
32+
}
33+
},
34+
{
35+
name: "Very big",
36+
body: function () {
37+
var x = eval('1234567890'.repeat(20)+'0n');
38+
var y = BigInt(eval('1234567890'.repeat(20)+'7n'));
39+
assert.isTrue(x*y == 15241578753238836750495351562566681945008382873376009755225118122311263526910001524158887669562677518670946627038562550221003043773814983252552966212772443410028959019878067369875323883776284103056504141139485896967159837982037108626735823345526793582838000483112332160794086427327693963857597928498242646061072549927232083524835691205694817405890606569121173139765328562261853981054717510588324962300n);
40+
}
41+
},
42+
{
43+
name: "With signed number",
44+
body: function () {
45+
assert.isTrue(-3n * 4n == -12n);
46+
assert.isTrue(3n * -4n == -12n);
47+
assert.isTrue(-3n * -4n == 12n);
48+
assert.isTrue(-1n * 1n == -1n);
49+
}
50+
},
51+
{
52+
name: "With zero",
53+
body: function () {
54+
assert.isTrue(-4n * 0n == 0n);
55+
assert.isTrue(4n * 0n == 0n);
56+
assert.isTrue(0n * 4n == 0n);
57+
assert.isTrue(0n * -4n == 0n);
58+
}
59+
},
60+
{
61+
name: "With assign",
62+
body: function () {
63+
var x = 3n;
64+
var y = 2n;
65+
y *= x;
66+
assert.isTrue(x == 3n);
67+
assert.isTrue(y == 6n);
68+
y = x * 4n;
69+
assert.isTrue(y == 12n);
70+
y = 8n * x;
71+
assert.isTrue(y == 24n);
72+
}
73+
},
74+
];
75+
76+
testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

test/BigInt/rlexe.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,16 @@
5454
<compile-flags>-args summary -endargs -ESBigInt</compile-flags>
5555
</default>
5656
</test>
57-
<test>
57+
<test>
5858
<default>
5959
<files>bitwise_not.js</files>
6060
<compile-flags>-args summary -endargs -ESBigInt</compile-flags>
6161
</default>
6262
</test>
63+
<test>
64+
<default>
65+
<files>multiply.js</files>
66+
<compile-flags>-args summary -endargs -ESBigInt</compile-flags>
67+
</default>
68+
</test>
6369
</regress-exe>

0 commit comments

Comments
 (0)