4

假设我有这两种类型:

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

它们中的哪一个可以保证按标准工作?(以及为什么/如何?)

4

3 回答 3

4

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

没有分支就不可能实现。

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

从根本上说,unsigned类型使用没有任何符号位的模运算。他们不知道他们环绕,并且语言规范没有用负数识别环绕。此外,分配一个超出有符号整数变量范围的值会导致实现定义的、可能无意义的结果(整数溢出)。

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

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

编辑:我之前忘记了演员表,这会产生一个很大的无符号值,相当于其他解决方案!

于 2012-12-11T06:31:12.823 回答
1

Potatoswatter 的回答可能是最务实的解决方案,但“没有分支就不可能实现”对我来说就像是红布对公牛。如果您的假设系统实现了类似的未定义的溢出/强制转换操作,那么我的假设系统通过杀死小狗来实现分支。

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

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;
于 2012-12-11T07:17:51.100 回答
0

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

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

于 2012-12-11T06:24:44.090 回答