ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

IEEE 754标准

2021-03-10 20:02:00  阅读:279  来源: 互联网

标签:10 0.1 754 二进制 浮点数 0.2 标准 IEEE 1001


from:https://www.jianshu.com/p/7c636d8f18d5

由于不同机器所选用的基数、尾数位长度和阶码位长度不同,因此对浮点数的表示有较大差别,这不利于软件在不同计算机之间的移植。为此,美国IEEE(电器及电子工程师协会)提出了一个从系统角度支持浮点数的表示方法,称为IEEE754标准(IEEE,1985),当今流行的计算机几乎都采用了这一标准。

Java中浮点数,既float和double,都是采用的IEEE754标准。无论在java python javaScript里面都存在 1 + 2 != 3 问题,这个问题的产生根源在于计算存储数字是二进制,对无限循环小数和无理数采用双精度64位double浮点数_float为32位,即52位小数+11位指数+1位符号。超过52位小数溢出而产生精度丢失。

例如在 chrome js console 中:

// 加法

0.1 + 0.2 = 0.30000000000000004

0.1 + 0.7 = 0.7999999999999999

0.2 + 0.4 = 0.6000000000000001

// 减法

0.3 - 0.2 = 0.09999999999999998

1.5 - 1.2 = 0.30000000000000004

// 乘法

0.8 * 3 = 2.4000000000000004

19.9 * 100 = 1989.9999999999998

// 除法

0.3 / 0.1 = 2.9999999999999996

0.69 / 10 = 0.06899999999999999

// 比较

0.1 + 0.2 === 0.3 // false

(0.3 - 0.2) === (0.2 - 0.1) // false

这个问题并不只是在Javascript中才会出现,任何使用二进制浮点数的编程语言都会有这个问题,只不过在 C++/C#/Java 这些语言中已经封装好了方法来避免精度的问题,而 JavaScript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型,所以精度误差的问题就显得格外突出。

       

浮点数丢失产生原因

JavaScript 中的数字类型只有 Number 一种,Number 类型采用 IEEE754 标准中的 “双精度浮点数” 来表示一个数字,不区分整数和浮点数。

什么是IEEE-745浮点数表示法

IEEE-745浮点数表示法是一种可以精确地表示分数的二进制示法,比如1/2,1/8,1/1024

十进制小数如何表示为转为二进制

十进制整数转二进制

十进制整数换成二进制一般都会:1=>1 2=>10 3=>101 4=>100 5=>101 6=>110   

6/2=3…0

3/2=1…1

1/2=0…1

倒过来就是110

十进制小数转二进制

0.25的二进制

0.25*2=0.5 取整是0

0.5*2=1.0    取整是1

即0.25的二进制为 0.01 ( 第一次所得到为最高位,最后一次得到为最低位)

0.8125的二进制

0.8125*2=1.625   取整是1

0.625*2=1.25     取整是1

0.25*2=0.5       取整是0

0.5*2=1.0        取整是1

即0.8125的二进制是0.1101(第一次所得到为最高位,最后一次得到为最低位)

0.1的二进制

0.1*2=0.2======取出整数部分0

0.2*2=0.4======取出整数部分0

0.4*2=0.8======取出整数部分0

0.8*2=1.6======取出整数部分1

0.6*2=1.2======取出整数部分1

0.2*2=0.4======取出整数部分0

0.4*2=0.8======取出整数部分0

0.8*2=1.6======取出整数部分1

0.6*2=1.2======取出整数部分1

接下来会无限循环

0.2*2=0.4======取出整数部分0

0.4*2=0.8======取出整数部分0

0.8*2=1.6======取出整数部分1

0.6*2=1.2======取出整数部分1

所以0.1转化成二进制是:0.0001 1001 1001 1001…(无限循环)

0.1 => 0.0001 1001 1001 1001…(无限循环)

同理0.2的二进制是0.0011 0011 0011 0011…(无限循环)

科普科学计数法

科学记数法是一种把一个数表示成a与10的n次幂相乘的形式(1≤a<10,n为整数)的记数法。

例如:19971400000000=1.99714×10^13。计算器或电脑表达10的幂是一般是用E或e,也就是1.99714E13=19971400000000。

科学记数法的形式是由两个数的乘积组成的。表示为a×10^b(aEb),其中一个因数为a(1≤|a|<10),另一个因数为10^n。

科学计数法的精确度

运用科学记数法a×10^n的数字,它的精确度以a的最后一个数在原数中的数位为准。

