首页 > 其他分享 >浮点数运算的一些问题

浮点数运算的一些问题

时间:2022-12-02 17:11:19浏览次数:36  
标签:阶码 运算 二进制 浮点数 整数 print 一些 小数

引入

先来看个代码:

print(1-0.7 == 0.3)

很多人会觉得这一看不就是True吗,但实际上结果为False。因为1-0.7的结果为0.30000000000000004

浮点数转二进制的方法

可以用这个网站验证答案:https://c.runoob.com/front-end/58/

因为所有的数据本质都是通过二进制数存储的,所以要分析这个问题的本质,我们得先去看浮点数的二进制表示。因为整数和小数的转换方法不同,所以先将十进制数的整数部分和小数部分分别转换后,再加以合并。

(1)整数部分:整数部分比较简单,因为一个有限的十进制整数都能转换成有限的二进制数。采用除2取余,逆序连接的方法,代码如下:

n = 173
ls = []
while n != 0:
    ls.append(str(n%2))
    n//=2
ls.reverse()
print(''.join(ls))

(2)小数部分:小数部分会麻烦一点了,具体的方法为:

'''
如:0.625=(0.101)bin
0.625*2=1.25======取出整数部分1
0.25*2=0.5========取出整数部分0
0.5*2=1==========取出整数部分1
直到积中的小数部分为零为止,从上倒下连接
'''
import math

n = 0.625
s = ''
while int(n)!= n:
    n*=2
    s+=str(int(n))
    n = math.modf(n)[0]
print(s)

在这里可以发现,十进制的小数转二进制的时候,很容易就会无限循环下去,例如0.2转换成二进制就i是0011001100110011001100……,由于计算机存储的位是有限的,所以必然会造成精度的损失。

IEEE 754 

这里就不得不提到IEEE 754 了,这东西其实就是一个浮点数的运算标准。因为对于有符号整数来说,存储的方式其实很简单,一位表示符号,剩下的位都可以用来表示数值,但浮点数的情况比较复杂。对于float32,也就是32位的浮点数,是这样表示的:V = (-1)^S*M*2^E

 (1)(-1)^s 表示符号位,当 s=0,V 为正数;当 s=1,V 为负数

 (2)M称为尾数,1≤ M <2,也就是说,M 可以写成 1.xxxxxx 的形式,其中 xxxxxx 表示小数部分。

 (3)E表示阶码。例如浮点数5.0,用二进制表示是101.0,用科学计数法表示为1.01*2^2(注意,因为这里是二进制数,所以是乘2的2次方而不是10的二次方)。此时阶码E就是2,尾数M为1.01

对于 32 位的浮点数,最高的 1 位是符号位 s,接着的 8 位是指数 E,剩下的 23 位为有效数字 M。

尾数

因为M 总是写成 1.xxxxxx *2^E的形式,所以在计算机内部保存 M 时,默认这个数的第一位总是 1,因此可以被舍去,只保存后面的 xxxxxx 部分。比如保存 1.01 的时候,只保存小数部分的 01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省 1 位有效数字。以32位浮点数为例,留给 M 只有 23 位,将第一位的1舍去以后,等于可以保存 24 位有效数字。

阶码

上面说到了,阶码是一个八位的二进制数,并且它是一个无符号数,所以取值范围是[0,255]。但是科学计数法中的指数并不一定都是正数,例如十进制0.5转换为二进制是0.1,因为1<=M<2,所以要写成1*2^-1,所以阶码部分,采用移码来表示。简单地讲,就是阶码的真实值为计算机存储值减去中间数127,比如,2^10 的 E 是 10,所以保存成 32 位浮点数时,必须保存成 10+127=137,即 10001001,这样阶码的范围就变成了[-127,128]。阶码这里又有一些特殊情况:

