【问题标题】:Add a long and ulong in C#在 C# 中添加 long 和 ulong
【发布时间】:2017-02-14 22:44:34
【问题描述】:

我正在使用的 api 有一个 ulong 字段(我无法更改它)。我需要通过long 数量调整此字段(也可以是int)。但是我不能把这些加在一起:Operator '+' is ambiguous on operands of type 'ulong' and 'long'

我无法将long 转换为ulong,因为我会丢失符号,我无法将ulong 转换为long,因为我可能会丢失部分价值。

最终我想尝试调整该值,如果它会导致换行,我想返回 false 并且不完成调整。如果它不换行,我想完成调整并返回 true;我可以做这部分,但前提是我首先能想出一种方法将这两个字段加在一起。

public ulong Value;
public bool AdjustValue(long amount)
{
    // Operator '+' is ambiguous on operands of type 'ulong' and 'long'
    ulong adjustValue = Value + amount; 

    if (amount < 0 && adjustValue > Value)
        return false;
    if (amount > 0 && adjustValue < Value)
        return false;

    Value = adjustValue;
    return true;
}

我该如何解决这个问题?

【问题讨论】:

  • 不清楚你想做什么,但也许你可以使用 System.Numerics 中的 BigInteger 类找到解决方案
  • if (long &lt; 0) ulong -= (ulong)(-long); else ulong += (ulong)long;?
  • @itsme86 - 完美,我试过了,但错过了-long 部分!谢谢
  • 没问题。对我来说似乎仍然很笨拙,并且可能比简单的添加操作生成更多的 IL 指令。可能是某个地方更好的解决方案。
  • 也许将两者都转换为十进制然后将结果转换回来?

标签: c# math


【解决方案1】:

有人会讨厌这个答案,但您可以使用十进制数据类型,因为它能够存储 28-29 位有效数字,而 2^64 只有 20 位数字。

使用浮点数据类型自然会在性能方面产生一些开销,但另一方面,此代码确实很容易阅读,并且可能比需要if 的解决方案具有更好的分支预测/流水线。

public static ulong Add(this ulong baseValue, long offset)
{
    decimal adjustedValue = (decimal)baseValue + offset;
    return (ulong)adjustedValue;
}

如果你溢出,系统会抛出一个足够好的ArgumentOutOfRangeException,但你也可以自己滚动:

public static ulong Add(this ulong baseValue, long offset)
{
    decimal adjustedValue = (decimal)baseValue + offset;
    if (adjustedValue > ulong.MaxValue) throw new ArgumentOutOfRangeException("Too big to fit!");
    if (adjustedValue < ulong.MinValue) throw new ArgumentOutOfRangeException("Too small to fit!");
    return (ulong)adjustedValue;
}

【讨论】:

  • 我正在阅读答案,现在我已经成长为一名开发人员,我认为这实际上是最好的答案,因为我学会了不要害怕抛出异常并在适当的地方实际使用它们。
  • @AnthonyNichols:没关系……这是你的问题。也就是说,重要的是要记住,即使没有异常,与 CPU 理解的类型相比,decimal 类型也会产生巨大的性能开销,当然异常本身的成本也非常高。这适用于此类计算不经常发生的代码(在您的场景中很可能就是这种情况),但在计算支配代码执行时间的情况下使用这种方法是不明智的。
【解决方案2】:

为了可读性,您可能更喜欢评论中的建议:

adjustValue = Value;
if (amount < 0)
    adjustValue -= (ulong)(-amount);
else
    adjustValue += (ulong)amount;

这清楚地表明了您期望数学如何工作。

但是……

几乎每个现代硬件 CPU 中的整数表示都是二进制补码。原因之一是使用二进制补码的数学有一个非常好的特性:无论是处理有符号值还是无符号值,都可以得到相同的加法和减法二进制表示。

这意味着您只需将调整值转换为 ulong 即可进行添加。根据 C# 语言规则,强制转换将简单地将有符号值的二进制表示重新解释为无符号。添加无符号值的效果与添加具有相同二进制表示的有符号值的效果完全相同。

这是一个显示此工作的代码示例:

static void Main(string[] args)
{
    ulong[] values = { ulong.MinValue, ulong.MaxValue, long.MaxValue };
    long[] adjust = { 0, 1, -1, long.MinValue, long.MaxValue };

    for (int i = 0; i < values.Length; i++)
    {
        for (int j = 0; j < adjust.Length; j++)
        {
            ulong value = values[i];

            bool result = AdjustValue(adjust[j], ref value);
            Console.WriteLine($"{values[i]} + {adjust[j]} = {values} (overflow: {!result})");
        }
    }
}

static bool AdjustValue(long amount, ref ulong value)
{
    ulong adjustValue = value + (ulong)amount;

    if (amount < 0 && adjustValue > value)
        return false;
    if (amount > 0 && adjustValue < value)
        return false;

    value = adjustValue;
    return true;
}

注意:默认情况下,运行时的数学运算是未选中的。但是,有一个编译器开关可以改变这一点。如果您在启用该开关的情况下编译代码,强制检查算术运算,则需要将上述内容包装在 unchecked 块中以避免出现溢出异常。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-15
    • 2019-02-26
    • 2012-10-14
    • 1970-01-01
    • 1970-01-01
    • 2011-04-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多