车间的工人在生产出来产品后,须要完成初步的自检,并经过手机上报。在实际生产中,用户(工人)不方便进行数值的输入,于是表单中的一些项设计成 picker 模式以供选取数值。数值的取值范围,根据容许的偏差范围生成。示例以下:javascript
示例一 // 偏差 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
由以上例子能够得知,取值范围的计算是根据偏差范围的最小值的最小位数做为基数,从最小值(包含)逐步累加至最大值(包含)。java
首先,根据最小值获取小数位的个数。数组
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);
最后,根据偏差范围和基数,循环生成取值范围。prototype
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 这样简单的数字。
经过一个简单的例子来验证上面这段话。code
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
由此可知,前面计算取值范围的方法中,遇到了相似的问题。ip
var max = 0.06; var value = 0.05; console.log(value + 0.01 === max); //false
由于从 0.05 + 0.01
的结果并不等于 0.06
,因此循环只执行了 5 次(而非预期的 6 次)就结束了。ci
在尝试修复此问题以前,先把前面的代码封装一下。
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; }
注意事项:
JavaScript 中浮点型精度的偏差,是很是基础可是却又常常不被重视的问题。文中分享的方案,足以覆盖项目中的全部状况,但若是用在其它地方或项目中,在一些极端状况下可能会有问题。