确定 Int64 变量和 Int64.MaxValue 之间差异的最佳方法是什么?

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

我正在编写一个处理非常大的数字的 x64 应用程序。

我正在处理一种情况,我需要确定将 UInt64 添加到 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 ...

我可能遗漏了一些明显的东西,但我似乎无法找到一种安全的方法来计算 Int64.MaxValue - signedInt64Var 的初始差异,因为这将始终产生一个值 >= 0 并且可能产生超出 Int64 范围的值(如果 signedInt64Var 为负)。

是否需要使用按位运算符手动屏蔽符号位?

或者是否有仅使用基本比较运算符和一些 signed/unsigned 转换的解决方案?

我想尽可能避免转换为十进制 - 高容量性能优先级。

其实很简单,cast就够了,没有剩下需要特别注意的corner case。

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
}

但是,这听起来像是一个 XY 问题。解决您的原始任务

I need to determine whether it is safe to add a UInt64 to an Int64

我就做一下操作,看看你的结果是否有效。在C++中,你必须提前测试是否会发生溢出,因为有符号整数溢出是未定义的行为。在 C# 中没有这样的问题。

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);
}

这些溢出测试依赖于常规(无界)算术的不变量——如果它们失败,则发生溢出。

  • 当且仅当另一个操作数为负时,总和小于一个操作数
  • b 是无符号的,因此永远不会为负数

使用 checked 关键字,我有一种稍微不同的方法来执行此操作。我假设 unsigned long 可能大于 signed long 的最大正值,只要结果在 Int64 的范围内即可。

这是 add_if_in_range() 函数,以及几个测试其 return 值的测试用例:

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");
    }

}