Python与浮点数

在周六参加 TDD Workshop 的时候, 遇到一个问题就是因为涉及到浮点数运算导致单元测试迟迟通不过- -. 回来就在这方面查了查

简单的例子

In [1]: 0.1 + 0.2 == 0.3Out[1]: FalseIn [4]: round(2.675, 2)Out[4]: 2.67

简单说这个就是因为浮点数的问题引起的, 也导致我们浮点数的单元测试没有通过.

关于浮点数

不管是什么数, 在计算机中最终都会被转化为 0 和 1 进行存储, 所以我们需要先弄明白以下几点问题

一个小数如何转化为二进制浮点数的二进制如何存储浮点数的二进制表示

首先我们要了解浮点数二进制表示, 有以下两个原则:

整数部分对 2 取余然后逆序排列小数部分乘 2 取整数部分, 然后顺序排列2.25 的二进制表示是?

整数部分的二进制表示为 10, 小数部分我们逐步来算0.25 * 2 = 0.5 整数部分取 00.5 * 2 = 1.0 整数部分取 1所以 2.25 的二进制表示为 10.01

0.1 的表示是什么?

我们继续按照浮点数的二进制表示来计算0.1 * 2 = 0.2 整数部分取 00.2 * 2 = 0.4 整数部分取 00.4 * 2 = 0.8 整数部分取 00.8 * 2 = 1.6 整数部分取 10.6 * 2 = 1.2 整数部分取 10.2 * 2 = 0.4 整数部分取 0…

所以你会发现, 0.1 的二进制表示是 0.00011001100110011001100110011……00110011作为二进制小数的循环节不断的进行循环.

这就引出了一个问题, 你永远不能存下 0.1 的二进制, 即使你把全世界的硬盘都放在一起, 也存不下 0.1 的二进制小数.

浮点数的二进制存储

Python 和 C 一样, 采用 IEEE 754 规范来存储浮点数. IEEE 754 对双精度浮点数的存储规范将 64 bit 分为 3 部分.

第 1 bit 位用来存储 符号, 决定这个数是正数还是负数然后使用 11 bit 来存储指数部分剩下的 52 bit 用来存储尾数

而且可以指出的是, double 能存储的数的个数是有限的, double 能代表的数必然不超过 2^64 个, 那么现实世界上有多少个小数呢? 无限个. 计算机能做的只能是一个接近这个小数的值, 是这个值在一定精度下与逻辑认为的值相等. 换句话说, 每个小数的存储(但是不是所有的), 都会伴有精度的丢失.

浮点数计算的问题

现在我们可以看一开始提到的例子

0.1 + 0.2 == 0.3

≈0.1 在 Python 中真正的数字是 0.10000000000000000555111512312578270211815834045410156250.2 在 Python 中真正的数字是 0.2000000000000000111022302462515654042363166809082031250.3 在 Python 中真正的数字是 0.299999999999999988897769753748434595763683319091796875

这就是为什么 0.1 + 0.2 != 0.3 的原因

round(2.675, 2)

In [4]: round(2.675, 2)Out[4]: 2.67

为什么 2.675 精确两位小数之后不是 2.68 呢, 因为 2.675 在计算机中真正的数字是 2.67499999999999982236431605997495353221893310546875

坑啊坑.

我是如何遇到了这个问题

简单地说是因为我理解错了 decimal 这个模块的用法.我一开始的使用方式是

In [14]: Decimal(2.675) * Decimal(1.2)Out[14]: Decimal('3.209999999999999668043315637')

因为没有仔细看库手册导致的错误使用. 正确的用法是:

In [15]: Decimal('2.675') * Decimal('1.2')Out[15]: Decimal('3.2100')

将字符串传入 Decimal, 而将数字直接传入, 它的效果是查看该数字在计算机中实际存储的数字.

decimal是如何实现的计算精准

我粗略的过了一下 decimal 这个库的源代码, 这个根据 General Decimal Arithmetic Specification 来设计, 简单地说就是将传入的字符串记录符号, 记录一个大数(整数和小数部分直接拼接而成), 记录小数点位置, 然后重写这个类的 operation进行实现.

参考

using-decimal-in-pythonPEP327 Decimal Data Type代码之谜(五)- 浮点数(谁偷了你的精度?)Double-precision floating-point formatFloating Point Arithmetic: Issues and LimitationsIEEE 754Decimal CodeGeneral Decimal ArithmeticSpecification

在周六参加 TDD Workshop 的时候, 遇到一个问题就是因为涉及到浮点数运算导致单元测试迟迟通不过- -. 回来就在这方面查了查

简单的例子

<tabl

Python与浮点数

相关文章:

你感兴趣的文章:

标签云: