为什么0的0次方等于NaN(非数字)呢?

学过JavaScript的童鞋应该非常清楚,0.1 + 0.2 是不等于0.3的,而是等于0.00004,至于为什么会这样?好像有点说不清楚,只知道是JavaScript精确度

已经很久没有写技术文章了,脑袋瓜有点生锈,写的不好别见怪,今天就是想带点干货给大家分享一下。文章的内容有一点点难度,不过基本都是计算机组成原理的知识,算是温故而知新吧!

学过JavaScript的童鞋应该非常清楚,0.1 + 0.2 是不等于0.3的,而是等于0.00004,至于为什么会这样?好像有点说不清楚,只知道是JavaScript精确度问题,其他不得而知了。没关系,看完慢慢就懂了!

说明:转换过程就不在这边描述了

IEEE二进制浮点数算术标准(IEEE 754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。
这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number)),一些特殊数值(无穷(Inf)与非数值(NaN)),以及这些数值的“浮点数运算符”;它也指明了四种数值舍入规则和五种例外状况(包括例外发生的时机与处理方式)。

上图是64位的双精度浮点数,最高位是符号位S(sign),中间的11位是指数E(exponent),剩下的52位为尾数(有效数字)M(mantissa)。

根据IEEE 754标准,任意一个浮点数的二进制都可以用如下公式进行表示:

S为符号位:表示浮点数的正负(0代表正数,1代表负数);

E为指数位:存储指数,该数都会加上一个常数(偏移量),用来表示次方数;

M为尾数位:表示有效位(尾数),超出的部分自动进1舍0;

双精度的浮点数真值(带有正负号的数值是真值)最终可以表示为:

说明:E是无符号整数,长度是11位,取值范围是为0~2047。因为科学计数法中的指数是可以为负数,所以约定减去一个中间数(偏移量)1023,[0,1022] 表示为负,[1024,2047] 表示为正。

大部分二进制浮点数都以规格化格式进行存放,以便将有效数字的精度最大化,提升精确度。

小数点向右移动4位,让其小数点左边只有一个“1”。

根据IEEE754标准,双精度浮点的尾数只能存储52位,红色的“1”是第53位,根据进1舍0的原则进行操作,操作后的值为:

指数-4等于1019(E) – 1023(常量),由此可得E等于1019,把1019转为二进制。

说明:红色为符号位;绿色为指数位;蓝色为尾数位;

小数点向右移动3位,让其小数点左边只有一个“1”。

红色的“1”是第53位,根据进1舍0的原则进行操作,操作后的值为:

指数-3等于1020(E) – 1023(常量),由此可得E等于1020,把1020转为二进制。

说明:红色为符号位;绿色为指数位;蓝色为尾数位;

对阶的目的是使两数的小数点位置对齐,方便两数进行运算,换句话说就是两数的阶码要相等。根据小阶向大阶看齐的原则,应使0.1的尾数向右移动1位(可以理解为小数点向左移动1位),阶码加1。

尾数向右移动1位后,阶码和尾数的值变化如下:

蓝色的“1”是右移补的位,红色的0是舍去的位,根据IEEE 754标准双精度浮点的尾数只能存储52位,遵循进1舍0的原则进行操作。

0.1的科学计数法表示:

0.1的二进制存储格式:

尾数部分M通常都是规格化表示的,非"0"的尾数其第1位总是"1",而这一位也称作隐藏位,因为存储的时候该位会被省略。比如存储1.0110时,只存储尾数0110,等到读取的时候才把第1位的1加补上去,这么做相当于多保存了1位有效数字。

根据尾数求和的结果,进行规格化处理,即尾数向右移1位,阶码加1。

尾数只能存储52位,红色的“1”需要舍去,根据进1舍0的原则进行操作可得:

指数-2等于1021(E) – 1023(常量),由此可得E等于1021,把1021转为二进制;

