繁体   English   中英

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

[英]How computer does floating point arithmetic?

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

cout << 1.0 / 3.0 <<endl;

我看到0.333333 ,但是当我写的时候

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

我看到了1

电脑如何做到这一点? 请只解释这个简单的例子。 对我来说足够了。

让我们做数学。 为简便起见,我们假设您只有四个有效数字(以2为基数)。

当然,由于gcd(2,3)=1 ,所以当以base-2表示时, 1/3是周期性的。 特别是,它不能精确表示,因此我们需要对近似值感到满意

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为底的分数。

第一个小数位是1/2,第二个小数位是1/4,然后继续为1/2 n

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

(这使浮点格式难以用于货币值。尽管这些值始终是有理数( n / 100),但实际上.00,.25,.50和.75只能以a的任意位数精确表示。以两个为基数。)

无论如何,当您将它们添加回去时,系统最终将有机会将结果四舍五入为可以精确表示的数字。

在某个时候,它发现自己将.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为底的分数来表示。 实际上,很少。

对于这个特定的示例:我认为当今的编译器太聪明了,并且如果可能的话,会自动确保原始类型的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构造函数。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM