javascript进制转换_JavaScript 加减危机——为什么会出现这样的结果?

在日常工作计算中,我们如履薄冰,但是 JavaScript 总能给我们这样那样的 surprise~

  1. 0.1 + 0.2 = ?
  2. 1 - 0.9 = ?

如果小伙伴给出内心的结果:

  1. 0.1 + 0.2 = 0.3
  2. 1 - 0.9 = 0.1

那么小伙伴会被事实狠狠地扇脸:

console.log(0.1 + 0.2); // 0.30000000000000004console.log(1 - 0.9); // 0.09999999999999998

为什么会出现这种情况呢?咱们一探究竟!

三 问题复现

返回目录

下面,我们会通过探讨 IEEE 754 标准,以及 JavaScript 加减的计算过程,来复现问题。

3.1 根源:IEEE 754 标准

返回目录

JavaScript 里面的数字采用 IEEE 754 标准的 64 位双精度浮点数。该规范定义了浮点数的格式,对于 64 位的浮点数在内存中表示,最高的 1 位是符号为,接着的 11 位是指数,剩下的 52 位为有效数字,具体:

  • 第 0 位:符号位。用 s 表示,0 表示为正数,1 表示为负数;
  • 第 1 - 11 位:存储指数部分。用 e 表示;
  • 第 12 - 63 位:存储小数部分(即有效数字)。用 f 表示。
31d4422d0c6887706b7d7a7d882979ce.png

符号位决定一个数的正负,指数部分决定数值的大小,小数部分决定数值的精度。

IEEE 754 规定,有效数字第一位默认总是 1,不保存在 64 位浮点数之中。

也就是说,有效数字总是 1.XX......XX的形式,其中 XX......XX 的部分保存在 64 位浮点数之中,最长可能为 52 位。

因此,JavaScript 提供的有效数字最长为 53 个二进制位(64 位浮点的后 52 位 + 有效数字第一位的 1)。

3.2 复现:计算过程

返回目录

通过 JavaScript 计算 0.1 + 0.2 时,会发生什么?

1、 将 0.1 和 0.2 换成二进制表示:

