关于浮点数的一些常见要点
本来这里应该有一篇关于浮点数的介绍的文章,不过个人认为书里面写得还是比较完善了,就暂时想欠着了,而这些关键点书里面一般介绍的比较少,我个人认为应该单独写文章出来
浮点数的不精确性
正如绝大多数的文章都会告诉你的一样,浮点数都是不精确的,所有浮点数都无法精确地表达实数.举一个例子,比如.0.01 在单精度浮点型中可以用说是0.01000000000298023226097399174250313080847263336181640625.进行表示.因此对于绝大多数的语言而言,浮点数的使用相等==使用严重的问题.通常这一类的文档都会给出建议使用相减后的绝对值在某一个区间进行表示某两个浮点数相等的判断.
<?php
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;
if(abs($a-$b) < $epsilon) {
echo "true";
}
?>
远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等
浮点数的四则运算
设两个浮点数 X=Mx2Ex ,Y=My2Ey
实现X±Y要用如下5步完成:
- 对阶操作:小阶向大阶看齐
- 进行尾数加减运算
- 规格化处理:尾数进行运算的结果必须变成规格化的浮点数,对于双符号位(就是使用00表示正数,11表示负数,01表示上溢出,10表示下溢出)的补码尾数来说,就必须是
001×××…×× 或110×××…××的形式
若不符合上述形式要进行左规或右规处理. - 舍入操作:在执行对阶或右规操作时常用“0”舍“1”入法将右移出去的尾数数值进行舍入,以确保精度.
- 判结果的正确性:即检查阶码是否溢出
若阶码下溢(移码表示是00…0),要置结果为机器0;
若阶码上溢(超过了阶码表示的最大值)置溢出标志.
现在用一个具体的例子来说明上面的5个步骤
例题:假定X=0 .0110011211,Y=0.11011012-10(此处的数均为二进制), 计算X+Y;
首先,我们要把这两个数变成2进制表示,对于浮点数来说,阶码通常用移码表示,而尾数通常用补码表示.
要注意的是-10的移码是00110
[X]浮: 0 1 010 1100110
[Y]浮: 0 0 110 1101101
符号位 阶码 尾数
-
求阶差:│ΔE│=|1010-0110|=0100
-
对阶:Y的阶码小,Y的尾数右移4位
[Y]浮变为 0 1 010 0000110 1101暂时保存 -
尾数相加,采用双符号位的补码运算
00 1100110
+00 0000110
00 1101100 -
规格化:满足规格化要求
-
舍入处理,采用0舍1入法处理
故最终运算结果的浮点数格式为: 0 1 010 1101101
即X+Y=+0. 1101101*210
浮点数的乘除法
- 阶码运算:阶码求和(乘法)或阶码求差(除法)
即 [Ex+Ey]移= [Ex]移+ [Ey]补
[Ex-Ey]移= [Ex]移+ [-Ey]补 - 浮点数的尾数处理:浮点数中尾数乘除法运算结果要进行舍入处理
例题:X=0 .0110011211,Y=0.11011012-10 求X*Y
解:[X]浮: 0 1 010 1100110
[Y]浮: 0 0 110 1101101
-
阶码相加
[Ex+Ey]移=[Ex]移+[Ey]补=1 010+1 110=1 000
1 000为移码表示的0 -
原码尾数相乘的结果为:
0 10101101101110 -
规格化处理:已满足规格化要求,不需左规,尾数不变,阶码不变.
-
舍入处理:按舍入规则,加1进行修正
所以 X※Y= 0.1010111*20
浮点数运算的思考
- 浮点数的精度丢失在每一个表达式,而不仅仅是表达式的求值结果.floor((0.1 + 0.7) * 10) 通常会返回 7 而不是预期中的 8,因为该结果内部的表示其实是类似 7.9999999999999991118…因此使用浮点数计算会比较危险,写代码的时候请谨慎.
- 在数学上相等的两个计算序列可以产生不同的浮点值
- 检查除数不为零并不能保证除法不会溢出.
- 尽管如前所述,IEEE 754的各个算术运算保证精确到ULP的一半以内,但更复杂的公式可能由于四舍五入而遭受更大的误差。如果问题或其数据是病态的,那么精度的损失可能很大,这意味着正确的结果对其数据中的微小扰动过敏。然而,如果使用对该数据在数值上不稳定的算法,即使是良好条件的函数也会遭受很大的精度损失:编程语言中表达式的明显等同表达式的数值稳定性可能显着不同。消除这种精度损失风险的一种方法是设计和分析数值稳定的算法,这是数学分支的一个目标,称为数值分析.大多数语言都会提供某些任意精度库来给码农来使用
- 转换为整数并不直观:转换(63.0 / 9.0)到整数产生7,但转换(0.63 / 0.09)可能会产生6.这是因为转换通常是截断而不是舍入. 地板和天花板功能可以产生从直观预期值中偏离一个的答案.
int float double的转化
- 从int转换成float,数字不会溢出,但是可能被舍入.
- 从int或float转换成double,因为double有更大的范围(也就是可表示值的范围),也有更高的精度(也就是有效位数),所以能够保留精确的数值.
- 从double转换成float,因为范围要小一些,所以值可能溢出成正无穷或负无穷.另外,由于精确度较小,它还可能被舍人.
- 从float或 者double转换成int,值将会向零舍入.例如,1.999将被转换成1,而-1.999将被转换成-1.进一步来说,值可能会溢出.C语言标准没有对这种情况指定固定的结果.与Intel兼容的微处理器指定位模式[10*"00] (字长为w时的为整数不确定( integer indefinite) 值. 一 个从浮点数到整数的转换,如果不能为该浮点数找到一个合理的整数近似值,就会产生这样一个值.因此,表达式(int>+lelO会得到-21483648,即从一个正值变成了一个负值.