(1)阶码E全为0,有效数字 M 不再加上第一位的 1,而是还原为 0.xxxxxx 的小数。这样做是为了表示机器零(计算机中小到机器数的精度达不到的数均视为“机器零”),以及接近于 0 的很小的数字。

(2)阶码E全为1,阶码全为1的情况有两种,用来表示特殊值,第一种是尾数M也全为1,这时表示正负无穷大(正还是负取决于符号位)。若M不全为1,则表示NaN(Not a Number,非数,这个概念也是IEEE 754定义的),例如0除以0的结果。

 遗留的问题

现在我们就可以解释为什么1-0.7的结果是0.30000000000000004了,由于计算机存储浮点数的位数有限,所以浮点数转成二进制表示的时候,必然会造成精度的损失,例如0.3转换为二进制表示为:

0.0100110011001100110011001100110011001100110011001101(舍入后的的结果),而把这一串再转换为十进制就是0.30000000000000004

 

但这里还有一个问题,既然计算机都是二进制存储的,那么1-0.7和0.3应该同时存在舍入,那么它们俩应该还是相等的,但事实并非如此。而且,使用加减法很容易出问题,但乘除法不会。

print(1-0.9)#0.09999999999999998
print(0.2/2)#0.1

至于是为什么,暂时还搞不清楚,以后来填坑

标签:阶码,运算,二进制,浮点数,整数,print,一些,小数
From: https://www.cnblogs.com/haruyuki/p/16930362.html

相关文章

  • 对深度学习中全连接层、卷积层的一些理解
    1、全连接层卷积层和全连接层构成了构成了特征提取器,而全连接层构成了分类器,全连接层将特征提取得到的特征图映射成一维特征向量,该特征向量包含所有特征信息,可以转化为分......
  • 智能眼镜抓取log和安装和卸载的一些命令
    安装在智能眼镜里的也是个APK文件因此adb命令都是一样的 adbdevicesadblogcat-vtime>log.txtadbinstall adbuninstallbzh.ama.xperteye.vkadbuninstallbz......
  • js中的...扩展(展开)运算符
             ......
  • Golang语言算术运算符教程
    语法运算符说明范例结果+正号+55-负号-5-5+加法运算符3+58-减法运算符5-32*乘法运算符5*315/除法运算符10/33%取模10%31++自增运算符a=2,a++3--自减运算符a=2;a--1+字符串连......
  • 收藏:关于正则表达式的的一些经验
    匹配中文字符的正则表达式:[\u4e00-\u9fa5]评注:匹配中文还真是个头疼的事,有了这个表达式就好办了匹配双字节字符(包括汉字在内):[^\x00-\xff]评......
  • 一些相当不错的php开源 AJAX聊天工具
    一些相当不错的php开源AJAX聊天工具,详细的可以看到​​​http://roshanbh.com.np/2008/09/free-ajax-chat-applications-php.html​​下了几个来看,相当好,改了下就可以用了......
  • lucene 2.0中的一些要注意的地方
    lucene是一款不错的针对搞全文搜索的API,可以结合JAVA使用,但在用lucene2.0时,如果参考目前的一些文章,可能会有一些API是过期了,我在看一些讲lucene的文章时,就遇到这类的情况,于......
  • 8.golang语言学习,运算符介绍
    1.算术运算自增,自减,只能单独使用,++,--只能写在变量后面2.赋值运算符优先级,单目运算,赋值运算从右到左运算,其余从左到右,无三目运算,用if实现3.比较运算符/关系4.......
  • int *p[]的一些使用
    int*p[]是一个存数组的指针,其中的元素是指针,要对元素所指的位置调用需要二阶调用#include<iostream>#include<cstdio>#include<cstring>usingnamespacestd;intma......
  • python-练习(知识点到逻辑运算符)
    1.在终端中显示古诗"登高"print("登高")print("作者:杜甫")print("风急天高猿啸哀,渚清沙白鸟飞回。")print("无边落木萧萧下,不尽长江滚滚来。")pr......