0.1 -> 0.0001100110011001...(无限)0.2 -> 0.0011001100110011...(无限

浮点数用二进制表达式是无穷的

我自己是一名从事了多年开发的web前端老程序员,目前辞职在做自己的web前端私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的web前端学习干货,各种框架都有整理,送给每一位前端小伙伴,想要获取的可以关注我的头条号并在后台私信我:前端,即可免费获取。

2、 因为 IEEE 754 标准的 64 位双精度浮点数的小数部分最多支持 53 位二进制位,所以两者相加之后得到二进制为:

0.0100110011001100110011001100110011001100110011001100

因为浮点数小数位的限制,这个二进制数字被截断了,用这个二进制数转换成十进制,就成了 0.30000000000000004,从而在进行算数计算时产生误差。

3.3 扩展:数字安全

返回目录

在看完上面小数的计算不精确后,jsliang 觉得有必要再聊聊整数,因为整数同样存在一些问题:

console.log(19571992547450991);// 19571992547450990console.log(19571992547450991 === 19571992547450994);// true

是不是很惊奇!

因为 JavaScript 中 Number 类型统一按浮点数处理,整数也不能逃避这个问题:

// 最大值const MaxNumber = Math.pow(2, 53) - 1;console.log(MaxNumber); // 9007199254740991console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991// 最小值const MinNumber = -(Math.pow(2, 53) - 1);console.log(MinNumber); // -9007199254740991console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991

即整数的安全范围是: [-9007199254740991, 9007199254740991]。

超过这个范围的,就存在被舍去的精度问题。

当然,这个问题并不仅仅存在于 JavaScript 中,几乎所有采用了 IEEE-745 标准的编程语言,都会有这个问题,只不过在很多其他语言中已经封装好了方法来避免精度的问题。

  • PHP Float 浮点型 - Manual
  • Java 您的小数点到哪里去了? - Brian Goetz

而因为 JavaScript 是一门弱类型的语言,从设计思想上就没有对浮点数有个严格的数据类型,所以精度误差的问题就显得格外突出。

到此为止,我们可以看到 JavaScript 在处理数字类型的操作时,可能会产生一些问题。

事实上,工作中还真会有问题!

某天我处理了一个工作表格的计算,然后第二天被告知线上有问题,之后被产品小姐姐问话:

  • 为什么小学生都能做出的小数计算,你们计算机算不了呢?

默哀三秒,产生上面的找到探索,最终找到下面的解决方案。

四 解决问题

返回目录

下面尝试通过各种方式来解决浮点数计算的问题。

4.1 toFixed()

返回目录

toFixed() 方法使用定点表示法来格式化一个数值。

  • 《toFixed - MDN》

语法:numObj.toFixed(digits)

参数:digits。小数点后数字的个数;介于 0 到 20(包括)之间,实现环境可能支持更大范围。如果忽略该参数,则默认为 0。

const num = 12345.6789;num.toFixed(); // '12346':进行四舍五入,不包括小数部分。num.toFixed(1); // '12345.7':进行四舍五入,保留小数点后 1 个数字。num.toFixed(6); // '12345.678900':保留小数点后 6 个数字,长度不足时用 0 填充。(1.23e+20).toFixed(2); // 123000000000000000000.00 科学计数法变成正常数字类型

toFixed() 得出的结果是 String 类型,记得转换 Number 类型。

toFixed() 方法使用定点表示法来格式化一个数,会对结果进行四舍五入。

通过 toFixed() 我们可以解决一些问题:

原加减乘数:

console.log(1.0 - 0.9);// 0.09999999999999998console.log(0.3 / 0.1);// 2.9999999999999996console.log(9.7 * 100);// 969.9999999999999console.log(2.22 + 0.1);// 2.3200000000000003

使用 toFixed():

// 公式:parseFloat((数学表达式).toFixed(digits));// toFixed() 精度参数须在 0 与20 之间parseFloat((1.0 - 0.9).toFixed(10));// 0.1 parseFloat((0.3 / 0.1).toFixed(10));// 3 parseFloat((9.7 * 100).toFixed(10));// 970parseFloat((2.22 + 0.1).toFixed(10));// 2.32

那么,讲到这里,问题来了:

  • parseFloat(1.005.toFixed(2))

会得到什么呢,你的反应是不是 1.01 ?

然而并不是,结果是:1。

这么说的话,enm...摔!o(╥﹏╥)o

toFixed() 被证明了也不是最保险的解决方式。

4.2 手写简易加减乘除

返回目录

既然 JavaScript 自带的方法不能自救,那么我们只能换个思路:

  • 将 JavaScript 的小数部分转成字符串进行计算
/** * @name 检测数据是否超限 * @param {Number} number  */const checkSafeNumber = (number) => { if (number > Number.MAX_SAFE_INTEGER || number < Number.MIN_SAFE_INTEGER) { console.log(`数字 ${number} 超限,请注意风险!`); }};/** * @name 修正数据 * @param {Number} number 需要修正的数字 * @param {Number} precision 端正的位数 */const revise = (number, precision = 12) => { return +parseFloat(number.toPrecision(precision));}/** * @name 获取小数点后面的长度 * @param {Number} 需要转换的数字 */const digitLength = (number) => { return (number.toString().split('.')[1] || '').length;};/** * @name 将数字的小数点去掉 * @param {Number} 需要转换的数字 */const floatToInt = (number) => { return Number(number.toString().replace('.', ''));};/** * @name 精度计算乘法 * @param {Number} arg1 乘数 1 * @param {Number} arg2 乘数 2 */const multiplication = (arg1, arg2) => { const baseNum = digitLength(arg1) + digitLength(arg2); const result = floatToInt(arg1) * floatToInt(arg2); checkSafeNumber(result); return result / Math.pow(10, baseNum); // 整数安全范围内的两个整数进行除法是没问题的 // 如果有,证明给我看};console.log('------乘法:');console.log(9.7 * 100); // 969.9999999999999console.log(multiplication(9.7, 100)); // 970console.log(0.01 * 0.07); // 0.0007000000000000001console.log(multiplication(0.01, 0.07)); // 0.0007console.log(1207.41 * 100); // 120741.00000000001console.log(multiplication(1207.41, 100)); // 0.0007/** * @name 精度计算加法 * @description JavaScript 的加法结果存在误差,两个浮点数 0.1 + 0.2 !== 0.3,使用这方法能去除误差。 * @param {Number} arg1 加数 1 * @param {Number} arg2 加数 2 * @return arg1 + arg2 */const add = (arg1, arg2) => { const baseNum = Math.pow(10, Math.max(digitLength(arg1), digitLength(arg2))); return (multiplication(arg1, baseNum) + multiplication(arg2, baseNum)) / baseNum;}console.log('------加法:');console.log(1.001 + 0.003); // 1.0039999999999998console.log(add(1.001, 0.003)); // 1.004console.log(3.001 + 0.07); // 3.0709999999999997console.log(add(3.001, 0.07)); // 3.071/** * @name 精度计算减法 * @param {Number} arg1 减数 1 * @param {Number} arg2 减数 2 */const subtraction = (arg1, arg2) => { const baseNum = Math.pow(10, Math.max(digitLength(arg1), digitLength(arg2))); return (multiplication(arg1, baseNum) - multiplication(arg2, baseNum)) / baseNum;};console.log('------减法:');console.log(0.3 - 0.1); // 0.19999999999999998console.log(subtraction(0.3, 0.1)); // 0.2/** * @name 精度计算除法 * @param {Number} arg1 除数 1 * @param {Number} arg2 除数 2 */const division = (arg1, arg2) => { const baseNum = Math.pow(10, Math.max(digitLength(arg1), digitLength(arg2))); return multiplication(arg1, baseNum) / multiplication(arg2, baseNum);};console.log('------除法:');console.log(0.3 / 0.1); // 2.9999999999999996console.log(division(0.3, 0.1)); // 3console.log(1.21 / 1.1); // 1.0999999999999999console.log(division(1.21, 1.1)); // 1.1console.log(1.02 / 1.1); // 0.9272727272727272console.log(division(1.02, 1.1)); // 数字 9272727272727272 超限,请注意风险!0.9272727272727272console.log(1207.41 / 100); // 12.074100000000001console.log(division(1207.41, 100)); // 12.0741/** * @name 按指定位数四舍五入 * @param {Number} number 需要取舍的数字 * @param {Number} ratio 精确到多少位小数 */const round = (number, ratio) => { const baseNum = Math.pow(10, ratio); return division(Math.round(multiplication(number, baseNum)), baseNum); // Math.round() 进行小数点后一位四舍五入是否有问题,如果有,请证明出来 // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/round}console.log('------四舍五入:');console.log(0.105.toFixed(2)); // '0.10'console.log(round(0.105, 2)); // 0.11console.log(1.335.toFixed(2)); // '1.33'console.log(round(1.335, 2)); // 1.34console.log(-round(2.5, 0)); // -3console.log(-round(20.51, 0)); // -21

在这份代码中,我们先通过石锤乘法的计算,通过将数字转成整数进行计算,从而产生了 安全 的数据。

JavaScript 整数运算会不会出问题呢?

乘法计算好后,假设乘法已经没问题,然后通过乘法推出 加法、减法 以及 除法 这三则运算。

最后,通过乘法和除法做出四舍五入的规则。

JavaScript Math.round() 产生的数字会不会有问题呢、

这样,我们就搞定了两个数的加减乘除和四舍五入(保留指定的长度),那么,里面会不会有问题呢?

如果有,请例举出来。

如果没有,那么你能不能依据上面两个数的加减乘除,实现三个数甚至多个数的加减乘除?

五 现成框架

返回目录

这么重要的计算,如果自己写的话你总会感觉惶惶不安,感觉充满着危机。

所以很多时候,我们可以使用大佬们写好的 JavaScript 计算库,因为这些问题大佬已经帮我们进行了大量的测试了,大大减少了我们手写存在的问题,所以我们可以调用别人写好的类库。

下面推荐几款不错的类库:

  • Math.js。

Math.js 是一个用于 JavaScript 和 Node.js 的扩展数学库。

它具有支持符号计算的灵活表达式解析器,大量内置函数和常量,并提供了集成的解决方案来处理不同的数据类型,例如数字,大数,复数,分数,单位和矩阵。

强大且易于使用。

  • decimal.js

JavaScript 的任意精度的十进制类型。

  • big.js

一个小型,快速,易于使用的库,用于任意精度的十进制算术运算。

  • bignumber.js

一个用于任意精度算术的 JavaScript 库。

最后的最后,值得一提的是:如果对数字的计算非常严格,或许你可以将参数丢给后端,让后端进行计算,再返回给你结果。

例如涉及到比特币、商城商品价格等的计算~


作者:jsliang
链接:https://juejin.im/post/5ddc7fa66fb9a07ad665b1f0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/552043.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java实现复制粘贴的计算器_软帝学院教你用java编写计算器(三)

教你用java编写计算器(三)import java.awt.Color;import java.awt.Dimension;import java.awt.event.ActionListener;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JMenu;import javax.swing.JMenuBar;import javax.swing.JMenuItem;import javax…

php 公众号验证回调方法_如何进行公众号文章收集 两种收集方法详解

大家都知道优质的公众号吸引用户最关键的就是要优质的文章&#xff0c;所以会有专人负责进行公众号文章收集工作&#xff0c;下面我们跟随拓途数据一起来了解一下如何进行公众号文章收集的相关资料吧。 如何进行公众号文章收集方案一&#xff1a;基于搜狗入口 在网上能搜索到的…

mysql保存一个文件怎么打开_悄悄告诉你,MySQL 通过SQL语句导出到Excel的方法-sql文件怎么打开...

执行SQL语句select fullname,time,endtime,closed from chat_archive into outfile c:/xxx.xls注意&#xff1a;因为office默认的是gb2312编码&#xff0c;服务器端生成的很有可能是utf-8编码&#xff0c;此时有几种选择1、把查询出来的结果转换为GB2312格式(字段fullname)sele…

gerber文件怎么导贴片坐标_SMT贴片机在线编程调试

SMT贴片机分为离线编程和在线编程调试&#xff0c;在线编程调试就是在 SMT 贴片机上对离线编程的程序进行优化调试编辑。SMT 贴片机在线编程调试总体上就是两个步骤&#xff0c;一个是离线编程的程序进行编程&#xff0c;然后就是总体检查并备份到贴片机电脑内。一、在 SMT 贴片…

java销售额查询_用JSP+JavaBean开发模式实现一个销售额的查询

数据库使用mysql&#xff0c;如下&#xff1a;vo包的Sales类&#xff1a;package com.vo;public class Sales {public String salestime;public float salesnum;public String getSalestime() {return salestime;}public void setSalestime(String salestime) {this.salestime …

spss数据_怎么建立SPSS数据库、录入数据?

怎么把收集的问卷、测试数据等原始资料转变为“SPSS数据库”&#xff1f;数据包括离散&#xff08;单选题、多选题等&#xff09;、连续&#xff08;年龄、身高、肺活量、人数等&#xff09;两类。以下面四个题目为例&#xff0c;介绍采用SPSS建立数据库的方法&#xff1a;A2.学…

php进度条如何计算,投票最后显示进度条的百分比怎么算

我自己写的一个投票结果显示&#xff0c;其中设定票数最多的那个进度条为100&#xff05;。public class voteresult : System.Web.UI.Page{protected System.Web.UI.HtmlControls.HtmlTableCell td_vote;protected System.Web.UI.HtmlControls.HtmlTable tab_result;private v…

函数的返回值可以不用赋值_C语言学习|函数的应用《一》

C语言为程序的结构提供了函数和模块一、函数的定义与使用《编程之道》中写道&#xff1a;“一个程序应该是灵活自由的、它的子过程就像串在一根线子上的珍珠。”子过程在C语言中被称为”函数“。程序的执行从主函数开始&#xff0c;往复、循环、迭代地调用一个又一个函数。函数…

php的yii框架配置,php配置yii框架_PHP教程

个人爱好&#xff0c;研究了下php的yii框架。首先&#xff0c;研究yii框架的前提是下载php的一键安装和zend studio.. php的一键安装给出连接如下http://www.download3k.com/Install-XAMPP.html 。。。zend studion的链接如下http://www.zend.com/en/products/studio/downloads…

蚂蚁庄园 php源码,蚂蚁庄园五体投地

蚂蚁庄园五体投地&#xff0c;蚂蚁庄园小课堂的题目你答对了吗&#xff1f;今天题目有点难&#xff0c;大家可能不清楚&#xff0c;人们常常会对极其敬重的人五体投地&#xff0c;五体投地的五体指的是什么意思呢&#xff1f;“五体”又称“五轮”&#xff0c;指双肘、双膝和额…

电脑端二维码识别工具_电脑端自签工具更新,多功能软件一键签名

因为苹果后台的调整&#xff0c;电脑端的自签工具 Cydia Impactor 一直无法使用&#xff0c;如今虽然没有等到大胡子对 Cydia Impactor 适配更新&#xff0c;却等到了全新的替代工具。先说下为什么 Cydia Impactor 为什么让那么多人惦记&#xff0c;虽然对于不越狱安装越狱工具…

python列表是顺序表还是链表_顺序表与链表

Python中的顺序表 Python中的list和tuple两种类型采用了顺序表的实现技术&#xff0c;具有前面讨论的顺序表的所有性质。 tuple是不可变类型&#xff0c;即不变的顺序表&#xff0c;因此不支持改变其内部状态的任何操作&#xff0c;而其他方面&#xff0c;则与list的性质类似。…

js for foreach 快慢_js基本搜索算法实现与170万条数据下的性能测试

前言今天让我们来继续聊一聊js算法&#xff0c;通过接下来的讲解&#xff0c;我们可以了解到搜索算法的基本实现以及各种实现方法的性能&#xff0c;进而发现for循环&#xff0c;forEach&#xff0c;While的性能差异&#xff0c;我们还会了解到如何通过web worker做算法分片&am…

iterm php,iTerm2笔记

本文是 iTerm2 的使用笔记&#xff0c;不定期更新。1 注释说明对于 Preferences 的修改&#xff0c;> 表示需要切换选项卡&#xff0c;-> 表示在同一选项卡内2 参考3 杂301 如何随时随地一键调用 Quake-like iTerm2首先声明&#xff1a;由于 Mac OS 本身对窗体「最大化」…

matlab emd功率谱密度,【脑电信号分类】脑电信号提取PSD功率谱密度特征

脑电信号是一种非平稳的随机信号&#xff0c;一般而言随机信号的持续时间是无限长的&#xff0c;因此随机信号的总能量是无限的&#xff0c;而随机过程的任意一个样本函数都不满足绝对可积条件&#xff0c;所以其傅里叶变换不存在。不过&#xff0c;尽管随机信号的总能量是无限…

组装服务器配置清单_2020年组装电脑配置清单列表

随着电脑技术的不断革新&#xff0c;越来越多的家庭都有各式各样的电子设备。而电脑现在基本上是家家都有的物品&#xff0c;可是在购买电脑的时候新手小白需要注意那些事项呢&#xff1f;今天我们就给告诉小白如何组装电脑以小白组装电脑配置清单。1、购买电脑&#xff0c;您首…

oracle 关于归档的视图,oracle 与归档日志相关的几个视图

归档日志占据的数据库举足轻重的位置&#xff0c;以下系统视图来了解归档日志情况V$ARCHIVEV$ARCHIVED_LOG 已归档日志详单V$ARCHIVE_GAP 归档日志丢失V$ARCHIVE_PROCESSES 归档进程信息V$ARCHIVE_DEST 查看备份路径情况V$ARCHIVE_DEST_STATUSv$recovery_f…

count数据库优化oracle,迷惑性SQL性能问题排查与优化

&#xff1a;数据科学、人工智能从业者的在线大学。数据科学(Python/R/Julia)数据分析、机器学习、深度学习作者简介戴秋龙&#xff0c;拥有超过八年的电信、保险、税务行业核心系统ORACLE数据库优化&#xff0c;优化经验&#xff0c;具备丰富的行业服务背景。对Oracle数据库有…

swiper.js pagination指示点不变_电缆故障点的四种实用测定方法

一、电缆故障的种类与判断无论是高压电缆或低压电缆&#xff0c;在施工安装、运行过程中经常因短路、过负荷运行、绝缘老化或外力作用等原因造成故障。电缆故障可概括为接地、短路、断线三类&#xff0c;其故障类型主要有以下几方面&#xff1a;①三芯电缆一芯或两芯接地。②二…

wampserver php扩展openssl 不可用_PHP基础及WAMP集成基础

PHP语言编写的基础框架 PHP语言的编写框架与HTML5的一致&#xff0c;都是一下框架&#xff1a;<!DOCTYPE html> <html><head></head><body> </body> </html>PHP的主要表达语句在body里面&#xff0c;主题内容在<?p ?>标签中…