计算机如何进行浮点运算
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。
计算机是如何做到这一点的?请解释一下这个简单的例子。这对我来说已经足够了。
第一个分数位是½,第二个是¼,它继续为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构造函数。
- 使用双精度的浮点运算
- 将异常字符串解析为向量上的浮点运算?
- -O1改变浮点运算
- 将浮点运算与 Z3 C++ API 结合使用
- C++模运算符,使用 #define 和自己的类进行浮点运算
- 浮点运算
- 使用浮点值与0.0进行比较
- 浮点运算的精度
- 为什么GDB对浮点运算的评估与C++不同
- C中的浮点运算出错
- 在Visual C++中,浮点运算优化会导致罕见而奇怪的行为
- 为什么十进制浮点运算的提议没有被 C++0x 接受?
- 为什么这个浮点运算被编译得如此奇怪(没有经过优化)
- 浮点运算结果的重现性
- 在浮点运算上执行平方运算和平方根运算时:表达式必须具有整型或非整型枚举类型
- C++:浮点运算的稳定性策略
- 自动获取某些代码段的浮点运算计数的方法
- 用于浮点运算的快速、基于秩的基数排序
- 浮点运算中的整数转换
- 计算机如何进行浮点运算