计算机如何进行浮点运算?

时间:2011-05-17 15:24:40

标签: c++ math floating-point ieee-754

我看过很长篇文章,解释了如何存储浮点数以及这些数字的算法是如何完成的,但请简要说明我写作时的原因

cout << 1.0 / 3.0 <<endl;

我看到 0.333333 ,但是当我写

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

我看到 1

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

5 个答案:

答案 0 :(得分:28)

答案 1 :(得分:17)

我们来算一算。为简洁起见,我们假设您只有四个重要(基数为2)的数字。

当然,由于gcd(2,3)=11/3在base-2中表示时是周期性的。特别是,它无法准确表示,因此我们需要满足于近似值

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

更接近1/3的实际值而不是

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

因此,以小数形式打印A会给出 0.34375 (您在示例中看到 0.33333 的事实只是证明了一个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:)

答案 2 :(得分:17)

问题是浮点格式表示基数2中的分数。

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

的问题在于,并非每个有理数(一个可以表示为两个整数的比率的数字)实际上都具有这种基本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

最左边的位是符号,接下来的8位是指数,其余位是分数。在指数和分数之间是一个假定的“1”,它始终存在,因此实际上不存储,作为归一化的最左边分数位。我写的零实际上并不像o那样作为单个位存在。

这里发生了很多事情,每一步,FPU采取了相当英勇的措施来围绕结果。已经保留了两个额外的精度数字(超出了结果中适合的数字),并且FPU在许多情况下知道剩下的最右边位中的任何一个或至少一个是否为1。如果是这样,则该部分的那部分超过0.5(缩放),因此它向上舍入。中间舍入值允许FPU将最右边的位一直传送到整数部分,最后舍入到正确的答案。

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

答案 3 :(得分:2)

至于这个具体的例子:我认为编译器现在太聪明了,如果可能的话,自动确保原始类型的const结果是准确的。我没有设法愚弄g ++做一个像这样错误的简单计算。

但是,通过使用非const变量很容易绕过这些事情。尽管如此,

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 ???

答案 4 :(得分:0)

这是有效的,因为默认精度为6位数,舍入为6位数,结果为1.请参阅C++ draft standard (n3092)中的27.5.4.1 basic_ios构造函数。