13600,精确到十位,记作:1.360X10^4

13600 ,精确到百位,记作:1.36X10^4

13600,精确到千位,记作:1.3X10^4

十进制的5.0,写成二进制是101.0,相当于1.01×2^2

十进制的-5.0,写成二进制是-101.0,相当于-1.01×2^2,推荐阅读《浮点数的二进制表示 

在二进制里面,即a×2^b,1≤a<2,也就是说,a可以写成1.xxxxxx的形式,其中xxxxxx表示小数部分。IEEE 754规定,在计算机内部保存a时,默认这个数的第一位总是1,因此可以被舍去,只保存后面的xxxxxx部分。比如保存1.01的时候,只保存01,等到读取的时候,再把第一位的1加上去。这样做的目的,是节省1位有效数字。以64位浮点数为例,留给a只有52位,将第一位的1舍去以后,等于可以保存53位有效数字

所以js浮点数表示的数字为[-1*2^53*2^1023,1*2^53*2^1024] ,±1 为符号位,2^53 为小数位(53-1), 2^1024 1024位指数位(1024*2=2048=2^11为指数位)

IEEE-745浮点数表示法存储结构

在 IEEE754 中,双精度浮点数采用 64 位存储,即 8 个字节表示一个浮点数 。其存储结构如下图所示:

   

指数位可以通过下面的方法转换为使用的指数值:

   

IEEE-745浮点数表示法记录数值范围

从存储结构中可以看出, 指数部分的长度是11个二进制,即指数部分能表示的最大值是 2047(2^11-1)

取中间值进行偏移,用来表示负指数,也就是说指数的范围是 [-1023,1024] 

因此,这种存储结构能够表示的数值范围为 2^1024 到 2^-1023 ,超出这个范围的数无法表示 。2^1024  和 2^-1023  转换为科学计数法如下所示:

1.7976931348623157 × 10^308

5 × 10^-324

因此,JavaScript 中能表示的最大值是 1.7976931348623157 × 10^308,最小值为 5 × 10-324 。java双精度类型 double也是如此。

这两个边界值可以分别通过访问 Number 对象的 MAX_VALUE 属性和 MIN_VALUE 属性来获取:

Number.MAX_VALUE; // 1.7976931348623157e+308

Number.MIN_VALUE; // 5e-324

如果数字超过最大值或最小值,JavaScript 将返回一个不正确的值,这称为 “正向溢出(overflow)” 或 “负向溢出(underflow)” 。 

Number.MAX_VALUE+1 == Number.MAX_VALUE; //true

Number.MAX_VALUE+1e292; //Infinity

Number.MIN_VALUE + 1; //1

Number.MIN_VALUE - 3e-324; //0

Number.MIN_VALUE - 2e-324; //5e-324

IEEE-745浮点数表示法数值精度

在 64 位的二进制中,符号位决定了一个数的正负,指数部分决定了数值的大小,小数部分决定了数值的精度

IEEE754 规定,有效数字第一位默认总是1 。因此,在表示精度的位数前面,还存在一个 “隐藏位” ,固定为 1 ,但它不保存在 64 位浮点数之中。也就是说,有效数字总是 1.xx...xx 的形式,其中 xx..xx 的部分保存在 64 位浮点数之中,最长为52位 。所以,JavaScript 提供的有效数字最长为 53 个二进制位,其内部实际的表现形式为:

(-1)^符号位 * 1.xx...xx * 2^指数位

这意味着,JavaScript 能表示并进行精确算术运算的整数范围为:[-2^53-1,2^53-1],即从最小值 -9007199254740991 到最大值 9007199254740991 之间的范围 。

Math.pow(2, 53)-1 ; // 9007199254740991

-Math.pow(2, 53)-1 ; // -9007199254740991

可以通过 Number.MAX_SAFE_INTEGER 和  Number.MIN_SAFE_INTEGER 来分别获取这个最大值和最小值。 

console.log(Number.MAX_SAFE_INTEGER) ; // 9007199254740991

console.log(Number.MIN_SAFE_INTEGER) ; // -9007199254740991

对于超过这个范围的整数,JavaScript 依旧可以进行运算,但却不保证运算结果的精度。

Math.pow(2, 53) ; // 9007199254740992

Math.pow(2, 53) + 1; // 9007199254740992

9007199254740993; //9007199254740992

90071992547409921; //90071992547409920

0.923456789012345678;//0.9234567890123456

