为什么 0.1+0.2=0.30000000000000004
0.1 和 0.2 是如何表示的?
根据前面的讲解,十进制 0.1 转为二进制小数,得到的是 0.0001100… (重复1100)这样一个循环二进制小数,使用 IEEE754 表示如下图:
对这个 0.1 的二进制数据进行二进制编码,
再对这个编码进行切割,怎么切割呢,从左向右找,找到 第一个 1 就一刀砍下去,将这么长长的数切割成两端
0001100110011001100110011001100110011001100110011001101 |
---|
指数 | 尾数 |
---|---|
0001 | 100110011001100110011001100110011001100110011001101 |
可以发现前面有四位,是 0001 ,那么这个浮点数的指数就是 -4 ,为什么是负数呢,应为这是小数,是向右数数,
上面第二张图可以看出,浮点数的指数位有8个比特 ,一共可以表达 2 8 = 256 2^8=256 28=256数,float 类型既要表达绝对值很大大数或者很小的数, 0 和 255 对应两种特殊情况,编码1-254则依次对应 指数范围 -126-127
十进制指数 | 浮点数八位指数十进制编码 | 浮点数八位指数二进制编码 |
---|---|---|
-126 | 1 | 0000 0001 |
-125 | 2 | 0000 0010 |
… | … | … |
-4 | 123 | 0111 1011 |
1 | 128 | 1000 0000 |
127 | 254 | 1111 1110 |
也就是说,图2 里面的二进制编码 为 127+(-4)=123 =0111 1011
指数 | 23位尾数 | 舍入项 | 截断项 |
---|---|---|---|
0001 | 10011001100110011001100 | 1100 | 110011001100110011001101 |
第一个 1 分割 | 尾数最后一项是0,但是舍入项是0.75,需要进1 | 1 ∗ ( 0.5 ) − 1 + 1 ∗ ( 0.5 ) − 2 + 0 ∗ ( 0.5 ) − 3 + 0 ∗ ( 0.5 ) − 4 = 0.75 1*(0.5)^{-1}+1*(0.5)^{-2}+0*(0.5)^{-3}+0*(0.5)^{-4}=0.75 1∗(0.5)−1+1∗(0.5)−2+0∗(0.5)−3+0∗(0.5)−4=0.75 | |
10011001100110011001101 |
在浮点数的舍入问题上,IEEE 浮点格式定义了 4 种不同的舍入方式,如下表所示。其中,默认的舍入方法是向偶数舍入,而其他三种可用于计算上界和下界。
其次我们来考虑尾数 ,位数占用23 个比特,如果你直接在尾数 截取23 个比特,那么 用浮点数将永远小于我们需要表达的数,你把一条龙的一个小尾巴截了,龙的体重一定是变轻了。所以一定要加入舍入规则,这里不是简单的四舍五入 而是 四舍六入五成双,舍入细节请看 四舍六入五成双
同样的方法,0.2 用单精度浮点数表示是:0.20000000298023223876953125。所以,0.1 + 0.2 的结果是:0.300000004470348358154296875
用 python 代码 来实现
#将十进制小数转化为二进制
def dec2bin(x):
x -= int(x)
bins = []
while x:
x *= 2
bins.append(1 if x>=1. else 0)
x -= int(x)
return bins
#print()
#cc=dec2bin(0.1)
0.1 的二进制表示
cc1=dec2bin(0.1)
#cc1=0001100110011001100110011001100110011001100110011001101
0.2 的二进制表示
cc2=dec2bin(0.2)
#cc1=001100110011001100110011001100110011001100110011001101
全部代码
# -*- coding: utf-8 -*-
"""
Created on Thu Jun 9 17:20:29 2022
@author: luogantt
"""
import numpy as np
#将十进制小数转化为二进制
def dec2bin(x):
x -= int(x)
bins = []
while x:
x *= 2
bins.append(1 if x>=1. else 0)
x -= int(x)
return bins
print(dec2bin(.8125))
def expo(cc):
n=0
while (not cc[n]):
n=n+1
print(n)
return n+1
"""
>0.5 --1
<0.5 --0
==0.5 if up==0 --0
==0.5 if up==1 --1
"""
#决定舍入项是否要进一位
def roundings(cc,exp):
drop= np.array(cc[exp+23:exp+23+4])
weight=np.array([1/2,1/4,1/8,1/16])
ifdrop=np.sum(weight*drop)
if ifdrop>0.5:
return 1
elif ifdrop<0.5:
return 0
else:
if cc[exp+23]==0:
return 0
else:
return 1
# 将小数位转换成 float32
def float32(exp,cc):
fric=cc[exp:exp+23]
strfric=''.join([str(k) for k in fric])
intfric= int(strfric,2)
round1= roundings(cc,exp)
if round1:
intfric=intfric+1
intfric1=bin(intfric)
intfric2=intfric1[2:]
intfric3=[int(k) for k in intfric2]
sum=0
for k in range(len(intfric3)):
sum=sum+intfric3[k]*2**(-1*(k+exp+1))
sum=sum+(2)**(-(exp))
print(sum)
return sum
cc1=dec2bin(0.1)
exp1=expo(cc1)
sum1= float32(exp1,cc1)
print( '小数位',sum1)
cc2=dec2bin(0.2)
exp2=expo(cc2)
sum2= float32(exp2,cc2)
print(sum2)
#
print('0.1+0.2=',sum1+sum2)
[1, 1, 0, 1]
1
2
3
0.10000000149011612
小数位 0.10000000149011612
1
2
0.20000000298023224
0.20000000298023224
0.1+0.2= 0.30000000447034836