计算机如何进行浮点运算

How computer does floating point arithmetic?

本文关键字:浮点运算 何进行 计算机      更新时间:2023-10-16

我看过很长的文章,解释了如何存储浮点数以及如何对这些数字进行算术运算,但请简要解释我在编写时的原因

cout << 1.0 / 3.0 <<endl;

我看到0.333333,但当我写时

cout << 1.0 / 3.0 + 1.0 / 3.0 + 1.0 / 3.0 << endl;

我看到1

计算机是如何做到这一点的?请解释一下这个简单的例子。这对我来说已经足够了。

第一个分数位是½,第二个是¼,它继续为1/2n

的问题是,并不是每个有理数(一个可以表示为两个整数之比的数(实际上都有这种基2格式的有限表示。

(这使得浮点格式很难用于货币值。尽管这些值总是有理数(n/100(,但只有.00、.25、.50和.75在基数为2的分数的任何位数中都有精确的表示。)

不管怎样,当你把它们加回来时,系统最终有机会把结果四舍五入到一个它可以准确表示的数字。

在某个时刻,它发现自己将.666…数字添加到.333…数字上,就像这样:

  00111110 1  .o10101010 10101010 10101011
+ 00111111 0  .10101010 10101010 10101011o
------------------------------------------
  00111111 1 (1).0000000 00000000 0000000x  # the x isn't in the final result

最左边的位是符号,后面的八位是指数,剩下的位是分数。在指数和分数之间是一个假定的"1",它总是存在,因此实际上没有存储,作为归一化的最左边的分数位。我已经写了零,它们实际上并不像o那样作为单独的位存在。

这里发生了很多事情,在每一步中,FPU都采取了相当英勇的措施来扭转结果。保留了两个额外的精度数字(超出了结果的范围(,在许多情况下,FPU知道剩余的最右边的比特中是否有一个,或者至少有一个是一个。如果是,则该部分分数大于0.5(按比例缩放(,因此它向上取整。中间取整值允许FPU将最右边的位一直带到整数部分,最后取整到正确答案。

这并没有发生,因为有人加了0.5;FPU只是在格式的限制范围内尽了最大努力。实际上,浮点并不是不准确的。这是完全准确的,但我们希望在以10为基数的有理数世界观中看到的大多数数字都不能用以2为基数的分数表示。事实上,很少有人这样做。

让我们计算一下。为了简洁起见,我们假设您只有四个有效数字(以2为基数(。

当然,由于gcd(2,3)=1,所以1/3在以基-2表示时是周期性的。特别是,它不能精确地表示,所以我们需要满足于近似

A := 1×1/4 + 0×1/8 + 1×1/16 + 1*1/32

比更接近CCD_ 4的实际值

A' := 1×1/4 + 0×1/8 + 1×1/16 + 0×1/32

因此,以十进制打印A会得到0.34375(您在示例中看到0.333333这一事实正好证明double中的有效位数较多(。

当这些加起来三次时,我们得到

A + A + A
= ( A + A ) + A
= ( (1/4 + 1/16 + 1/32) + (1/4 + 1/16 + 1/32) ) + (1/4 + 1/16 + 1/32)
= (   1/4 + 1/4 + 1/16 + 1/16 + 1/32 + 1/32   ) + (1/4 + 1/16 + 1/32)
= (      1/2    +     1/8         + 1/16      ) + (1/4 + 1/16 + 1/32)
=        1/2 + 1/4 +  1/8 + 1/16  + 1/16 + O(1/32)

O(1/32)项不能在结果中表示,因此它被丢弃,我们得到

A + A + A = 1/2 + 1/4 + 1/8 + 1/16 + 1/16 = 1

QED:(

对于这个特定的例子:我认为现在的编译器太聪明了,如果可能的话,会自动确保基元类型的const结果是准确的。我没能骗过g++做这样一个简单的错误计算。

然而,通过使用非常量变量可以很容易地绕过这些事情。尽管如此,

int d = 3;
float a = 1./d;
std::cout << d*a;

将精确地产生1,尽管这不应该是真正的预期。正如前面所说,原因是operator<<将错误四舍五入。

至于它为什么能做到这一点:当你将大小相似的数字相加或将float乘以int时,你几乎得到了浮点型所能提供的所有精度——也就是说,误差/结果的比例非常小(换句话说,假设你有正误差,误差发生在小数点后几位(。

因此,3*(1./3),即使作为一个浮点值,并不完全是==1,也有很大的校正偏差,这阻止了operator<<处理小误差。然而,如果你只减去1就消除了这种偏差,浮点将直接滑到错误处,突然之间,它就不再是可忽略的了。正如我所说,如果您只是键入3*(1./3)-1,这不会发生,因为编译器太聪明了,但请尝试

int d = 3;
float a = 1./d;
std::cout << d*a << " - 1 = " <<  d*a - 1 << " ???n";

我得到的(g++,32位Linux(是

1 - 1 = 2.98023e-08 ???

这是有效的,因为默认精度为6位数,四舍五入到6位数,结果为1。参见C++标准草案(n3092(中27.5.4.1 basic_ios构造函数。