#include<stdio.h>
int main(void)
{
unsigned short a,e,f ; // 2 bytes data type
unsigned int temp1,temp2,temp4; // 4 bytes data type
unsigned long temp3; // 8 bytes data type
a=0xFFFF;
e=((a*a)+(a*a))/(2*a); // Line 8
//e=(((unsigned long)(a*a)+(unsigned long)(a*a)))/(unsigned int)(2*a);
temp1=a*a;
temp2=a*a;
temp3=(unsigned long)temp1+(unsigned long)temp2; // Line 14
temp4=2*a;
f=temp3/temp4;
printf("%u,%u,%lu,%u,%u,%u,%u\n",temp1,temp2,temp3,temp4,e,f,a);
return(1);
}
如何修复算法(在第8行通过适当的中间结果类型转换)以便处理溢出?目前它打印65534而不是预期的65535。
为什么第14行必须进行类型转换?
答案 0 :(得分:2)
您必须在执行溢出操作之前提升类型。在第8行,这将是乘法,所以
e = ((unsigned) a * a + (unsigned) a * a) / (2 * (unsigned) a);
请注意,仅提升*
等对称操作的一个操作数就足够了。如果您愿意,可以使用(unsigned) a * (unsigned) a
,但(unsigned) a * a
也可以使用。
这将处理乘法,它将不再溢出。但是,现在增加会溢出。虽然32位unsigned
足够a * a
,但a * a + a * a
还不够。为此你需要unsigned long
(假设它更大)。您可以正式将+
的第一个操作数提升为unsigned long
e = ((unsigned long) ((unsigned) a * a) + (unsigned) a * a) / (2 * (unsigned) a);
(同样,仅提升+
的第一个操作数就足够了,这意味着第二个乘法可以留在unsigned
)。
以上看起来有点过于复杂,为了让它看起来更干净,你可以从头开始在第一次乘法中使用unsigned long
e = ((unsigned long) a * a + (unsigned) a * a) / (2 * (unsigned) a);
或者你可以在任何地方使用unsigned long
来使它看起来更干净
e = ((unsigned long) a * a + (unsigned long) a * a) / (2 * (unsigned long) a);
temp1 = a * a;
行中出现同样的问题。出于同样的原因,它们会溢出。你必须这样做
temp1 = (unsigned) a * a;
temp2 = (unsigned) a * a;
避免溢出,即在乘法之前提升a
。
这正是你在第14行中正确做的,即在添加之前提升+
的操作数,尽管只提升一个操作数就足够了
temp3 = (unsigned long) temp1 + temp2;
答案 1 :(得分:1)
如何修复算术(在第8行通过适当的中间结果类型转换)以便处理溢出?
您需要将分子中两个乘法的一个伙伴强制转换为足够大的类型。从(2^16-1)² = 2^32 - 2^17 + 1
开始,分子将溢出32位无符号整数,因此您需要更大的东西(通常是64位类型)。如果(unsigned) long
符合尺寸要求,
e = (((unsigned long)a*a)+((unsigned long)a*a))/(2*a);
将是明确定义且安全的(同样使用强制转换为long
,此处仅需要33位)。
然而,使用unsigned long
不可移植,因为它可能是32位类型。使用(unsigned) long long
或(u)int_least64_t
可移植且安全。
目前它打印65534而不是预期的65535。
这是因为如果-2
是int
(很可能是)一个二进制补码32位整数类型,在溢出时具有环绕行为,则计算结果为2*a*a/(2*a)
[有符号整数的溢出但是,是未定义的行为,因此编译器优化可能会改变它;如果编译器分析计算并看到它是a
,它可以将它简化为a
,因为这将是任何非零(2^16-1)² = 2^32 - 2^17 + 1 ≡ -2^17 + 1 = -131071 (mod 2^32)
(-131071) + (-131071) = -262142
(-262142)/131070 = -2
的结果,其中不会发生溢出;除以0也是UB,所以所有情况都被覆盖了。
*
通常的算术转换应用于+
,/
和unsigned short
的操作数,因此int
个操作数转换为unsigned short
(因为这里所有int
值都可以表示为int
s),算术是在-2
类型上执行的。
结果unsigned short
通过向其添加USHRT_MAX +1
转换为USHRT_MAX - 1
,从而导致e
之后成为temp1
的值。
为什么第14行必须进行类型转换?
因为添加temp2
和unsigned
会导致值超出32位2^32
范围,因此该结果的减少模{{1}}会改变总体结果。
答案 2 :(得分:0)
您首先使用“较大”类型(即,不要将a
声明为unsigned short
,只需将其声明为unsigned int
)。如果你需要施放,那么,只需施放一个操作的一侧。促销活动将在您身上进行。