7

首先,这不是关于精度或类似问题的问题。

我的问题是,编译器如何决定如何表示一个数字?

我们以 C 为例。我写

double d = 4.5632;

它如何选择它的二进制表示?我知道它没有精确表示,那么它如何选择最接近的可表示数字?它是在编译时完成的吗?它是由CPU还是操作系统完成的?

仅在您知道这是如何发生的情况下回答,诸如“不要担心”之类的回答没有帮助。此外,“它取决于平台”也没有帮助,您可以选择一个平台并为此进行解释。

4

5 回答 5

6

编译器不决定(通常)。CPU(通常)有一个浮点单元,它需要浮点值以特定格式表示(通常是IEEE-754)。当然,可以模拟完全不同的架构,在这种情况下,编译器/模拟器作者可以自由选择完全不同的表示。但这不是典型的。

至于如何将特定的词法表示4.5632转换为底层表示,这是由 C 标准指定的。所以从 C99 标准的第 6.4.4.2 节开始(我已经强调了最相关的部分):

有效数字部分被解释为(十进制或十六进制)有理数;指数部分的数字序列被解释为十进制整数。对于十进制浮点常量,指数表示 10 的幂,有效数部分将按其缩放。对于十六进制浮点常量,指数表示 2 的幂,有效数部分将按其缩放。对于十进制浮点常量,以及当 FLT_RADIX 不是 2 的幂时的十六进制浮点常量,结果要么是最接近的可表示值,要么是紧邻最接近的可表示值的较大或较小的可表示值,在实现定义中选择方式. 对于 FLT_RADIX 是 2 的幂时的十六进制浮点常量,结果被正确舍入。

这将在编译时完成(尽管标准没有强制要求)。

于 2012-04-28T14:30:10.350 回答
0

是的,特定的转换是在编译时完成的,因为double d = 4.5632;它是一个编译时常量。编译到您的代码中的是该值以目标体系结构使用的浮点格式表示。在 32 位 IEEE-754 表示的情况下,这是0x409205BC. CPU 如何“知道”这是一个有点接近 4.5632 的值取决于浮点标准本身。同样,在 32 位 IEEE-754 的情况下,我们有 1 位用于符号,8 位用于指数,23 位用于尾数。

当涉及到舍入时,有几种方法可以应用。IEEE-754 规范提到了四种方法:四舍五入到最近,四舍五入到零,四舍五入到负无穷大,四舍五入到正无穷大。

于 2012-04-28T14:35:10.300 回答
0

编译器生成在平台上运行的程序。该平台可能在编译器之前就已经存在,反之亦然。所有事物的二进制表示构成 ABI,它本质上是编译器输出的规范。最后,无论出于何种原因,事情都完成了,但希望有一个 ABI 可以准确地说明发生了什么。

在实践中,几乎所有平台都根据 IEEE 754(又名 IEC 559)实现浮点运算。这个相当古老的国际标准定义了浮点数的位的含义,以及程序十进制表示应如何四舍五入为浮点数。点值。

没有 FPU 的平台通常仍会在软件中从 IEEE 754 数字中打包和解包位域,因为它们很可能以二进制形式出现在文件中。

对互操作性和数值精度要求有限的平台(例如 GPU)可能会放宽 IEEE 754 要求的精度标准,但它定义的数值范围最适合各种应用。

当然,如果你想要最终的便携性,你不能依赖任何东西。但可以肯定的是,从十进制到二进制 FP(假设 FPU 本身不是十进制)的转换是在编译时执行的。

于 2012-04-28T14:35:41.877 回答
0

对于您的具体示例,是的,二进制表示是在编译时编码的。它可能会调用一个 C 库(atod、sscanf 等),并且该库对截断或舍入所做的任何事情都会发生。并且编译器的“功能”或“规则”不一定与您执行相同操作时发生的运行时规则相同。无论如何,您都不应该检查与浮点的等价性,但是如果您要获取编译时值,然后为程序提供一个字符串并转换该运行时(假设您在命令行上传递值 4.5632 并使用其中一个库调用)你不一定会得到相同的浮点值。我已经看到编译器(gcc 等)在编译时常量方面做得非常糟糕,因此通常,

double d; int a;
a 45632;
d = a;
d/=10000;

即使它进行了优化,它也往往会得到更好、更准确的答案。

您确实在 int 到 double 转换中冒着硬件 + OS 错误的风险,Hauser 对 FPU 错误发表了一些评论,这些错误往往出现在 int to float 和 float to int 操作中。即使在编译时我会假设编译器实际上会对浮点数做两个 int 然后除法,而不是像你的代码那样直接做一个字符串浮点数。

自从我展示所有这些以来已经有几年了,也许编译器已经变得更好(怀疑)。希望硬件变得更好(很可能过去很难找到没有容易发现错误的 fpu)。

于 2012-04-28T14:44:34.903 回答
0

您的特定示例由编译器转换,因为它是十进制文字。你想要细节,所以让我们选择 gcc。它在real.c中进行转换(我不知道这是否是当前版本,但那是我通过 Google 找到的第一个副本),在一个名为 real_from_string() 的函数中。它本质上是用长除法进行转换:在你的情况下,45632/10000。

(十进制到浮点的转换非常复杂;如果您想了解更多信息,请查看我的博客。)

于 2012-04-28T17:50:08.107 回答