JS计算精度小记

栏目: JavaScript · 发布时间: 6年前

内容简介:整数部分除二取余数, 直到商为0,逆序排列,小数部分乘2取整,顺序排列,直到积中小数部分为0或者注意:二进制转为十进制不分整数部分与小数部分。

整数部分除二取余数, 直到商为0,逆序排列,小数部分乘2取整,顺序排列,直到积中小数部分为0或者 到达要求精度

8转为二进制

8 / 2 = 4...0  取0
4 / 2 = 2...0  取0
2 / 2 = 1...0  取0
1 / 2 = 0...1  取1

二进制结果为:1000

0.25转为二进制

0.25 * 2 = 0.50  取0
0.50 * 2 = 1.00  取1

二进制结果为:01

于是可得出8.25的二进制表示:1000.01
复制代码

2. 二进制如何转为十进制?

注意:二进制转为十进制不分整数部分与小数部分。

二进制1000.01转为十进制

1 * 2^3 + 0 * 2^2 + 0 * 2^1 + 0 * 2^0 + 0 * 2^-1 + 0 * 2^-2 = 8.25
复制代码

二. javascript是如何保存数字的

JavaScript 里的数字是采用 IEEE 754 标准的 64 位 double 双精度浮点数

  1. sign bit(符号): 用来表示正负号,1位 (0表示正,1表示负)

  2. exponent(指数): 用来表示次方数,11位

  3. mantissa(尾数): 用来表示精确度,52位

JS计算精度小记

对于没有接触的读者来说,以上可能理解起来很模糊,没关系,接下来我们用案例具体说明其流程,先看一下上述的十进制数8.25在JS中是如何保存的

  1. 十进制的 8.25 会被转化为二进制的 1000.01
  2. 二进制 1000.01 可用二进制的科学计数法 1.00001 * 2^4 表示;
  3. 1.00001 * 2^4 的小数部分 00001 (二进制)就是mantissa(尾数)了, 4 (十进制)加上 1023 就是exponent(指数)了(这里后面讲解为什么要加上1023);
  4. 接下来指数 4 要加上 1023 后转为二进制 10000000011
  5. 我们的十进制 8.25 是一个正数,所以符号为二进制表示为 0
  6. 8.25 最终的二进制保存 0-10000000011-0000100000000000000000000000000000000000000000000000

注意点:

  1. 不够位的我们都用0补充;
  2. 步骤2得出的科学计数中的整数本分1我们好像忘记,这里因为Javascript为了更最大限度的提高精确度,而省略了这个1, 这样在我们我们本来只能保存(二进制)52位的尾数,实际是有(二进制)53位的;
  3. 指数部分是11位,表示的范围是[0, 2047],由于科学计数中的指数可正可负,所以,中间数为 1023,[0,1022] 表示为负,[1024,2047] 表示为正, 这也解释了为什么我们科学计数中的指数要加上1023进行存储了。

三. javascript是如何读取数字的

我们还是以8.25的二进制 0-10000000011-0000100000000000000000000000000000000000000000000000 来讲述

  1. 首先我们获取指数部分的二进制 1000000001 ,转化为十进制为 10271027 减去 1023 就是我们实际的指数 4 了;
  2. 获取尾数部分 0000100000000000000000000000000000000000000000000000 实际是 0.00001 (后面的0就不写了),然后加上我们忽略的 1 ,得出 1.00001
  3. 因为首位为 0 ,所以我们的数为正数,得出二进制的科学计数为 1.00001 * 2^4 ,接着再转为十进制数,就得到了我们的 8.25

四. 从0.1+0.2来看javascript精度问题

这里就要进入我们的正题了,看懂了前面的原理说明,这部分将会变得很好理解了。

要计算 0.1+0.2 ,首先计算要先读取到这两个浮点数

0.1存储为64位二进制浮点数

没有忘记以上步骤吧~

  1. 先将0.1转化为二进制的整数部分为 0 ,小数部分为 0001100110011001100110011001100110011... 咦,这里居然进入了无限循环,那怎么办呢?暂时先不管;
  2. 我们得到的无限循环的二进制数用科学计数表示为 1.100110011001100110011001100110011... * 2^-4
  3. 指数位即是 -4 + 1023 = 1019 ,转化位11位二进制数 01111111011
  4. 尾数位是无限循环的,但是双精度浮点数规定尾数位52位,于是超出52位的将被略去,保留 1001100110011001100110011001100110011001100110011010
  5. 最后得出0.1的64位二进制浮点数: 0-01111111011-1001100110011001100110011001100110011001100110011010

同上,0.2存储为64位二进制浮点数: 0-01111111100-1001100110011001100110011001100110011001100110011010

读取到两个浮点数的64为二进制后,再将其转化为可计算的二进制数

  1. 0.1转化为 1.1001100110011001100110011001100110011001100110011010 * 2^(1019 - 1023) —— 0.00011001100110011001100110011001100110011001100110011010 ;
  2. 0.2转化为 1.1001100110011001100110011001100110011001100110011010 * 2^(1020 - 1023) —— 0.0011001100110011001100110011001100110011001100110011010 ;

接着将两个浮点数的二进制数进行加法运算,得出 0.0100110011001100110011001100110011001100110011001100111 转化为十进制数即为 0.30000000000000004

不难看出,精度缺失是在存储这一步就丢失了,后面的计算只是在不精准的值上进行的运算。

五. javascript如何解决精度问题出现的计算错误问题

对于小数或者整数的简单运算可如下解决:

function numAdd(num1, num2) { 
  let baseNum, baseNum1, baseNum2; 
  try { 
    baseNum1 = String(num1).split(".")[1].length; 
  } catch (e) { 
    baseNum1 = 0; 
  } 
  try { 
    baseNum2 = String(num2).split(".")[1].length; 
  } catch (e) { 
    baseNum2 = 0;
  } 
  baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
  return (num1 * baseNum + num2 * baseNum) / baseNum;
};

复制代码

如: 0.1 + 0.2 通过函数处理后,相当于 (0.1 * 10 + 0.2 * 10) / 10

但是如同我们前面所了解的,浮点数在存储的时候就已经丢失精度了,所以浮点数乘以一个基数仍然会存在精度缺失问题,比如 2500.01 * 100 = 250001.00000000003 , 所以我们可以在以上函数的结果之上使用toFixed(),保留需要的小数位数。

一些复杂的计算,可以引入一些库进行解决。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

玻璃笼子

玻璃笼子

[美]尼古拉斯·卡尔 / 杨柳 / 中信出版社 / 2015-11 / 49.00元

这是一本关于自动化的书,它提醒我们自动化对人类的影响,人们心安理得享受技术带来的便利却忽视了,它已经渗透进了生活和工作改变了我们的思维和认知方式。商家在设计程序和应用时,早就把他们的想法埋入了编程和APP中。 卡尔的作品无疑是给我们这个时代灌入了的一剂清醒药。他独特的思考问题角度,犀利甚至略为偏激 的言论再加上丰富的*前沿的科技案例会让人读起来畅快淋漓,且醍醐灌顶,意识到自动化等高科技潜移默......一起来看看 《玻璃笼子》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具