Search code examples
c#64-bitunsignedsignedunsigned-integer

What's the best method of determining the difference between an Int64 variable and Int64.MaxValue?


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.


Solution

  • 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.

    • A sum is less than one operand if and only if the other operand is negative
    • b is unsigned and therefore never negative