Reputation: 21292
I'm writing an x64 application that crunches very large numbers.
I'm dealing with a situation where I need to determine whether it is safe to add a UInt64 to an Int64.
// Error: Operator '<=' is ambiguous on operands of type 'ulong' and 'long'
if (UNsignedUInt64Var <= Int64.MaxValue - signedInt64Var)
signedInt64Var = (Int64)(((UInt64)signedInt64Var) + UNsignedUInt64Var); // Maybe works?
else
// ... move on to a different operation ... unrelated ...
I might be missing something obvious, but I can't seem to figure out a safe way to compute the initial difference of Int64.MaxValue - signedInt64Var
since this will always produce a value >= 0
and could potentially produce a value outside the range of Int64 (if signedInt64Var is negative).
Do I need to use bitwise operators to mask the sign bit manually?
Or is there a solution using only basic comparison operators and maybe some signed/unsigned casting?
I'd like to avoid converting to decimal if at all possible - high-volume performance priorities.
Upvotes: 0
Views: 123
Reputation: 2534
Using the checked keyword, I have a somewhat different way of doing this. I'm assuming that the unsigned long may be larger than the maximum positive value for the signed long, as long as the result fits within the range of Int64.
Here's the add_if_in_range() function, and several test cases to check its return values:
public static class Test {
public static Int64 add_if_in_range(Int64 basevalue, UInt64 newvalue) {
if (basevalue < 0 & (newvalue >= (UInt64)Int64.MaxValue) ) {
UInt64 ubase = (UInt64)(-basevalue);
return checked((Int64)(newvalue-ubase));
} else {
return checked(basevalue + (Int64)newvalue);
}
}
}
void Main()
{
// Some Test Cases:
Int64 l; UInt64 ul;
l = Int64.MaxValue; ul = 100; // result causes overflow of Int64
try {
Console.WriteLine(Test.add_if_in_range(l, ul));
} catch (OverflowException) {
Console.WriteLine("Adding would cause an overflow");
}
l = Int64.MaxValue - 101; ul = 100; // result should be +9223372036854775806
try {
Console.WriteLine(Test.add_if_in_range(l, ul));
} catch (OverflowException) {
Console.WriteLine("Adding would cause an overflow");
}
l = Int64.MaxValue/2; ul = (UInt64)Int64.MaxValue/2; // result should be +9223372036854775806
try {
Console.WriteLine(Test.add_if_in_range(l, ul));
} catch (OverflowException) {
Console.WriteLine("Adding would cause an overflow");
}
l = Int64.MinValue; ul = 100; // result should be -9223372036854775708
try {
Console.WriteLine(Test.add_if_in_range(l, ul));
} catch (OverflowException) {
Console.WriteLine("Adding would cause an overflow");
}
l = Int64.MinValue; ul = UInt64.MaxValue; // result should be +9223372036854775807
try {
Console.WriteLine(Test.add_if_in_range(l, ul));
} catch (OverflowException) {
Console.WriteLine("Adding would cause an overflow");
}
l = Int64.MinValue; ul = UInt64.MaxValue-100; // result should be +9223372036854775807
try {
Console.WriteLine(Test.add_if_in_range(l, ul));
} catch (OverflowException) {
Console.WriteLine("Adding would cause an overflow");
}
l = Int64.MinValue + 1; ul = UInt64.MaxValue; // result causes overflow of Int64
try {
Console.WriteLine(Test.add_if_in_range(l, ul));
} catch (OverflowException) {
Console.WriteLine("Adding would cause an overflow");
}
}
Upvotes: 1
Reputation: 283733
It's actually really easy, casts are enough and there are no remaining corner cases that need special attention.
unchecked {
UInt64 n1 = (UInt64) Int64.MaxValue; // this is in range
UInt64 n2 = (UInt64) signedInt64Var; // this may wrap-around, if value is negative
UInt64 result = n1 - n2; // this is correct, because it
// wraps around in exactly the same cases that n2 did
}
However, this smells like an XY problem. Addressing your original task
I need to determine whether it is safe to add a UInt64 to an Int64
I would just do the operation and test whether your result is valid. In C++, you have to test in advance whether overflow could occur, because signed integer overflow is undefined behavior. In C# there is no such concern.
Int64 a;
UInt64 b;
unchecked {
UInt64 uresult = b + (UInt64)a;
bool unsigned_add_was_safe = (uresult < b) == (a < 0);
Int64 sresult = a + (Int64)b;
bool signed_add_was_safe = (sresult >= a);
}
These overflow tests rely on invariants that are true for conventional (unbounded) arithmetic -- if they fail, then overflow occurred.
b
is unsigned and therefore never negativeUpvotes: 2