我最近正在使用浮点数,但我意识到了一些我不期望的关于浮点数的东西。这是一个例子
a = 0.1
print(f"{a:0.20f}")
#'0.10000000000000000555'
b = a * 10
print(f"{b:0.20f}")
#'1.00000000000000000000'
我希望最后的打印输出1.00000000000000005551
(即1后跟0.1
的数字1到21)。
我很好奇的是为什么乘以10时浮点误差会消失。正常的算术规则表明,浮点误差会传播,但实际上并没有发生。为什么会这样?有办法避免吗?
[10和0.1000000000000000055511151231257827021181583404541015625,IEEE 754 64位二进制表示形式为1.000000000000000055511151231257827021181583404541015625。
这不能完全代表。它由1.0和1.0000000000000002括起来。0000000000000002220446049250313080847263336181640625
接近1.0,所以这是乘法的舍入到最接近的结果。
我使用一个简短的Java程序计算了数字:
import java.math.BigDecimal;
public strictfp class Test {
public static void main(String[] args) {
BigDecimal rawTenth = new BigDecimal(0.1);
BigDecimal realProduct = rawTenth.multiply(BigDecimal.TEN);
System.out.println(realProduct);
System.out.println(new BigDecimal(Math.nextUp(1.0)));
}
}
输出:
1.0000000000000000555111512312578270211815834045410156250
1.0000000000000002220446049250313080847263336181640625
此答案说明了如何仅需一点点算术就可以确定将1/10转换为浮点并乘以10将产生正1的结果;无需计算大数或精确数字。
您的Python实现使用通用的IEEE-754 binary64格式。 (Python并不严格限制应使用哪种浮点格式实现。)在这种格式下,数字实际上表示为一些53位整数乘以2的幂。因为2 -4≤1/ 10 <2 -3,所以最接近的1/10的可表示数是某个整数M乘以2 -3-5]]。 (-53将53位整数缩放为½和1之间,而-3将其整数缩放为2 -4和2 -3。)让我们将可表示的数字称为x。
那么我们有x = M•2 − 56
= 1/10 + e,其中e是当我们将1/10舍入到最接近的可表示值时发生的舍入误差。由于我们舍入到最接近的可表示值,因此| e |。 ≤½•2 − 56 = 2 − 57。要确切地找到e是什么,请将1/10乘以2 56
。 WolframAlpha告诉我们这是7205759403792793 + 3/5。为了获得最接近的可表示值,我们应该四舍五入,所以M = 7205759403792794和e = 2/5•2 − 56。尽管我使用WolframAlpha进行了说明,但是我们不需要M,并且可以通过以两个模10的幂观察模式来找到e:2 1]→2,2 2→4,2 < [3→8、2 4→6、2 5→2、2 6→4,因此模式以4的循环重复,而56模4为0,所以2 [56]模10的余数与2 [4] 6的余数相同,因此分数为6/10 = 3/5。我们知道应该四舍五入到最接近的整数1,因此e = 2/5•2 − 56。So x = M•2 − 56 = 1/10 + 2/5•2 − 56。现在我们可以算出用浮点算法计算10•x的结果。结果就像我们先用实数算法计算10•x,然后四舍五入到最接近的可表示值。在实数运算中,10•x = 10•(1/10 + 2/5•2 − 56
)= 1 + 10•2/5•2 − 56 = 1 + 4• 2-56 = 1 + 2 -54
。两个相邻的可代表值是1和1 + 2 -52,并且1 + 2 -54比1 + 2 -52更接近1。所以结果是1。