假设我有这两种类型:
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);
其中哪一项可以保证符合标准? (以及为什么/如何?)
答案 0 :(得分:4)
这三个人都没有工作。如果差值为负(无论是绝对值),第一个失败,第二个与第一个相同,如果任一操作数太大,则第三个失败。
没有分支机构就无法实施。
c = b < a? a - b : - static_cast< sint64 >( b - a );
从根本上说,unsigned
类型使用模运算而没有任何符号位。他们不知道他们四处乱转,语言规范也没有确定负数的回绕。此外,在有符号整数变量范围之外指定一个值会导致实现定义的,可能无意义的结果(整数溢出)。
考虑一台没有硬件的机器可以在本机负整数和二进制补码之间进行转换。但是,它可以使用按位求反和本机二进制补码相加来执行二次补码减法。 (奇怪,也许,但这就是C和C ++目前所需要的。)语言将它留给程序员,然后转换负值。唯一的方法是否定正值,这要求计算的差值为正。所以......
最好的解决方案是避免任何尝试首先将负数表示为大的正数。
编辑:我之前忘了演员阵容,这会产生一个大的无符号值,与其他解决方案等效!
答案 1 :(得分: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;
答案 2 :(得分:0)
如果差值的绝对值大于2 ^ 63 将包装(或未定义)哪个好。但对于那些情况 绝对差值小于2 ^ 63我希望结果是正确的。
然后,假设传统架构,那么您建议使用的所有三种符号都可以工作。显着的差异
是第三个sint64(a) - sint64(b)
调用未定义的行为
当差异不可表示时,前两个是
保证环绕(无符号算术溢出保证环绕并且从无符号到有符号的转换是实现定义的,而有符号算术溢出是未定义的。)