Skip to content

Commit 37b1764

Browse files
kzrnmtannergoodingdanmoseley
authored
Optimize BigInteger.Divide (#96895)
* Add divide tests * Divide by Burnikel-Ziegler algorithm * Copy from UInt128 * fix * Test * Assert(CompareActual()) * OuterLoop * RunWithFakeThreshold * Use ActualLength * Clear * Unnecessary if * Remove unreachable path * Fix * DivideGrammarSchool if n is less than or equals to DivideThreshold Fast Recursive Division (19) * Fix D3n2n * DivideBurnikelZieglerThreshold * remainder * halfN → n * DisableParallelization * Resolve merge conflicts * Add brace * Rename DummyForDebug to InitializeForDebug * Add comment * Fix format --------- Co-authored-by: Tanner Gooding <tagoo@outlook.com> Co-authored-by: Dan Moseley <danmose@microsoft.com>
1 parent d44116e commit 37b1764

12 files changed

+750
-59
lines changed

src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.AddSub.cs

+5-4
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,13 @@ public static void AddSelf(Span<uint> left, ReadOnlySpan<uint> right)
7171
// Same as above, but we're writing the result directly to a and
7272
// stop execution, if we're out of b and c is already 0.
7373

74-
for ( ; i < right.Length; i++)
74+
for (; i < right.Length; i++)
7575
{
7676
long digit = (Unsafe.Add(ref leftPtr, i) + carry) + right[i];
7777
Unsafe.Add(ref leftPtr, i) = unchecked((uint)digit);
7878
carry = digit >> 32;
7979
}
80-
for ( ; carry != 0 && i < left.Length; i++)
80+
for (; carry != 0 && i < left.Length; i++)
8181
{
8282
long digit = left[i] + carry;
8383
left[i] = (uint)digit;
@@ -100,7 +100,7 @@ public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, S
100100
{
101101
Debug.Assert(right.Length >= 1);
102102
Debug.Assert(left.Length >= right.Length);
103-
Debug.Assert(Compare(left, right) >= 0);
103+
Debug.Assert(CompareActual(left, right) >= 0);
104104
Debug.Assert(bits.Length == left.Length);
105105

106106
// Switching to managed references helps eliminating
@@ -132,8 +132,9 @@ public static void Subtract(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, S
132132
public static void SubtractSelf(Span<uint> left, ReadOnlySpan<uint> right)
133133
{
134134
Debug.Assert(left.Length >= right.Length);
135+
135136
// Assertion failing per https://github.com/dotnet/runtime/issues/97780
136-
// Debug.Assert(Compare(left, right) >= 0);
137+
// Debug.Assert(CompareActual(left, right) >= 0);
137138

138139
int i = 0;
139140
long carry = 0L;

src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs

+480-50
Large diffs are not rendered by default.

src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.FastReducer.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public FastReducer(ReadOnlySpan<uint> modulus, Span<uint> r, Span<uint> mu, Span
3232
r[r.Length - 1] = 1;
3333

3434
// Let mu = 4^k / m
35-
Divide(r, modulus, mu);
35+
DivRem(r, modulus, mu);
3636
_modulus = modulus;
3737

3838
_q1 = q1;

src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.GcdInv.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public static void Gcd(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right, Span<u
5858
{
5959
Debug.Assert(left.Length >= 2);
6060
Debug.Assert(right.Length >= 2);
61-
Debug.Assert(Compare(left, right) >= 0);
61+
Debug.Assert(CompareActual(left, right) >= 0);
6262
Debug.Assert(result.Length == left.Length);
6363

6464
left.CopyTo(result);

src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.PowMod.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ stackalloc uint[StackAllocThreshold]
227227

228228
if (value.Length > modulus.Length)
229229
{
230-
Remainder(value, modulus, valueCopy);
230+
Remainder(value, modulus, valueCopy.Slice(0, value.Length));
231231
}
232232
else
233233
{
@@ -276,7 +276,7 @@ stackalloc uint[StackAllocThreshold]
276276

277277
if (value.Length > modulus.Length)
278278
{
279-
Remainder(value, modulus, valueCopy);
279+
Remainder(value, modulus, valueCopy.Slice(0, value.Length));
280280
}
281281
else
282282
{

src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.Utils.cs

+28-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,26 @@ public static int Compare(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right)
3131
return left[iv] < right[iv] ? -1 : 1;
3232
}
3333

34+
private static int CompareActual(ReadOnlySpan<uint> left, ReadOnlySpan<uint> right)
35+
{
36+
if (left.Length != right.Length)
37+
{
38+
if (left.Length < right.Length)
39+
{
40+
if (ActualLength(right.Slice(left.Length)) > 0)
41+
return -1;
42+
right = right.Slice(0, left.Length);
43+
}
44+
else
45+
{
46+
if (ActualLength(left.Slice(right.Length)) > 0)
47+
return +1;
48+
left = left.Slice(0, right.Length);
49+
}
50+
}
51+
return Compare(left, right);
52+
}
53+
3454
public static int ActualLength(ReadOnlySpan<uint> value)
3555
{
3656
// Since we're reusing memory here, the actual length
@@ -49,11 +69,18 @@ private static int Reduce(Span<uint> bits, ReadOnlySpan<uint> modulus)
4969

5070
if (bits.Length >= modulus.Length)
5171
{
52-
Divide(bits, modulus, default);
72+
DivRem(bits, modulus, default);
5373

5474
return ActualLength(bits.Slice(0, modulus.Length));
5575
}
5676
return bits.Length;
5777
}
78+
79+
[Conditional("DEBUG")]
80+
public static void InitializeForDebug(Span<uint> bits)
81+
{
82+
// Reproduce the case where the return value of `stackalloc uint` is not initialized to zero.
83+
bits.Fill(0xCD);
84+
}
5885
}
5986
}

src/libraries/System.Runtime.Numerics/tests/BigInteger/divide.cs

+56
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ public static void RunDivideTwoLargeBI()
2525
}
2626
}
2727

28+
[Fact]
29+
public static void RunDivideOneLargeOneHalfBI()
30+
{
31+
byte[] tempByteArray1 = new byte[0];
32+
byte[] tempByteArray2 = new byte[0];
33+
34+
// Divide Method - Two Large BigIntegers
35+
for (int i = -1; i <= 1; i++)
36+
for (int j = -1; j <= 1; j++)
37+
{
38+
tempByteArray1 = GetRandomByteArray(s_random, 512 + i);
39+
tempByteArray2 = GetRandomByteArray(s_random, 256 + j);
40+
VerifyDivideString(Print(tempByteArray1) + Print(tempByteArray2) + "bDivide");
41+
}
42+
}
43+
2844
[Fact]
2945
public static void RunDivideTwoSmallBI()
3046
{
@@ -149,6 +165,31 @@ public static void RunDivideBoundary()
149165
VerifyDivideString(Math.Pow(2, 33) + " 2 bDivide");
150166
}
151167

168+
[Fact]
169+
public static void RunDivideLarge()
170+
{
171+
byte[] tempByteArray1 = new byte[0];
172+
byte[] tempByteArray2 = new byte[0];
173+
174+
for (int i = 1; i < 15; i += 3)
175+
{
176+
var num = 1 << i;
177+
tempByteArray1 = GetRandomByteArray(s_random, num);
178+
tempByteArray2 = GetRandomByteArray(s_random, num / 2);
179+
VerifyDivideString(Print(tempByteArray1) + Print(tempByteArray2) + "bDivide");
180+
}
181+
182+
// Divide Method - Two Large BigIntegers
183+
for (int i = -1; i <= 1; i++)
184+
for (int j = -1; j <= 1; j++)
185+
{
186+
int num = (1 << 13) + i;
187+
tempByteArray1 = GetRandomByteArray(s_random, num);
188+
tempByteArray2 = GetRandomByteArray(s_random, num / 3 + j);
189+
VerifyDivideString(Print(tempByteArray1) + Print(tempByteArray2) + "bDivide");
190+
}
191+
}
192+
152193
[Fact]
153194
public static void RunOverflow()
154195
{
@@ -161,6 +202,21 @@ public static void RunOverflow()
161202
Assert.Equal(z, BigInteger.Divide(x, y));
162203
}
163204

205+
[Fact]
206+
public void D3n2nBound()
207+
{
208+
var right = (BigInteger.One << (BigIntegerCalculator.DivideBurnikelZieglerThreshold * 4 * 32 - 1))
209+
+ (BigInteger.One << (BigIntegerCalculator.DivideBurnikelZieglerThreshold * 2 * 32)) - 1;
210+
var rem = right - 1;
211+
212+
var qi = BigIntegerCalculator.DivideBurnikelZieglerThreshold * 8 * 32 * 4 - 1;
213+
var q = (BigInteger.One << qi) - 1;
214+
var left = q * right + rem;
215+
216+
var q2 = BigInteger.Divide(left, right);
217+
Assert.Equal(q, q2);
218+
}
219+
164220
private static void VerifyDivideString(string opstring)
165221
{
166222
StackCalc sc = new StackCalc(opstring);

src/libraries/System.Runtime.Numerics/tests/BigInteger/divrem.cs

+95
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,22 @@ public static void RunDivRem_OneSmallOneLargeBI()
5959
}
6060
}
6161

62+
[Fact]
63+
public static void RunDivRem_OneLargeOneHalfBI()
64+
{
65+
byte[] tempByteArray1 = new byte[0];
66+
byte[] tempByteArray2 = new byte[0];
67+
68+
// Divide Method - Two Large BigIntegers
69+
for (int i = -1; i <= 1; i++)
70+
for (int j = -1; j <= 1; j++)
71+
{
72+
tempByteArray1 = GetRandomByteArray(s_random, 512 + i);
73+
tempByteArray2 = GetRandomByteArray(s_random, 256 + j);
74+
VerifyDivRemString(Print(tempByteArray1) + Print(tempByteArray2) + "bDivRem");
75+
}
76+
}
77+
6278
[Fact]
6379
public static void RunDivRem_OneLargeOne0BI()
6480
{
@@ -114,6 +130,73 @@ public static void Boundary()
114130
VerifyDivRemString(Math.Pow(2, 33) + " 2 bDivRem");
115131
}
116132

133+
[Fact]
134+
public static void RunDivRemMedium()
135+
{
136+
byte[] tempByteArray1 = new byte[0];
137+
byte[] tempByteArray2 = new byte[0];
138+
139+
for (int i = 1; i < 8; i++)
140+
{
141+
var num = 1 << i;
142+
tempByteArray1 = GetRandomByteArray(s_random, num);
143+
tempByteArray2 = GetRandomByteArray(s_random, num / 2);
144+
VerifyDivRemString(Print(tempByteArray2) + Print(tempByteArray1) + "bDivRem");
145+
}
146+
147+
// Divide Method - Two Large BigIntegers
148+
for (int i = -1; i <= 1; i++)
149+
for (int j = -1; j <= 1; j++)
150+
{
151+
int num = (1 << 7) + i;
152+
tempByteArray1 = GetRandomByteArray(s_random, num);
153+
tempByteArray2 = GetRandomByteArray(s_random, num / 3 + j);
154+
VerifyDivRemString(Print(tempByteArray2) + Print(tempByteArray1) + "bDivRem");
155+
}
156+
}
157+
158+
[Fact]
159+
[OuterLoop]
160+
public static void RunDivRemLarge()
161+
{
162+
byte[] tempByteArray1 = new byte[0];
163+
byte[] tempByteArray2 = new byte[0];
164+
165+
for (int i = 8; i < 10; i++)
166+
{
167+
var num = 1 << i;
168+
tempByteArray1 = GetRandomByteArray(s_random, num);
169+
tempByteArray2 = GetRandomByteArray(s_random, num / 2);
170+
VerifyDivRemString(Print(tempByteArray2) + Print(tempByteArray1) + "bDivRem");
171+
}
172+
173+
// Divide Method - Two Large BigIntegers
174+
for (int i = -1; i <= 1; i++)
175+
for (int j = -1; j <= 1; j++)
176+
{
177+
int num = (1 << 10) + i;
178+
tempByteArray1 = GetRandomByteArray(s_random, num);
179+
tempByteArray2 = GetRandomByteArray(s_random, num / 3 + j);
180+
VerifyDivRemString(Print(tempByteArray2) + Print(tempByteArray1) + "bDivRem");
181+
}
182+
}
183+
184+
[Fact]
185+
public void D3n2nBound()
186+
{
187+
var right = (BigInteger.One << (BigIntegerCalculator.DivideBurnikelZieglerThreshold * 4 * 32 - 1))
188+
+ (BigInteger.One << (BigIntegerCalculator.DivideBurnikelZieglerThreshold * 2 * 32)) - 1;
189+
var rem = right - 1;
190+
191+
var qi = BigIntegerCalculator.DivideBurnikelZieglerThreshold * 8 * 32 * 4 - 1;
192+
var q = (BigInteger.One << qi) - 1;
193+
var left = q * right + rem;
194+
195+
var (q2, r2) = BigInteger.DivRem(left, right);
196+
Assert.Equal(q, q2);
197+
Assert.Equal(rem, r2);
198+
}
199+
117200
[Fact]
118201
public static void RunDivRemTests()
119202
{
@@ -211,4 +294,16 @@ private static string Print(byte[] bytes)
211294
return MyBigIntImp.Print(bytes);
212295
}
213296
}
297+
298+
299+
[Collection(nameof(DisableParallelization))]
300+
public class divremTestThreshold
301+
{
302+
[Fact]
303+
public void RunDivRemMedium()
304+
{
305+
BigIntTools.Utils.RunWithFakeThreshold(BigIntegerCalculator.DivideBurnikelZieglerThreshold, 8, divremTest.RunDivRemMedium);
306+
}
307+
308+
}
214309
}

src/libraries/System.Runtime.Numerics/tests/BigInteger/modpow.cs

+25
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,31 @@ public static void ModPow2Large1SmallInt()
156156
}
157157
}
158158