IEEE-745浮点数表示法数值精度丢失

计算机中的数字都是以二进制存储的,二进制浮点数表示法并不能精确的表示类似0.1这样 的简单的数字

如果要计算 0.1 + 0.2 的结果,计算机会先把 0.1 和 0.2 分别转化成二进制,然后相加,最后再把相加得到的结果转为十进制 

但有一些浮点数在转化为二进制时,会出现无限循环 。比如, 十进制的 0.1 转化为二进制,会得到如下结果:

0.1 => 0.0001 1001 1001 1001…(无限循环)

0.2 => 0.0011 0011 0011 0011…(无限循环)

而存储结构中的尾数部分最多只能表示 53 位。为了能表示 0.1,只能模仿十进制进行四舍五入了,但二进制只有 0 和 1 , 于是变为 0 舍 1 入 。 因此,0.1 在计算机里的二进制表示形式如下:

0.1 => 0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 101

0.2 => 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 001

用标准计数法表示如下:

0.1 => (−1)0 × 2^4 × (1.1001100110011001100110011001100110011001100110011010)2

0.2 => (−1)0 × 2^3 × (1.1001100110011001100110011001100110011001100110011010)2 

在计算浮点数相加时,需要先进行 “对位”,将较小的指数化为较大的指数,并将小数部分相应右移:

最终,“0.1 + 0.2” 在计算机里的计算过程如下:

   

经过上面的计算过程,0.1 + 0.2 得到的结果也可以表示为:

(−1)0 × 2−2 × (1.0011001100110011001100110011001100110011001100110100)2=>.0.30000000000000004

通过 JS 将这个二进制结果转化为十进制表示:

(-1)**0 * 2**-2 * (0b10011001100110011001100110011001100110011001100110100 * 2**-52); //0.30000000000000004

console.log(0.1 + 0.2) ; // 0.30000000000000004

这是一个典型的精度丢失案例,从上面的计算过程可以看出,0.1 和 0.2 在转换为二进制时就发生了一次精度丢失,而对于计算后的二进制又有一次精度丢失 。因此,得到的结果是不准确的。

几乎所有的编程语言浮点数都是都采用IEEE浮点数算术标准

在C或者java里面,double是64位浮点数,float 是32 浮点数:  1bit符号  8bit指数部分 23bit尾数。推荐阅读《JAVA 浮点数的范围和精度

long与double在java中本身都是用64位存储的,但是他们的存储方式不同,导致double可储存的范围比long大很多

long可以准确存储19位数字,而double只能准备存储16位数字(实际测试,是17位,)。double由于有exp位,可以存16位以上的数字,但是需要以低位的不精确作为代价。如果一个大于17位的long型数字存到double上,就会丢失数字末尾的精度

如果需要高于19位数字的精确存储,则必须用BigInteger来保存,当然会牺牲一些性能。

java 基本数据类型

基本类型存储需求位数bit数取值范围取值范围

byte1byte8bit1*8(8)-2^7~2^7-1-128~127

short2byte16bit2*8(16)-2^15~2^15-1-32768~32767

int4byte32bit4*8(32)-2^31~2^31-1-2147483648~2147483647

long8byte64bit8*8(64)-2^63~2^63-1=-9223372036854775808~9223372036854775807

float4byte32bit4*8(32)3.4028235E38~1.4E-453.4028235*10^38~1.4*10^-45

double8byte64bit8*8(64)-2^1023~2^10241.7976931348623157*10^308~4.9*10^-324

char2byte16bit2*8(16)2^16 - 1unicode编码范围

 java中char类型占2个字节、16位可以存放汉子,字母和数字占一个字节,一个字节8位,中文占2个字节,16位。

在表中,long最大为=2^63-1,而double为2^1024,

java中int和float都是32位,long和double都是64位。为什么float和double表示的范围大那么多呢?因为double采用IEEE-745浮点数表示法

double是n*2^m(n乘以2的m次方)这种形式存储的,只需要记录n和m两个数就行了,m的值影响范围大,所以表示的范围比long大。

但是m越大,n的精度就越小,所以double并不能把它所表示的范围里的所有数都能精确表示出来,而long就可以。

float浮点数,小数点后第7位是部分准确的。例如,1.0000004就是1.00000035通过得到的,其实际保存和1.0000003相同。1.0000006也是通过舍入得到的。再往前第6位及以后均可以通过小数准确表示出来。通常说float数据的有效位是6~7位,也是这个原因。一般来说,无论是整数或者小数,用float表示时,从左边第一个非0的数字算起,从高到低的7位是准确的。此后的数位是不能保证精确的

