Giffyguy
Giffyguy

Reputation: 21292

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.

Upvotes: 0

Views: 123

Answers (2)

Cahit
Cahit

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

Ben Voigt
Ben Voigt

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.

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

Upvotes: 2

Related Questions