159+
// InlineData randomly generated using a new Random(0) and the same logic as is used in MyBigIntImp
160+
// When using the VerifyModPowString approach, these tests were taking over 100s to execute.
161+
[Theory]
162+
[OuterLoop]
163+
[InlineData("16152447169612934532253858872860101823992426489763666485380167221632010974596350526903901586786157942589749704137347759619657187784260082814484793173551647870373927196771852", "81750440863317178198977948957223170296434385667401518194303765341584160931262254891905182291359611229458113456891057547210603179481274334549407149567413965138338569615186383", "-7196805437625531929619339290119232497987723600413951949478998858079935202962418871178150151495255147489121599998392939293913217145771279946557062800866475934948778", "5616380469802854111883204613624062553120851412450654399190717359559254034938493684003207209263083893212739965150170342564431867389596229480294680122528026782956958")]
164+
[InlineData("-19804882257271871615998867413310154569092691610863183900352723872936597192840601413521317416195627481021453523098672122338092864102790996172", "5005126326020660905045117276428380928616741884628331700680598638081087432348216495484429178211470872172882902036752474804904132643", "-103139435102033366056363338063498713727200190198435", "-4559679593639479333979462122256288215243720737403")]
165+
[InlineData("-2263742881720266366742488789295767051326353176981812961528848101545308514727342989711034207353102864275894987436688194776201313772226059035143797121004935923223042331190890429211181925543749113890129660515170733848725", "313166794469483345944045915670847620773229708424183728974404367769353433443313247319899209581239311758905801464781585268041623664425", "3984523819293796257818294503433333109083365267654089093971156331037874112339941681299823291492643701164442964256327008451060278818307268485187524722068240", "-1502346344475556570236985614111713440763176490652894928852811056060906839905964408583918853958610145894621840382970373570196361549098246030413222124498085")]
166+
[InlineData("-342701069914551895507647608205424602441707687704978754182486401529587201154652163656221404036731562708712821963465111719289193200432064875769386559645346920", "247989781302056934762335026151076445832166884867735502165354252207668083157132317377069604102049233014316799294014890817943261246892765157594412634897440785353202366563028", "121555428622583377664148832113619145387775383377032217138159544127299380518157949963314283123957062240152711187509503414343", "87578369862034238407481381238856926729112161247036763388287150463197193460326629595765471822752579542310337770783772458710")]
167+
[InlineData("-282593950368131775131360433563237877977879464854725217398276355042086422366377452969365517205334520940629323311057746859", "5959258935361466196732788139899933762310441595693546573755448590100882267274831199165902682626769861372370905838130200967035", "6598019436100687108279703832125132357070343951841815860721553173215685978621505459125000339496396952653080051757197617932586296524960251609958919233462530", "-4035534917213670830138661334307123840766321272945538719146336835321463334263841831803093764143165039453996360590495570418141622764990299884920213157241339")]
168+
[InlineData("-283588760164723199492097446398514238808996680579814508529658395835708678571214111525627362048679162021949222615325057958783388520044013697149834530411072380435126870273057157745943859", "1042611904427950637176782337251399378305726352782300537151930702574076654415735617544217054055762002017753284951033975382044655538090809873113604", "11173562248848713955826639654969725554069867375462328112124015145073186375237811117727665778232780449525476829715663254692620996726130822561707626585790443774041565237684936409844925424596571418502946", "6662129352542591544500713232459850446949913817909326081510506555775206102887692404112984526760120407457772674917650956873499346517965094865621779695963015030158124625116211104048940313058280680420919")]
169+
[InlineData("683399436848090272429540515929404372035986606104192913128573936597145312908480564700882440819526500604918037963508399983297602351856208190565527", "223751878996658298590720798129833935741775535718632942242965085592936259576946732440406302671204348239437556817289012497483482656", "1204888420642360606571457515385663952017382888522547766961071698778167608427220546474854934298311882921224875807375321847274969073309433075566441363244101914489524538123410250010519308563731930923389473186", "1136631484875680074951300738594593722168933395227758228596156355418704717505715681950525129323209331607163560404958604424924870345828742295978850144844693079191828839673460389985036424301333691983679427765")]
170+
[InlineData("736513799451968530811005754031332418210960966881742655756522735504778110620671049112529346250333710388060811959329786494662578020803", "2461175085563866950903873687720858523536520498137697316698237108626602445202960480677695918813575265778826908481129155012799", "-4722693720735888562993277045098354134891725536023070176847814685098361292027040929352405620815883795027263132404351040", "4351573186631261607388198896754285562669240685903971199359912143458682154189588696264319780329366022294935204028039787")]
171+
[InlineData("1596188639947986471148999794547338", "685242191738212089917782567856594513073397739443", "41848166029740752457613562518205823134173790454761036532025758411484449588176128053901271638836032557551179866133091058357374964041641117585422447497779410336188602585660372002644517668041207657383104649333118253", "39246949850380693159338034407642149926180988060650630387722725303281343126585456713282439764667310808891687831648451269002447916277601468727040185218264602698691553232132525542650964722093335105211816394635493987")]
172+
[InlineData("-1506852741293695463963822334869441845197951776565891060639754936248401744065969556756496718308248025911048010080232290368562210204958094544173209793990218122", "64905085725614938357105826012272472070910693443851911667721848542473785070747281964799379996923261457185847459711", "2740467233603031668807697475486217767705051", "-1905434239471820365929630558127219204166613")]
173+
public static void ModPow3LargeInt(string value, string exponent, string modulus, string expected)
174+
{
175+
BigInteger valueInt = BigInteger.Parse(value);
176+
BigInteger exponentInt = BigInteger.Parse(exponent);
177+
BigInteger modulusInt = BigInteger.Parse(modulus);
178+
BigInteger resultInt = BigInteger.Parse(expected);
179+
180+
// Once with default threshold
181+
Assert.Equal(resultInt, BigInteger.ModPow(valueInt, exponentInt, modulusInt));
182+
}
183+
159184
[Fact]
160185
public static void ModPow0Power()
161186
{

src/libraries/System.Runtime.Numerics/tests/BigInteger/op_divide.cs

+24
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ public static void RunDividePositivenonZero()
3232
VerifyDivideString(Print(tempByteArray1) + Print(tempByteArray2) + "b/");
3333
}
3434

