python float 精度_为什么说浮点数缺乏精确性? python中浮点数运算问题

浮点数运算揭秘

解释下 0.1+0.1-0.2=0 具体是怎么蒙对的,0.1+0.1+0.1-0.3 怎么就蒙不对了。

首先在 CPython 中浮点数总是64位的,这里为了书写方便,假设为32位的IEEE 754浮点数,计算的道理都一样,于是第一位是符号位,后面8位是阶码,最后23位是尾数。

0.1换算成二进制:

0.000110011001100110011001100110011001100110011001100110011001...

因为尾数只能有23位所以需要修约,IEEE 754定义的修约的规则可以有四种:取最靠近的(对于中间数0.5,IEEE 754默认修约成偶数,叫做 round half to even,譬如23.5和24.5都取为24),向上取整,向下取整,向靠近0取整。默认的取整规则是取就近的,这个很重要,直接决定了什么数能蒙对,什么数蒙不对。

0.1再换成以2为基数的幂数,尾数只取23位的话:

equation?tex=1.10011001100110011001101+%5Ctimes+2%5E%7B-4%7D

注意最后1100被round成了1101,因为第24位是一个1(于是进位,第23位的0变成1)。因为阶码有-127的移位(单精度),即使真正的2的指数加上127才是存储阶码,所以阶码应该为

01111111(127)-00000100(4) = 01111011

再加上尾数和符号位,整个32位(最左端为符号位)就为:

00111101110011001100110011001101,即3d cc cc cd

Intel的CPU都是little endian的,我们假设这里的CPU是Intel,所以在进程的虚拟内存中0.1被表示为cd cc cc 3d。

再看0.2,0.2换成位二进制:

0.001100110011001100110011001100110011001100110011001100110011...

因为0.2=0.1*2,所以就是把0.1向左移一位的结果,他和0.1不出意料地具有相同的尾数,只是阶码多了一位:

equation?tex=1.10011001100110011001101+%5Ctimes+2%5E%7B-3%7D

同理,这里阶码相当于0.1的阶码加一,于是就是01111011 + 00000001 = 01111100,同理,整个32位(最左端为符号位)就为:

00111110010011001100110011001101,在little endian的机器内存中就是cd cc 4c 3e。

那0.1+0.1时又发生了什么呢?两个浮点数相加时,遵循以下步骤:0 操作数检查

比较阶码大小并完成对阶

尾数求和运算

结果规格化(舍入处理)

首先两个数都不为0(回忆下0是怎么在浮点数中被表示的),阶码也相等,所以直接对尾数求和。两个尾数又相等,求和的结果相当于左移1位,结果是11.00110011001100110011010,溢出了,所以再右移1位以规格化(相当于小数点左移一位),阶码加1,右移的时候末尾的0被舍去,最后尾数变为1.10011001100110011001101,阶码是-4+1=-3,和0.2长得一模一样。

那如果0.2再加上一个0.1呢?这时候为了得出Python解释器给出的那个结果,我们需要真正使用双精度浮点数了(11位的阶码和52的尾数)。0.1的尾数和阶码分别为1.1001100110011001100110011001100110011001100110011010和-4,请注意尾数原本以110011001结尾,只不过最后这个1后面又是一个1,并且后面大于0.5,于是110011001加一变为110011010,这就是导致0.2+0.1和0.3不一样的根本原因。

0.2则具有相同的尾数和-3的阶码,做0.1和0.2的加法时,首先把0.1的尾数变为0.11001100110011001100110011001100110011001100110011010以对阶,然后把这个数再和1.1001100110011001100110011001100110011001100110011010相加,结果是

10.01100110011001100110011001100110011001100110011001110,这个数的尾数是53位的,而且又有溢出,所以应该右移一位,然后舍去最后两位,注意最后两位刚好是中间数,但是根据IEEE 754的默认rounding scheme,应该向前进位变成偶数,所以结果就是

1.0011001100110011001100110011001100110011001100110100(10被舍去),同时阶码加一变为-2。

再看0.3是怎么表示的,它的阶码和尾数分别为1.0011001100110011001100110011001100110011001100110011和-2。注意尾数结尾0011后面接着的本应是剩下0011的循环,小于0.5,所以无法进位,所以导致了0.3和0.2+0.1不一样,我们把0.3和0.2+0.1摆在一起,看看区别有多大:

0.2+0.1:

1.0011001100110011001100110011001100110011001100110100

0.3:

1.0011001100110011001100110011001100110011001100110011

也就是说只要0.3在最低位加一的话就和0.2+0.1一样了,而尾数的最低位是第52位,再乘上-2的阶码,就是2的负54次方,这个数刚好就是:5.551115123125783e-17。

### 三、Python 中单精度浮点数与双精度浮点数的区别 在 Python 中,浮点数默认使用的是双精度(`float` 类型),它基于 IEEE 754 标准实现,具有更高的精度和更大的数值范围。而单精度浮点数通常在 Python 中并不直接支持,但在某些数值计算库(如 NumPy)中提供了专门的数据类型(如 `numpy.float32`)来处理。 #### 三、一、数据精度与表示方式 单精度浮点数占用 **4 字节**(32 位),其中符号位 1 位、阶码 8 位、尾数 23 位,能够表示大约 7 位有效数字。而双精度浮点数占用 **8 字节**(64 位),包含 1 位符号、11 位阶码和 52 位尾数,能够表示大约 15 位有效数字。因此,双精度浮点数在表示小数时更加精确,适用于科学计算和金融建模等对精度要求较高的场景[^1]。 #### 三、二、内存占用与性能影响 由于单精度浮点数占用的内存更少,因此在处理大规模数据集时可以节省内存空间并提升计算效率。然而,Python 原生的 `float` 类型始终是双精度形式,若需使用单精度浮点数,必须借助如 NumPy 等第三方库来实现。例如: ```python import numpy as np # 单精度浮点数示例 a = np.float32(1.0) print(a.itemsize) # 输出: 4 # 双精度浮点数示例 b = np.float64(1.0) print(b.itemsize) # 输出: 8 ``` #### 三、三、转换与精度丢失问题浮点数之间进行转换时,例如将双精度转换为单精度,可能会导致精度丢失。这种精度丢失是由于单精度浮点数无法完整表示双精度浮点数的所有信息,特别是在处理小数部分时可能出现舍入误差。IEEE 754 标准规定了浮点数的转换方式,但在实际操作中仍需谨慎处理,尤其是在比较浮点数是否相等时,应避免使用 `==` 操作符,而是采用误差容忍的方式进行判断[^3]。 ```python import numpy as np # 双精度转单精度 value = np.float64(0.1) single_value = np.float32(value) print(f"Original: {value}, Single: {single_value}") # 输出: Original: 0.1, Single: 0.10000000149011612 ``` #### 三、四、Python 中的底层实现与扩展支持 Python 原生的浮点数类型基于 C 语言的 `double` 实现,即双精度浮点数Python 的灵活性是以牺牲一定的性能为代价的,因为每个浮点数对象都包含类型信息、引用计数等元数据。相比之下,NumPy 提供了更高效的固定类型数组,支持单精度(`float32`)和双精度(`float64`)等多种数据类型,适用于大规模数值计算任务[^4]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值