JS toFixed()填坑

问题

1、在JS中四舍五入的函数 toFixed(n) , n为要保留的小数位数。 n为0~20,当n超过20的时候,JS会出错。

2、当截取小数点前一位是0时,输出结果可能会有所不同。

1
2
3
4
5
var a = 0.145;

a.toFixed(2);

//结果为0.14;

靠谱分析

学会查标准
摘自ECMA-262 5.1 Edition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
15.7.4.5 Number.prototype.toFixed (fractionDigits)

Return a String containing this Number value represented in decimal fixed-point notation with fractionDigits
digits after the decimal point. If fractionDigits is undefined, 0 is assumed. Specifically, perform the following
steps:
1. Let f be ToInteger(fractionDigits). (If fractionDigits is undefined, this step produces the value 0).
2. If f < 0 or f > 20, throw a RangeError exception.
3. Let x be this Number value.
4. If x is NaN, return the String "NaN".
5. Let s be the empty String.
6. If x < 0, then
a. Let s be "-".
b. Let x = –x.
7. If x >= 10^21, then
a. Let m = ToString(x).
8. Else, x < 10^21
a. Let n be an integer for which the exact mathematical value of n / 10^f – x is as close to zero as possible. If there are two such n, pick the larger n.
b. If n = 0, let m be the String "0". Otherwise, let m be the String consisting of the digits of the decimal representation of n (in order, with no leading zeroes).
c. If f != 0, then
i. Let k be the number of characters in m.
ii. If k ≤ f, then
1. Let z be the String consisting of f+1–k occurrences of the character '0'.
2. Let m be the concatenation of Strings z and m.
3. Let k = f + 1.
iii. Let a be the first k–f characters of m, and let b be the remaining f characters of m.
iv. Let m be the concatenation of the three Strings a, ".", and b.
9. Return the concatenation of the Strings s and m.

根据上述步骤

9.955.toFixed(2)的过程为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. f = 2
3. x = 9.955
5. s = ''
8.

a. n = 996
原因
当 n = 995 时 n / 10^f – x = -0.005000000000000782
当 n = 996 时 n / 10^f – x = 0.005000000000000782
同样靠近0, 选择大的那个
b. m = '996'
c.
i. k = 3
iii. a = '9', b = '96'
iv. m = a + '.' + b = '9.96'
9. Return s + m = '9.96'

9.655.toFixed(2)的过程为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1. f = 2
3. x = 9.655
5. s = ''
8.

a. n = 965
原因
当 n = 965 时 n / 10^f – x = -0.004999999999999005
当 n = 966 时 n / 10^f – x = 0.005000000000000782
965更靠近0
b. m = '965'
c.
i. k = 3
iii. a = '9', b = '65'
iv. m = a + '.' + b = '9.65'
9. Return s + m = '9.65'

简而言之其实还是浮点误差的锅

修复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function toFixed(number, decimal) {
decimal = decimal || 0;
var s = String(number);
var decimalIndex = s.indexOf('.');
if (decimalIndex < 0) {
var fraction = '';
for (var i = 0; i < decimal; i++) {
fraction += '0';
}
return s + '.' + fraction;
}
var numDigits = s.length - 1 - decimalIndex;
if (numDigits <= decimal) {
var fraction = '';
for (var i = 0; i < decimal - numDigits; i++) {
fraction += '0';
}
return s + fraction;
}
var digits = s.split('');
var pos = decimalIndex + decimal;
var roundDigit = digits[pos + 1];
if (roundDigit > 4) {
//跳过小数点
if (pos == decimalIndex) {
--pos;
}
digits[pos] = Number(digits[pos] || 0) + 1;
//循环进位
while (digits[pos] == 10) {
digits[pos] = 0;
--pos;
if (pos == decimalIndex) {
--pos;
}
digits[pos] = Number(digits[pos] || 0) + 1;
}
}
//避免包含末尾的.符号
if (decimal == 0) {
decimal--;
}
return digits.slice(0, decimalIndex + decimal + 1).join('');
}

实验:

1
2
3
4
5
6
toFixed(0.0056,2)
//'0.01'
toFixed(298561.7249999999999999,2)
//'298561.73'
toFixed(298561.7249999999999999,3)
//'298561.725'