35+
// Divide Method - One large and one half BigIntegers
36+
for (int i = -1; i <= 1; i++)
37+
for (int j = -1; j <= 1; j++)
38+
{
39+
tempByteArray1 = GetRandomByteArray(s_random, 512 + i);
40+
tempByteArray2 = GetRandomByteArray(s_random, 256 + j);
41+
VerifyDivideString(Print(tempByteArray1) + Print(tempByteArray2) + "b/");
42+
}
43+
3544
// Divide Method - One large and one small BigIntegers
3645
for (int i = 0; i < s_samples; i++)
3746
{
@@ -140,6 +149,21 @@ public static void RunOverflow()
140149
Assert.Equal(z, x / y);
141150
}
142151

152+
[Fact]
153+
public void D3n2nBound()
154+
{
155+
var right = (BigInteger.One << (BigIntegerCalculator.DivideBurnikelZieglerThreshold * 4 * 32 - 1))
156+
+ (BigInteger.One << (BigIntegerCalculator.DivideBurnikelZieglerThreshold * 2 * 32)) - 1;
157+
var rem = right - 1;
158+
159+
var qi = BigIntegerCalculator.DivideBurnikelZieglerThreshold * 8 * 32 * 4 - 1;
160+
var q = (BigInteger.One << qi) - 1;
161+
var left = q * right + rem;
162+
163+
var q2 = left / right;
164+
Assert.Equal(q, q2);
165+
}
166+
143167
private static void VerifyDivideString(string opstring)
144168
{
145169
StackCalc sc = new StackCalc(opstring);

0 commit comments

Comments
 (0)