用有符号长整型结果减去无符号长整型

Subtracting unsigned long longs with signed long long result?

本文关键字:长整型 无符号 结果 符号      更新时间:2023-10-16

假设我有这两种类型:

typedef unsigned long long uint64;
typedef signed long long sint64;

我有这些变量:

uint64 a = ...;
uint64 b = ...;
sint64 c;

我想从a中减去b,并将结果分配给c,很明显,如果差值的绝对值大于2^63,它将被包裹(或未定义),这是可以的。但对于绝对差值小于2^63的情况,我希望结果是正确的。

以下三种方式之一:

c = a - b; // sign conversion warning ignored
c = sint64(a - b);
c = sint64(a) - sint64(b);

他们中的哪一个能保证按标准工作?(为什么/如何?)

这三种方法都不起作用。如果差值为负(无论绝对值如何),则第一个会失败,第二个与第一个相同,如果任一操作数过大,则第三个会失败。

没有分支是不可能实现的。

c = b < a? a - b : - static_cast< sint64 >( b - a );

从根本上讲,unsigned类型使用无任何符号位的模运算。他们不知道自己被包装了,语言规范也没有用负数来识别包装。此外,赋值在有符号积分变量范围之外会导致实现定义的潜在无意义结果(积分溢出)。

考虑一台没有硬件在本机负整数和2的补码之间转换的机器。不过,它可以使用逐位否定和原生二的补码相加来执行二的补数相减。(也许很奇怪,但这正是C和C++目前所需要的。)然后,该语言将负值的转换留给程序员。唯一的方法是否定一个正值,这需要计算出的差值为正。所以…

最好的解决方案是首先避免将负数表示为大正数。

EDIT:我之前忘记了强制转换,它会产生一个大的无符号值,与其他解决方案等效!

Potatoswatter的答案可能是最实用的解决方案,但"没有分支就不可能实现"对我来说就像一块红抹布。如果你的假设系统实现了这样未定义的溢出/强制执行操作,my假设系统通过杀死小狗来实现分支。

所以我不完全熟悉标准会说什么,但这个怎么样:

sint64 c,d,r;
c = a >> 1;
d = b >> 1;
r = (c-d) * 2;
c = a & 1;
d = b & 1;
r += c - d;

我用一种相当详细的方式编写了它,因此各个操作都很清楚,但留下了一些隐式的强制转换。有什么东西没有定义?

Steve Jessop正确地指出,在差值正好为2^63-1的情况下,这确实失败了,因为在减去1之前乘法溢出。

因此,这里有一个更丑陋的版本,它应该涵盖所有下溢/上溢条件:

sint64 c,d,r,ov;
c = a >> 1;
d = b >> 1;
ov = a >> 63;
r = (c-d-ov) * 2;
c = a & 1;
d = b & 1;
r += ov + ov + c - d;

如果差值的绝对值大于2^63将包装(或未定义),这是可以的。但对于绝对差值小于2^63我希望结果是正确的。

然后,假设采用传统架构,您建议的所有三种符号都有效。显著差异第三个sint64(a) - sint64(b)调用未定义的行为当差异不可表示时,而前两者是保证环绕(无符号算术溢出保证环绕,从无符号到有符号的转换是实现定义的,而有符号算术溢出是未定义的)。