JavaScript remember a problem caused by the floating-point digital error

demand

Shop workers after production of products, complete the initial self-test, and reported by phone. In actual production, the user (worker) inconvenient to input values, and thus form some items designed picker mode for selecting values. Range of values, generated in accordance with the allowable error range. Examples are as follows:

示例一
// 误差
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

Can be known from the above example, the range is calculated according to the minimum number of digits of the minimum value of the error range as the base, and gradually accumulated from the minimum value (contained) to a maximum (inclusive).

achieve

First, a minimum value according to the number of decimal places.

function getDecimalPlace(value) {
    // 先将 Number 转换为 String
    value = value + '';
    // 查找小数点的位置,加 1 是为了方便计算小数点的位数。
    var floatIndex = value.indexOf('.') + 1;
    // 返回的结果是小数位的个数
    return floatIndex ? value.length - floatIndex : 0;
}

With several practical value, this test method.

getDecimalPlace(1); //0
getDecimalPlace('1.0'); //0
getDecimalPlace('1.5'); //1
getDecimalPlace('1.23'); //2

Then, the accumulated number calculation base decimal places.

var min = 0.01;
var max = 0.06;

var decimal = getDecimalPlace(min);
// 基数
var radixValue = Math.pow(10, -decimal);

Finally, based on the error range and base, generates cycle range.

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]

From the results, where it seems wrong. Yes, the maximum value of 0.06 does not appear in the value range.

problem

JavaScript uses the IEEE-754 floating-point representation. This is a binary notation, a binary floating-point representation and the like can not be accurately expressed as simple numbers 0.1.

By a simple example to verify the above paragraph.

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

It can be seen, the method of calculation of the range in front, encountered a similar problem.

var max = 0.06;
var value = 0.05;
console.log(value + 0.01 === max); //false

Because the 0.05 + 0.01results are not equal 0.06, the loop is performed only five times (instead of the expected 6 times) is over.

Before attempting to fix the problem, look at the front of the first package of code.

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;
}

Solve the problem

The most simple and crude way is to adjust cycling conditions, the maximum value is added to the array after the end of the cycle.

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;
}

Before using the test data again:

getRange(0.01, 0.06);
//结果:[0.01,0.02,0.03,0.04,0.05,0.06]

Operating results in line with expectations, the problem is solved.

New problems

However, follow-up tests and there was an accident.

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.6300000000000001This value is obviously not what we expect to receive. This behavior is consistent with the cause of the problem before.

Option One

Involved in the calculation of the value is first converted to an integer, and then calculated.

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;
}

Precautions:

  • When adding values ​​to the array, then divided by multiples need to obtain the final value.

Option II

Using the toFixed()method of floating-point format.

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;
}

Precautions:

  • toFixed method returns the value () of type String, and therefore need to be converted to Type Number.
  • Do a little optimization, adjusted loop condition, remove the outer loop push () the maximum sentence.

At last

JavaScript errors in floating-point accuracy, but is very basic but often not taken seriously in question. Article sharing program, enough to cover all cases in the project, but if used in other places or projects, in some extreme cases there may be a problem.

Reference Documents

Guess you like

Origin www.cnblogs.com/xiaoyucoding/p/11976237.html