Address bugs in BigInteger#27280
Address bugs in BigInteger#27280AntonLapounov merged 6 commits intodotnet:masterfrom ts2do:BigIntegerFix
Conversation
Method Add(ref BigInteger lhs, uint value, ref BigInteger result) would store most of the result blocks into lhs instead of result Method ShiftLeft(ulong input, uint shift, ref BigInteger output) with a shift argument exceeding 32 would generally compute the higher blocks incorrectly
AntonLapounov
left a comment
There was a problem hiding this comment.
Too many bugs in this code. For Add we should also set result._length in case carry == 0. For ShiftLeft the first if misses setting output. It should read like this:
if ((input == 0) || (shift == 0))
{
output.SetUInt64(input);
return;
}|
Looking at the things being fixed in this PR and the item called out by Anton. These bugs don't cause issues in production because the code code paths/failure cases in question aren't currently hit (and I don't believe ever will be hit). This type is only meant for (and is designed around) For example, |
|
Also, for reference, it looks like some of these bugs existed in the original |
|
@tannergooding I would not trust other parts of this code either. For instance, |
Yes, but it should never actually happen given the existing usages and if it did; would just result in a slower computation; not necessarily an incorrect computation. That is, the code as is being used; and as was ported from the cpp code; is still functioning correctly. There are certainly cases where |
Well, var x = new BigInteger();
x.SetUInt64(0);
var y = new BigInteger(0);
// Outputs 1
Console.WriteLine(BigInteger.Compare(ref x, ref y));I agree that if there were an easy way to hit one of these bugs, we would have hit it a long time ago. Still that makes it very challenging to reason about this code. |
|
@ts2do Thank you for spotting these bugs. As @tannergooding mentioned, the best approach is to remove both problematic methods by inlining them into their callers and simplifying. Please let us know if you might help with that or prefer us to change the code. |
|
The static Add method overload is indeed being used directly by in src/System.Private.CoreLib/shared/System/Number.Dragon4.cs (which is invoked by FormatSingle and FormatDouble in some cases). |
|
I've made the requested changes. I also have a more heavily modified version of BigInteger that I believe makes it 1) more consistent (e.g., results of static methods are always provided via Here's a gist with all of the changes: A few more notes: |
|
@ts2do Note that Dragon4 uses different |
Inline Add(ref BigInteger, uint, ref BigInteger) into Add(uint) Inline ShiftLeft(ulong, uint, ref BigInteger) into Pow2 Inline ExtendBlock and ExtendBlocks into Pow2 Handle 0 in SetUInt32 and SetUInt64
|
My mistake, I was searching some local changes that I was toying around with. Sorry for the delay, I've been having some trouble running tests against the changes (though I got it building). I plan to redo my setup with a fresh installs soon, so until then, I guess I'll use CI to test it? |
|
@ts2do Thank you. Your changes look great in general. Reviewing now. |
|
@tannergooding Don't we have a real product bug in var x = new BigInteger(7);
var y = new BigInteger(0);
BigInteger.Multiply(ref x, 6, ref y);
// Expected value: 42, actual value: 0
Console.WriteLine(y.ToUInt64()); |
There is a bug, but I don't believe it is one that can repro in production. The usages in |
@tannergooding What about this code path, where we multiply coreclr/src/System.Private.CoreLib/shared/System/Number.Dragon4.cs Lines 222 to 226 in 5b1c001 |
I'd need to check some math to determine if that is fine or not, but I believe it still works out due to how the margins exist and where they exist. Still noting that this was ported from the native code and so the bug, if it exists, has been around basically forever. |
|
@tannergooding I have tried Dragon4Double(1.0 / (1L << 31), -1, true, ref buffer);under a debugger and noticed that After fixing Would you be able to look at them? |
|
It seems that before this fix For instance, 2⁵⁵ is converted to |
|
Looks like its failing because the This assertion "should" still hold true as we have at least 1 fractional digit present |
|
Put up a PR for the fix here: #27688 |
|
Thank you for your contribution. As announced in dotnet/coreclr#27549 this repository will be moving to dotnet/runtime on November 13. If you would like to continue working on this PR after this date, the easiest way to move the change to dotnet/runtime is:
|
|
@ts2do, if you rebase your changes ontop of (or merge with) the latest master, everything should pass now and we can get this merged 😄 |
|
Everything should be all set. |
|
@tsdo Nice work — thanks a lot! |
* Method Add(ref BigInteger lhs, uint value, ref BigInteger result) would store most of the result blocks into lhs instead of result. * Method ShiftLeft(ulong input, uint shift, ref BigInteger output) with a shift argument exceeding 32 would generally compute the higher blocks incorrectly. * Multiply(ref BigInteger lhs, uint value, ref BigInteger result) would not set result._length in some cases. * IsZero() would incorrectly return false for non-canonical zeros with _length > 0. Fix: * Inline Add(ref BigInteger, uint, ref BigInteger) into Add(uint). * Inline ShiftLeft(ulong, uint, ref BigInteger) into Pow2. * Inline ExtendBlock and ExtendBlocks into Pow2. * Properly handle 0 in SetUInt32 and SetUInt64. Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
* Method Add(ref BigInteger lhs, uint value, ref BigInteger result) would store most of the result blocks into lhs instead of result. * Method ShiftLeft(ulong input, uint shift, ref BigInteger output) with a shift argument exceeding 32 would generally compute the higher blocks incorrectly. * Multiply(ref BigInteger lhs, uint value, ref BigInteger result) would not set result._length in some cases. * IsZero() would incorrectly return false for non-canonical zeros with _length > 0. Fix: * Inline Add(ref BigInteger, uint, ref BigInteger) into Add(uint). * Inline ShiftLeft(ulong, uint, ref BigInteger) into Pow2. * Inline ExtendBlock and ExtendBlocks into Pow2. * Properly handle 0 in SetUInt32 and SetUInt64. Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
* Method Add(ref BigInteger lhs, uint value, ref BigInteger result) would store most of the result blocks into lhs instead of result. * Method ShiftLeft(ulong input, uint shift, ref BigInteger output) with a shift argument exceeding 32 would generally compute the higher blocks incorrectly. * Multiply(ref BigInteger lhs, uint value, ref BigInteger result) would not set result._length in some cases. * IsZero() would incorrectly return false for non-canonical zeros with _length > 0. Fix: * Inline Add(ref BigInteger, uint, ref BigInteger) into Add(uint). * Inline ShiftLeft(ulong, uint, ref BigInteger) into Pow2. * Inline ExtendBlock and ExtendBlocks into Pow2. * Properly handle 0 in SetUInt32 and SetUInt64. Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
* Method Add(ref BigInteger lhs, uint value, ref BigInteger result) would store most of the result blocks into lhs instead of result. * Method ShiftLeft(ulong input, uint shift, ref BigInteger output) with a shift argument exceeding 32 would generally compute the higher blocks incorrectly. * Multiply(ref BigInteger lhs, uint value, ref BigInteger result) would not set result._length in some cases. * IsZero() would incorrectly return false for non-canonical zeros with _length > 0. Fix: * Inline Add(ref BigInteger, uint, ref BigInteger) into Add(uint). * Inline ShiftLeft(ulong, uint, ref BigInteger) into Pow2. * Inline ExtendBlock and ExtendBlocks into Pow2. * Properly handle 0 in SetUInt32 and SetUInt64. Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
* Method Add(ref BigInteger lhs, uint value, ref BigInteger result) would store most of the result blocks into lhs instead of result. * Method ShiftLeft(ulong input, uint shift, ref BigInteger output) with a shift argument exceeding 32 would generally compute the higher blocks incorrectly. * Multiply(ref BigInteger lhs, uint value, ref BigInteger result) would not set result._length in some cases. * IsZero() would incorrectly return false for non-canonical zeros with _length > 0. Fix: * Inline Add(ref BigInteger, uint, ref BigInteger) into Add(uint). * Inline ShiftLeft(ulong, uint, ref BigInteger) into Pow2. * Inline ExtendBlock and ExtendBlocks into Pow2. * Properly handle 0 in SetUInt32 and SetUInt64. Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
* Method Add(ref BigInteger lhs, uint value, ref BigInteger result) would store most of the result blocks into lhs instead of result. * Method ShiftLeft(ulong input, uint shift, ref BigInteger output) with a shift argument exceeding 32 would generally compute the higher blocks incorrectly. * Multiply(ref BigInteger lhs, uint value, ref BigInteger result) would not set result._length in some cases. * IsZero() would incorrectly return false for non-canonical zeros with _length > 0. Fix: * Inline Add(ref BigInteger, uint, ref BigInteger) into Add(uint). * Inline ShiftLeft(ulong, uint, ref BigInteger) into Pow2. * Inline ExtendBlock and ExtendBlocks into Pow2. * Properly handle 0 in SetUInt32 and SetUInt64. Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
Method
Add(ref BigInteger lhs, uint value, ref BigInteger result)would store most of the result blocks intolhsinstead ofresultMethod
ShiftLeft(ulong input, uint shift, ref BigInteger output)with a shift argument not evenly divisible by 32 would generally compute the higher blocks incorrectly