二进制数表示

时间:2012-04-28 14:27:41

标签: c binary floating-point computer-architecture

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

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

我们以C为例。我写了

double d = 4.5632;

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

仅在您知道如何发生这种情况时才回答,“不要担心”这样的答案没有帮助。此外,“它取决于平台”也没有帮助,你可以选择一个平台并为此解释。

5 个答案:

答案 0 :(得分:6)

编译器不会(通常)决定。 CPU(通常)具有浮点单元,该浮点单元要求浮点值以特定格式表示(通常为IEEE-754)。当然,可以模拟完全不同的体系结构,在这种情况下,编译器/模拟器作者可以自由选择完全不同的表示。但这不典型。

关于如何将特定词汇表示4.5632转换为基础表示,这是由C标准指定的。因此,从C99标准的第6.4.4.2节(我已经强调了最相关的部分):

  

有效位数部分被解释为(十进制或十六进制)   有理数;指数部分中的数字序列是   解释为十进制整数。对于十进制浮点常数,   exponent表示有效位数部分的幂10   缩放。对于十六进制浮点常量,指数表示   2的幂,有效位部分将被缩放。对于   十进制浮点常量,也用于十六进制浮点数   当FLT_RADIX不是2的幂时,常量结果是   最接近的可表示值,或者更大或更小的可表示值   值紧邻最近的可表示值,选择   以实现定义的方式。对于十六进制浮动   常量当FLT_RADIX是2的幂时,结果是正确的   圆形。

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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

实际上,几乎所有平台都根据IEEE 754(即IEC 559)实现浮点运算。这个相当古老的国际标准定义了浮点数的位是什么意思,以及程序十进制表示应该如何舍入到一个浮点值。

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

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

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

答案 3 :(得分:0)

对于您的具体示例,是二进制表示在编译时进行编码。它可能会调用一个C库(atod,sscanf,etC),无论该库执行截断还是舍入,都会发生这种情况。而编译器“功能”或“规则”的功能并不一定与执行相同操作时发生的运行时规则相同。你永远不应该检查浮点的等价性,但是如果你要获取编译时的值,然后为程序提供一个字符串并转换该运行时(比如你在命令行上传递值4.5632并使用其中一个库调用)你不一定会获得相同的浮点值。我已经看到编译器(gcc等)使用编译时常量做了一个非常糟糕的工作,所以作为规则,对于像你这样的数字(在尾数中不多),我对准确性的偏好是这样做的:

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

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

你确实在int转换为双转换时存在硬件+操作系统错误的风险,Hauser对FPU错误倾向于在int中浮动和浮动到int操作做了一些评论。即使在编译时,我也会假设编译器实际上会对浮点数执行两次int,然后执行除法,而不是像代码那样直接浮动一个字符串。

自从我展示了所有这些之后已经有几年了,也许编译器变得更好(可疑)。希望硬件变得更好(可能,在过去很容易找到错误的情况下找到fpu非常罕见)。

答案 4 :(得分:0)

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

(十分到浮点转换非常复杂;如果您想了解更多内容,请查看my blog。)