C算术转换:将无符号数与有符号数相乘,结果为浮点数

3
int main()
{
    printf("Hello World\n");
    int x = -10;
    unsigned y = 25;
    float z = x*y;
    printf("x=%d,y=%u,z=%f\n",x,y,z);
    return 0;
}

当我运行上述代码时,我得到了以下输出结果:

你好世界
x=-10,y=25,z=4294967046.000000

我的问题是: 对于第二个printf语句,我本来希望得到的结果是z=(float) ( (unsigned)(-10)*25 ) = (float) (4294967286 x 25) = (float) 107374182150,那么我错在哪里了呢?

你的 unsigned 是 int、float 还是遵循什么数据类型? - TechGeek49
在这种情况下,您应该使用“unsigned int”,因为它是一个离散的正值。 - TechGeek49
@TechGeek49:unsigned 限定符 变成了 unsigned int 类型。详见 C11 6.7.2。 - paxdiablo
我明白了。那是实际上的方式吗?还是像我在 C++、Java 等中实现的那样使用 unsigned int 更合适? - TechGeek49
@TechGeek49:我个人更喜欢使用更明确的unsigned int,但是在类型说明符和类型之间存在一些脱节。 - paxdiablo
2个回答

5
这里是发生的事情。根据 C11 6.3.1.8 Usual arithmetic conversions ("otherwise" 在这里起作用,因为之前的段落讨论了当其中一个类型已经是浮点型时会发生什么):

否则,如果具有无符号整数类型的操作数的级别大于或等于另一个操作数的类型的级别,则具有带符号整数类型的操作数将转换为具有无符号整数类型的操作数的类型。

这意味着您的符号值 -10 变成了无符号值 0xffff'fff6 或者 4,294,967,286。将它乘以 25 得到的是 107,374,182,150 或者 0x18'ffff'ff06(这是您所要的结果)。
然而,此时并没有进行 float 计算,因为乘法是原始的整数计算,其结果是一个整数。并且,由于您的无符号整数长度为32位,所以结果被截断为0xffff'ff06 或者 4,294,967,046
然后,你把它放进float里面。
为了使其与预期结果匹配,您应该更改表达式以强制执行此操作。
float z = 1.0f * (unsigned)x * y;

这将把“int * unsigned-int”计算更改为“float * unsigned-int * unsigned-int”,先使用无符号转换确保将x转换为等效的无符号值,然后乘以“1.0f”确保在“float”领域中执行乘法以避免整数截断。

我点了个赞,因为我发现它很有用,尽管我更喜欢使用unsigned的显式声明。 - TechGeek49

3

在回答@paxdiablo正确答案后,结果的起点是由于unsigned拥有与int相同的rank(级别),例如:

如果有对应的有符号整数类型,则无符号整数类型的任何级别都必须等于相应的有符号整数类型的级别。 C11标准-6.3.1算术运算操作数(p1)

这在@paxdiablo的答案中提到的整数转换中起作用:

6.3.1.8通常算术转换

  • 否则,在两个操作数上执行整数晋升。 然后将以下规则应用于晋升的操作数:
    • 否则,如果具有无符号整数类型的操作数的级别大于或等于其他操作数类型的级别,则将具有带符号整数类型的操作数转换为具有无符号整数类型的操作数的类型。

问题在于,负值-10(负数)在(几乎所有计算机中)以补码的形式存储。在补码中,-10的值取10的按位取反并加上1(因此在二进制中, 00001010变成了符号扩展到32位的11110110)。也就是说:

11111111111111111111111111110110

在这个过程中,unsigned值为4294967286。当它被乘以25时,它超出了unsigned的范围,因此该值被取模减少,直到它适合unsigned的范围,结果为42949670466.2.5类型(p9)

我错过了什么?

缺失的部分是理解将unsigned乘法的结果赋值为浮点数值。来自x * y的中间结果是unsignedfloat f = x * y;只是将结果分配给float

你想要的是将中间计算作为float完成,所以将其中一个操作数(而不是结果)强制转换为float,例如:

float f = (float)x * y

无论哪个值被转换为浮点数float,以下的方式都是完全正确的:
float f = x * (float)y;

现在的结果将会是-250


2
只有一个小问题,David,我认为(虽然我可能错了),它是补码(即互补)而不是恭维(如“你今天看起来很好”)。在ISO C和C++中,正在进行一些动作,以将二进制补码方法作为未来唯一的有符号编码方案。 - paxdiablo
1
好的,你今天看起来不错 - 但你也在那里 - 拼写检查没有救我...会修复。 - David C. Rankin
感谢 @DavidC.Rankin 的参与。我忽略了一个部分,即32位乘法具有32位输出(无符号环绕直到适合您提到的大小)。 - Vikas Yadav
1
很高兴它有所帮助。内容很多,需要多次回到标准来巩固,即使这样,您最终仍然会回到标准 - 只是为了确保 :) - David C. Rankin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接