记一次 JavaScript 浮点型数字误差引发的问题
需求
车间的工人在生产出来产品后,需要完成初步的自检,并通过手机上报。在实际生产中,用户(工人)不方便进行数值的输入,因而表单中的一些项设计成 picker 模式以供选取数值。数值的取值范围,根据允许的误差范围生成。示例如下:
示例一
// 误差
0.01mm ~ 0.06mm
// picker 展示的数值
0.01, 0.02, 0.03, 0.04, 0.05, 0.06
示例二
// 误差
15mm ~ 18mm
// picker 展示的数值
15, 16, 17, 18
示例三
// 误差
1.05mm ~ 1.1mm
// picker 展示的数值
1.05, 1.06, 1.07, 1.08, 1.09, 1.1
由以上例子可以得知,取值范围的计算是根据误差范围的最小值的最小位数作为基数,从最小值(包含)逐步累加至最大值(包含)。
实现
首先,根据最小值获取小数位的个数。
function getDecimalPlace(value) {
// 先将 Number 转换为 String
value = value + '';
// 查找小数点的位置,加 1 是为了方便计算小数点的位数。
var floatIndex = value.indexOf('.') + 1;
// 返回的结果是小数位的个数
return floatIndex ? value.length - floatIndex : 0;
}
用几个实际的数值,测试一下这个方法。
getDecimalPlace(1); //0
getDecimalPlace('1.0'); //0
getDecimalPlace('1.5'); //1
getDecimalPlace('1.23'); //2
然后,根据小数位的个数计算累加的基数。
var min = 0.01;
var max = 0.06;
var decimal = getDecimalPlace(min);
// 基数
var radixValue = Math.pow(10, -decimal);
最后,根据误差范围和基数,循环生成取值范围。
var value = min;
var range = [];
for (; value <= max; value += radixValue) {
range.push(value);
}
console.log(range);
//结果:[0.01,0.02,0.03,0.04,0.05]
从结果来看,好像哪里不对。没错,最大值 0.06 没有出现在取值范围中。
问题
JavaScript 采用了 IEEE-754 浮点数表示法。这是一种二进制表示法,二进制浮点数表示法并不能精确表示类似 0.1 这样简单的数字。
通过一个简单的例子来验证上面这段话。
var num1 = 0.2 - 0.1;
var num2 = 0.3 - 0.2;
console.log(num1 === num2); //false
console.log(num1 === 0.1); //true
console.log(num2 === 0.1); //false
由此可知,前面计算取值范围的方法中,遇到了类似的问题。
var max = 0.06;
var value = 0.05;
console.log(value + 0.01 === max); //false
因为从 0.05 + 0.01 的结果并不等于 0.06,所以循环只执行了 5 次(而非预期的 6 次)就结束了。
在尝试修复此问题之前,先把前面的代码封装一下。
function getRange(min, max) {
var decimal = getDecimalPlace(min);
var radixValue = Math.pow(10, -decimal);
var value = min;
var range = [];
for (; value <= max; value += radixValue) {
range.push(value);
}
return range;
}
解决问题
最简单粗暴的办法,就是调整循环条件,在循环结束后再将最大值添加至数组。
function getRange(min, max) {
var decimal = getDecimalPlace(min);
var radixValue = Math.pow(10, -decimal);
var value = min;
var range = [];
for (; value < max; value += radixValue) {
range.push(value);
}
range.push(max);
return range;
}
再次使用之前的数据测试:
getRange(0.01, 0.06);
//结果:[0.01,0.02,0.03,0.04,0.05,0.06]
运行结果与预期一致,问题解决。
新的问题
然而,后续的测试中又出现了意外。
getRange(1.55, 1.65);
// 结果:[1.55,1.56,1.57,1.58,1.59,1.6,1.61,1.62,1.6300000000000001,1.6400000000000001,1.65]
1.6300000000000001 这样的数值,显然不是我们期望得到的。出现此现象,与之前的问题原因一致。
方案一
将参与计算的数值,先转换为整型,再进行计算。
function getRange(min, max) {
var decimal = getDecimalPlace(min);
var radixValue = Math.pow(10, -decimal);
var multi = Math.pow(10, decimal)
var value = min * multi;
var range = [];
for (; value < max * multi; value += radixValue * multi) {
range.push(value / multi);
}
range.push(max);
return range;
}
注意事项:
- 向数组中添加数值时,需要再除以倍数,得到最终的数值。
方案二
使用 toFixed() 方法,对浮点型进行格式化。
function getRange(min, max) {
var decimal = getDecimalPlace(min);
var radixValue = Math.pow(10, -decimal);
var value = min;
var range = [];
for (; value < max || +value.toFixed(decimal) === max; value += radixValue) {
range.push(+value.toFixed(decimal));
}
return range;
}
注意事项:
- toFixed() 方法返回的值是 String 类型,因此需要再转换为 Number 类型。
- 做了一点优化,调整循环条件后,移除了循环外 push() 最大值的语句。
最后
JavaScript 中浮点型精度的误差,是非常基础但是却又经常不被重视的问题。文中分享的方案,足以覆盖项目中的所有情况,但如果用在其它地方或项目中,在一些极端情况下可能会有问题。
参考资料
记一次 JavaScript 浮点型数字误差引发的问题的更多相关文章
- [WinForm]TextBox只能输入数字或者正浮点型数字
关键代码: /// <summary> /// 只能输入数字[KeyPress事件] /// </summary> /// <param name="textB ...
- JavaScript将数字转换为大写金额
用JavaScript将数字转换为大写金额,好了 0.0 To code! var digitUppercase = function(n) { var fraction = ['角', '分']; ...
- QDoubleSpinBox浮点型数字调节框
样式: import sys from PyQt5.QtWidgets import QApplication, QWidget, QDoubleSpinBox class Demo(QWidget) ...
- JavaScript 格式化数字、金额、千分位、保留几位小数、舍入舍去…
JavaScript 格式化数字.金额.千分位.保留几位小数.舍入舍去… 类库推荐 1. Numeral.js 一个用于格式化和操作数字的JavaScript库.数字可以被格式化为货币,百分比,时间, ...
- 前端 javascript 数据类型 数字
1.数字(Number) JavaScript中不区分整数值和浮点数值,JavaScript中所有数字均用浮点数值表示. 转换: parseInt(..) 将某值转换成数字,不成功则NaN pa ...
- JavaScript字符串&数字间转换
比较操作符的操作数可以是任意类型.然而,只有数字和字符串才能真正执行边角操作,因此那些不是数字和字符串的操作数都讲进行类型转换,类型转换规则如下: 如果操作数为对象,那么对象转换为原始值:如 ...
- JS 浮点型数字运算(转)
示例: var num1=3.3; var num2=7.17; var ret=parseFloat(num1)+parseFloat(num2); //ret的值为:10.469999999999 ...
- 你可能不知道的 JavaScript 中数字取整
网上方法很多,标题党一下,勿拍 ^_^!实际开发过程中经常遇到数字取整问题,所以这篇文章收集了一些方法,以备查询. 常用的直接取整方法 直接取整就是舍去小数部分. 1.parseInt() parse ...
- JavaScript非数字(中文)排序
直接上代码: var arr=[ {name:"张散步",age:"23",sports:"篮球",number:"231123& ...
随机推荐
- mongo常用语法
首先要能进入控制台,进不去自己解决. 基本操作: show users:显示用户 show dbs:显示数据库列表 use <db name> 切换/创建数据库 show collecti ...
- Nginx总结(三)基于端口的虚拟主机配置
前面讲了如何配置基于IP的虚拟主机,大家可以去这里看看nginx系列文章:https://www.cnblogs.com/zhangweizhong/category/1529997.html 今天就 ...
- 【阿里云IoT+YF3300】4.Alink物模型之事件触发
名词解释:设备的功能模型之一,设备运行时的事件,事件一般包含需要被外部感知和处理的通知信息,可包含多个输出参数.如,某项任务完成的信息,或者设备发生故障或告警时的温度等,事件可以被订阅和推送. 在工控 ...
- python 17 内置模块
目录 1. 序列化模块 1.1 json 模块 1.2 pickle 模块 2. os 模块 3. sys 模块 4. hashlib 加密.摘要 4.1 加密 4.2 加盐 4.3 文件一致性校验 ...
- 水果机抽奖(CocosCreator)
推荐阅读: 我的CSDN 我的博客园 QQ群:704621321 一.前言 在前面给大家分享了大转盘的抽奖方式,这是现在游戏使用较多的一种抽奖方式,今天给大家介绍另一抽奖方式--水果 ...
- BZOJ2655 Calc - dp 拉格朗日插值法
BZOJ2655 Calc 参考 题意: 给定n,m,mod,问在对mod取模的背景下,从[1,m]中选出n个数相乘可以得到的总和为多少. 思路: 首先可以发现dp方程 ,假定dp[m][n]表示从[ ...
- CodeForces 1082 G Petya and Graph 最大权闭合子图。
题目传送门 题意:现在有一个图,选择一条边,会把边的2个顶点也选起来,最后会的到一个边的集合 和一个点的集合 , 求边的集合 - 点的集合最大是多少. 题解:裸的最大权闭合子图. 代码: #inclu ...
- hdu 4614 Vases and Flowers(线段树)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4614 题意: 给你N个花瓶,编号是0 到 N - 1 ,初始状态花瓶是空的,每个花瓶最多插一朵花. ...
- 解决go get下载包失败问题
由于某些不可抗力的原因,国内使用go get命令安装包时会经常会出现timeout的问题.本文介绍几个常用的解决办法. 从github克隆 golang在github上建立了一个镜像库,如https: ...
- Failed to read artifact descriptor for xxx:jar:版本号
解决步骤: 1.先删除对应的本地仓库下的文件夹,比如org.apache.maven.plugins:maven-resource,删掉plugins文件夹 2.项目右键-->Maven--&g ...