使用gcc在C语言中将浮点数转换为无符号整数

6
我正在使用gcc测试一些简单的浮点数到无符号整数的转换。以下代码片段给出结果为0。
const float maxFloat = 4294967295.0;
unsigned int a = (unsigned int) maxFloat;
printf("%u\n", a);

打印出了0(我认为这非常奇怪)。

另一方面,下面的代码片段:

const float maxFloat = 4294967295.0;
unsigned int a = (unsigned int) (signed int) maxFloat;
printf("%u\n", a);

输出2147483648,我认为这是正确的结果。

为什么会得到两个不同的结果?


2
@StoryTeller:有一个规范说明了表示424967295并打印它的结果是什么:数学。user3523954声称他们可以接受失去精度但得到零是错误的,这是因为他们希望C更加符合数学。例如,将越界的浮点值转换为整数的结果可能是饱和而不是未定义。他们的陈述相当简单易懂,而你的回应则显得嘲笑或者过于刻薄。 - Eric Postpischil
1
@EricPostpischil我非常不同意。已经给出了足够关于C语言的解释。如果OP坚持认为这是“错误”的,我不知道还有什么方法可以帮助他了。 - user2371524
1
@EricPostpischil - 这里你是错的。OP的评论明确表明他们认为他们的实现有问题。正如你自己所指出的那样,他们有理由这么认为,因为数学与他们观察到的结果不符。 - StoryTeller - Unslander Monica
1
@EricPostpischil - 但我不会同意你的观点。所以我们陷入了僵局。恐怕没有什么可做的了。这次我会原谅你那带有傲慢口吻的态度。祝你有美好的一天。 - StoryTeller - Unslander Monica
1
@EricPostpischil引用OP的话:“我理解未定义的含义”。进一步的引用包括“我不同意”和“计算机和编译器是有限状态机”(好的,非常感谢!)。对于我来说,讨论到此结束,再见。 - user2371524
显示剩余16条评论
2个回答

6
如果您首先执行此操作:
printf("%f\n", maxFloat);

你会得到以下输出:
4294967296.000000

假设一个float被实现为IEEE754单精度浮点类型,值4294967295.0无法被该类型准确表示,因为精度不够。它能够存储的最接近的值是4294967296.0。
假设一个int(以及unsigned int)是32位,值4294967296.0超出了这两种类型的范围。当给定整数类型无法表示值时,将浮点类型转换为整数类型会引发未定义行为
这在C标准的第6.3.1.4节中有详细说明,该节规定了从浮点类型到整数类型的转换。

1 当将实浮点类型的有限值转换为除了_Bool之外的整数类型时,小数部分将被丢弃(即向零舍入)。如果整数部分的值无法由整数类型表示,则行为未定义。61)

...

61) 将整数类型的值转换为无符号类型时执行的余数操作在将实浮点类型的值转换为无符号类型时不需要执行。因此,可移植实浮点值的范围为(-1,Utype_MAX + 1)。

上述段落中的脚注引用了第6.3.1.3节,其中详细介绍了整数到整数的转换:

1 当一个整数类型的值被转换为除了_Bool以外的另一种整数类型时,如果该值可以用新类型表示,则它保持不变。

2 否则,如果新类型是无符号的,则通过反复添加或减去比新类型中可以表示的最大值多一的值,直到该值在新类型的范围内进行转换。

3 否则,新类型为有符号类型且该值无法表示;结果要么是实现定义的,要么会引发实现定义的信号。

您在第一个代码片段中看到的行为与将整数类型的值转换为无符号类型时看到的行为一致(当涉及到整数时),但由于要转换的值具有浮点类型,因此这是未定义的行为。

仅因为一个实现这样做并不意味着所有实现都会这样做。 实际上,如果更改优化设置,则gcc会给出不同的结果。

例如,在我的机器上使用gcc 5.4.0,给定以下代码:

float n = 4294967296;
printf("n=%f\n", n);
unsigned int a = (unsigned int) n;
int b = (signed int) n;
unsigned int c = (unsigned int) (signed int) n;
printf("a=%u\n", a);
printf("b=%d\n", b);
printf("c=%u\n", c);

我使用-O0得到以下结果:

n=4294967296.000000
a=0
b=-2147483648
c=2147483648

而使用 -O1 编译选项:

n=4294967296.000000
a=4294967295
b=2147483647
c=2147483647

如果另一方面定义nlonglong long,则始终会得到此输出:
n=4294967296
a=0
b=0
c=0

将有符号整数转换为无符号整数在C标准中已经定义明确,如上所述。而将有符号整数转换为另一种有符号整数类型的结果是由实现定义的,gcc定义如下:

当将整数转换为有符号整数类型时,如果该值无法表示为该类型的对象,则产生的结果或引发的信号(C90 6.2.1.2、C99和C11 6.3.1.3)。

对于宽度为N的类型的转换,该值被模2^N减少以使其处于类型范围内;不会引发任何信号。


即使我使用 -O1 编译,仍然得到 0。可能是因为我使用的是旧版的 gcc(4.3.4)。 - user3523954
2
@user3523954 这是未定义行为的另一个例子。因为标准在这种情况下没有强制要求,所以实现可以随时自由地进行任何操作,并且不必详细记录其正在执行的操作。 - dbush

2
假设使用IEEE 754浮点数,数字4294967295.0无法在float中精确存储。相反,它将被存储为4294967296.0(即2的32次方)。
进一步假设您的unsigned int具有32个值位,这比适合unsigned int大1,因此根据C标准,转换的结果是未定义的-- 0是“合理”的结果。
在第二种情况下,您也有未定义的行为,我不知道在表示级别上发生了什么。事实是,这个数字对于32位有符号int来说太大了(仍然假设这是您的机器使用的类型)。

从您问题中的这个评论开始:

打印出2147483648,我相信这是正确的结果。

我认为您想要查看内存中您的float表示。强制转换会转换值,因此这不是查看表示方式的方法。以下代码可以实现:

int main(void) {
    const float maxFloat = 4294967295.0;
    unsigned char *floatBytes = &maxFloat;
    for (int i=0; i < sizeof maxFloat; ++i)
    {
        printf("0x%02x ", floatBytes[i]);
    }
    puts("");
}

在线示例


我认为在这种情况下转换的结果实际上是未定义的,如果我正确地阅读了6.3.1.4p1 - StoryTeller - Unslander Monica
@StoryTeller 这是正确的 - 我已经在这里做出了很多假设 ;) 我会更好地表达它。 - user2371524
那么这两段代码都遇到了未定义的问题...对吗?它们怎么会有如此不同的结果呢?我明白"未定义"的含义,但仍感到困惑。 - user3523954
那么你不认为将不同类型转换时会看到不同的“状态”吗?那好吧,只是不要相信它。 - user2371524
1
很难相信“未定义(undefined)”在两种不同情况下有如此不同的含义。- 请相信 - StoryTeller - Unslander Monica
显示剩余5条评论

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