double双精度浮点数小数部分有52位,和上面类似,最低6位(2^-52,2^-51,......)表示的规格化小数如下所示。从图中可以看出,双精度浮点数能准确表示到小数点后第15位,第16位部分准确。用double表示时,从左边第一个非0的数字起,从高到低的16位是准确的,此后的数位不一定精确

尽管浮点数表示的范围很广,但由于精度损失的存在,加上幂次的放大作用,一个浮点数实际上是表示了周围的一个有理数区间。如果将浮点数绘制到一个数轴上,直观上看,靠近0的部分,浮点数出现较密集。越靠近无穷大,浮点数分布越稀疏,一个浮点值代表了周围一片数据。如下图所示。从这个意义上来说,浮点数不宜直接比较相等,它们是代表了一个数据范围。实际应用中,如果要使用浮点数计算,一定要考虑精度问题。在满足精度要求的前提下,计算结果才是有效的。 

在计算精度要求情形下,例如商业计算等,应该避免使用浮点数,严格采取高精度计算。

   

浮点数丢失解决方案

我们常用的分数(特别是在金融的计算方面)都是十进制分数1/10,1/100等。或许以后电路设计或许会支持十进制数字类型以避免这些舍入问题。在这之前,你更愿意使用大整数进行重要的金融计算,例如,要使用整数‘分’而不是使用小数‘元’进行货比单位的运算

即在运算前我们把参加运算的数先升级(10的X的次方)到整数,等运算完后再降级(0.1的X的次方)。

在java里面有BigDecimal库,js里面有big.jsjs-big-decimal.js。当然BCD编码就是为了十进制高精度运算量制。

BCD编码

BCD编码(一般指8421BCD码形式)亦称二进码十进数或二-十进制代码。用4位二进制数来表示1位十进制数中的0~9这10个数。一般用于高精度计算。比如会计制度经常需要对很长的数字串作准确的计算。相对于一般的浮点式记数法,采用BCD码,既可保存数值的精确度,又可免去使电脑作浮点运算时所耗费的时间。

为什么采用二进制

二进制在电路设计中物理上更易实现,因为电子器件大多具有两种稳定状态,比如晶体管的导通和截止,电压的高和低,磁性的有和无等。而找到一个具有十个稳定状态的电子器件是很困难的。

二进制规则简单,十进制有55种求和与求积的运算规则,二进制仅有各有3种,这样可以简化运算器等物理器件的设计。另外,计算机的部件状态少,可以增强整个系统的稳定性。

与逻辑量相吻合。二进制数0和1正好与逻辑量“真”和“假”相对应,因此用二进制数表示二值逻辑显得十分自然。

可靠性高。二进制中只使用0和1两个数字,传输和处理时不易出错,因而可以保障计算机具有很高的可靠性

我觉得主要还是因为第一条。如果比如能够设计出十进制的元器件,那么对于设计其运算器也不再话下。

JS数字精度丢失的一些典型问题

两个简单的浮点数相加

0.1 + 0.2 != 0.3 // true

toFixed 不会四舍五入(Chrome)

1.335.toFixed(2) // 1.33

再问问一个问题 :在js数字类型中浮点数的最高精度多少位小数?(16位 or 17位?……why?

JavaScript 能表示并进行精确算术运算的整数范围为:[-2^53-1,2^53-1],即从最小值 -9007199254740991 到最大值 9007199254740991 之间的范围。'9007199254740991'.length//16 

IEEE754 规定,有效数字第一位默认总是1 。因此,在表示精度的位数前面,还存在一个 “隐藏位” ,固定为 1 ,但它不保存在 64 位浮点数之中。也就是说,有效数字总是 1.xx...xx 的形式,其中 xx..xx 的部分保存在 64 位浮点数之中,最长为52位 。所以,JavaScript 提供的有效数字最长为 53 个二进制位

let a=1/3

a.toString();//"0.3333333333333333"

a.toString();.length//18

a*3===0.3333333333333333*3===1

0.3333333333333332*3!==1

相关链接:  

http://0.30000000000000004.com

http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html



作者:ksfkf
链接:https://www.jianshu.com/p/7c636d8f18d5
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

标签:10,0.1,754,二进制,浮点数,0.2,标准,IEEE,1001
来源: https://www.cnblogs.com/liuqiyun/p/14513889.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有