浮点数的溢出其实是阶码的溢出表现出来的,在算术运算过程中要检查是否产生了溢出。若阶码正常,算术运算正常结束;若阶码溢出,则要进行相应处理。

如上求和结果的阶码为,没有产生溢出,因此运算结束。

二进制存储格式是计算机存储和运算的格式,此时把二进制转为十进制,我们可以看看最终求和的值会是多少?

规格化的值是转为非规格化

指数的值为2,将规格化的小数点向左移动2位即可。

非规格化的值转成十进制

为了方便大家学习与实践,提供如下PHP实践代码,它是将“0.110100”转成十进制的功能代码,大家可以尝试测试一下。

好了,干货分享完毕,谢谢大家!

声明:需要读者对二进制有一定的了解

对于 JavaScript 开发者来说,或多或少都遇到过 js 在处理数字上的奇怪现象,比如:


如果想要弄明白为什么会出现这些奇怪现象,首先要弄清楚 JavaScript 是怎样编码数字的

JavaScript 中的数字,不管是整数、小数、分数,还是正数、负数,全部是浮点数,都是用 8 个字节(64 位)来存储的。

一个数字(如 120.12-999)在内存中占用 8 个字节(64 位),存储方式如下:

  1. 63:符号位(1 位:0 表示这个数是正数,1 表示这个数是负数)

符号位很好理解,用于指明是正数还是负数,且只有 1 位、两种情况(0 表示正数,1 表示负数)。

其他两部分是分数部分和指数部分,用于计算一个数的绝对值。

1.1 绝对值计算公式


  • 这个公式是二进制的算法公式,结果用 abs 表示,分数部分用 f 表示,指数部分用 e 表示
  • 因为指数部分占 11 位,所以 e 的取值范围为 0() 到 2047()

从上面的公式可以看出:

1.2 绝对值的取值范围与边界

从上面的公式可以看出:

这只表示一个值 0,但加上符号位,所以有 +0-0


这只表示一种值 NaN



1.3 绝对值的最大安全值

但这个数值并不安全:从 1Number.MAX_VALUE 中间的数字并不连续,而是离散的。

所以才会有文章开头的现象:


因为 Math.pow(2, 53) + 1 不能用公式得出,就无法存储在内存中,所以只有取最靠近这个数的、能够用公式得出的其他数,Math.pow(2, 53),然后存储在内存中,这就是失真,即不安全。

1.4 小数的存储方式与计算

小数中,除了满足 m / (2 ^ n)m, n 都是整数)的小数可以用完整的 2 进制表示之外,其他的都不能用完整的 2 进制表示,只能无限的逼近一个 2 进制小数。

(注:[2] 表示二进制,^ 表示 N 次方)



 
... 根据公式计算,直到把分数部分的 52 位填满,然后取最靠近的数

从上面可以看出,小数中大部分都只是近似值,只有少部分是真实值,所以只有这少部分的值(满足 m / (2 ^ n) 的小数)可以直接比较大小,其他的都不能直接比较。



1.5 小数最大保留位数

js 从内存中读取一个数时,最大保留 17 位有效数字。





表示 1 与 Number 可表示的大于 1 的最小的浮点数之间的差值。


用于浮点数之间安全的比较大小。


js 所能表示的最大数值(8 个字节能存储的最大数值)。


最小安全值(包括符号)。


js 所能表示的最小数值(绝对值)。




3. 寻找奇怪现象的原因

0.3 的逼近算法类似。



f = 00111 有 53 位,超过了正常的 52 位,无法存储,所以取最近的数:


因为 Math.pow(2, 53) + 1 不能用公式得出,无法存储在内存中,所以只有取最靠近这个数的、能够用公式得出的其他数。

比这个数小的、最靠近的数:


比这个数大的、最靠近的数:



版权声明:自由转载-非商用-非衍生-保持署名()

我要回帖

更多关于 12的0次方等于多少 的文章

 

随机推荐