我希望最多保留两位小数,但只有必要时才这样做。
输入:
10
1.7777777
9.1
输出:
10
1.78
9.1
我该如何在JavaScript中实现这个?
我希望最多保留两位小数,但只有必要时才这样做。
输入:
10
1.7777777
9.1
输出:
10
1.78
9.1
我该如何在JavaScript中实现这个?
使用Math.round()
:
Math.round(num * 100) / 100
或者更具体地说,为了确保像1.005这样的数字正确舍入,请使用Number.EPSILON:
Math.round((num + Number.EPSILON) * 100) / 100
如果值是文本类型:
parseFloat("123.456").toFixed(2);
如果值是一个数字:
var numb = 123.23454;
numb = numb.toFixed(2);
存在一个缺点,像1.5这样的值将会输出"1.50"。@minitech建议的修复方法:
var numb = 1.5;
numb = +numb.toFixed(2);
// Note the plus sign that drops any "extra" zeroes at the end.
// It changes the result (which is a string) into a number again (think "0 + foo"),
// which means that it uses only as many digits as necessary.
看起来像是Math.round
是更好的解决方案。但它并不是! 在某些情况下,它将不能正确舍入:
Math.round(1.005 * 100)/100 // Returns 1 instead of expected 1.01!
在某些情况下(已在Chrome v.55.0.2883.87中测试),toFixed()也会不正确地进行舍入!
示例:
parseFloat("1.555").toFixed(2); // Returns 1.55 instead of 1.56.
parseFloat("1.5550").toFixed(2); // Returns 1.55 instead of 1.56.
// However, it will return correct result if you round 1.5551.
parseFloat("1.5551").toFixed(2); // Returns 1.56 as expected.
1.3555.toFixed(3) // Returns 1.355 instead of expected 1.356.
// However, it will return correct result if you round 1.35551.
1.35551.toFixed(2); // Returns 1.36 as expected.
我猜测,这是因为1.555实际上在后台是类似于浮点数1.55499994的东西。
解决方案1是使用带有所需舍入算法的脚本,例如:function roundNumber(num, scale) {
if(!("" + num).includes("e")) {
return +(Math.round(num + "e+" + scale) + "e-" + scale);
} else {
var arr = ("" + num).split("e");
var sig = ""
if(+arr[1] + scale > 0) {
sig = "+";
}
return +(Math.round(+arr[0] + "e" + sig + (+arr[1] + scale)) + "e-" + scale);
}
}
这个也可以在Plunker上找到。
注意: 这不是适用于所有人的通用解决方案。有几种不同的舍入算法,你的实现可能会有所不同,这取决于你的需求。还请参阅Rounding(舍入)。
解决方案2是避免前端计算并从后端服务器提取舍入后的值。
另一个可能的解决方案,但也不是万无一失。
Math.round((num + Number.EPSILON) * 100) / 100
在某些情况下,当你将数字如1.3549999999999998四舍五入时,会返回一个错误的结果。它应该是1.35,但实际结果为1.36。
roundNumberV2
中,有一个条件语句if (Math.pow(0.1, scale) > num) { return 0; }
,请问这个条件语句的目的是什么? - Mei LieMath.round()
速度要快得多。https://jsbin.com/kikocecemu/edit?js,output - Mattvar a = parseFloat(1/3).toFixed(2);
的操作,当你执行 var c = a + someNumber;
时,它不会像你期望的那样工作。它会把新的 a
当作字符串加到数字 someNumber
上。所以你可能需要这样做:var c = eval(a) + someNumber;
。 - vapcguyeval(a)
,而应该使用 Number(a)
、parseFloat(a)
(实际上两者的行为相同 https://dev59.com/bWct5IYBdhLWcg3wk-Rq#11988612),甚至可以只用 +a
。我更喜欢使用 Number(a)
。 - Simon_Weaverfunction roundToTwo(num) {
return +(Math.round(num + "e+2") + "e-2");
}
console.log('1.005 => ', roundToTwo(1.005));
console.log('10 => ', roundToTwo(10));
console.log('1.7777777 => ', roundToTwo(1.7777777));
console.log('9.1 => ', roundToTwo(9.1));
console.log('1234.5678 => ', roundToTwo(1234.5678));
console.log('1.3549999999999998 => ', roundToTwo(1.3549999999999998));
console.log('10.075 => ', roundToTwo(10.075));
+(val)
是使用 Number(val)
进行强制类型转换的等效方式。将 "e-2" 与数字拼接导致生成一个字符串,需要将其转换回数字。 - Jack-2.9e-7
,那么+(Math.round(num + "e+2") + "e-2")
会返回NaN
,这不是期望的结果。至少在Chrome 101上是如此。 - ZuabiMarkG的回答是正确的。以下是任意小数位数的通用扩展。
Number.prototype.round = function(places) {
return +(Math.round(this + "e+" + places) + "e-" + places);
}
使用方法:
var n = 1.7777;
n.round(2); // 1.78
单元测试:
it.only('should round floats to 2 places', function() {
var cases = [
{ n: 10, e: 10, p:2 },
{ n: 1.7777, e: 1.78, p:2 },
{ n: 1.005, e: 1.01, p:2 },
{ n: 1.005, e: 1, p:0 },
{ n: 1.77777, e: 1.8, p:1 }
]
cases.forEach(function(testCase) {
var r = testCase.n.round(testCase.p);
assert.equal(r, testCase.e, 'didn\'t get right number');
});
})
prototype
扩展)版本(ES6)易于阅读和直观:round = (num, precision) => Number(Math.round(num + "e+" + precision) + "e-" + precision);
- Dut A.function round(val, decimals) {
return +(Math.round(+(val.toFixed(decimals) + "e+" + decimals)) + "e-" + decimals);
}
- Perround(num * p) / p
天真的实现方式function naiveRound(num, decimalPlaces = 0) {
var p = Math.pow(10, decimalPlaces);
return Math.round(num * p) / p;
}
console.log( naiveRound(1.245, 2) ); // 1.25 correct (rounded as expected)
console.log( naiveRound(1.255, 2) ); // 1.25 incorrect (should be 1.26)
// testing edge cases
console.log( naiveRound(1.005, 2) ); // 1 incorrect (should be 1.01)
console.log( naiveRound(2.175, 2) ); // 2.17 incorrect (should be 2.18)
console.log( naiveRound(5.015, 2) ); // 5.01 incorrect (should be 5.02)
/**
* Round half up ('round half towards positive infinity')
* Negative numbers round differently than positive numbers.
*/
function round(num, decimalPlaces = 0) {
num = Math.round(num + "e" + decimalPlaces);
return Number(num + "e" + -decimalPlaces);
}
// test rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // 0
// testing edge cases
console.log( round(1.005, 2) ); // 1.01
console.log( round(2.175, 2) ); // 2.18
console.log( round(5.015, 2) ); // 5.02
console.log( round(-1.005, 2) ); // -1
console.log( round(-2.175, 2) ); // -2.17
console.log( round(-5.015, 2) ); // -5.01
如果您想要在对负数进行四舍五入时得到通常的行为,您需要在调用Math.round()之前将负数转换为正数,然后在返回之前将它们转换回负数。
// Round half away from zero
function round(num, decimalPlaces = 0) {
if (num < 0)
return -round(-num, decimalPlaces);
num = Math.round(num + "e" + decimalPlaces);
return Number(num + "e" + -decimalPlaces);
}
近似四舍五入
为了修正前面的naiveRound
示例中出现的四舍五入问题,我们可以定义一个自定义的四舍五入函数,该函数执行一个“接近相等”测试,以确定一个小数值是否与中点值足够接近,从而使其受到中点舍入的影响。
// round half away from zero
function round(num, decimalPlaces = 0) {
if (num < 0)
return -round(-num, decimalPlaces);
var p = Math.pow(10, decimalPlaces);
var n = num * p;
var f = n - Math.floor(n);
var e = Number.EPSILON * n;
// Determine whether this fraction is a midpoint value.
return (f >= .5 - e) ? Math.ceil(n) / p : Math.floor(n) / p;
}
// test rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // -1
// testing edge cases
console.log( round(1.005, 2) ); // 1.01
console.log( round(2.175, 2) ); // 2.18
console.log( round(5.015, 2) ); // 5.02
console.log( round(-1.005, 2) ); // -1.01
console.log( round(-2.175, 2) ); // -2.18
console.log( round(-5.015, 2) ); // -5.02
Number.EPSILON
有一种纯数学技术可以执行最接近舍入(使用"远离零的四舍五入"),在调用舍入函数之前应用epsilon修正。10 ** n
时发生的二进制舍入误差。
/**
* Round half away from zero ('commercial' rounding)
* Uses correction to offset floating-point inaccuracies.
* Works symmetrically for positive and negative numbers.
*/
function round(num, decimalPlaces = 0) {
var p = Math.pow(10, decimalPlaces);
var n = (num * p) * (1 + Number.EPSILON);
return Math.round(n) / p;
}
// rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // -1
// testing edge cases
console.log( round(1.005, 2) ); // 1.01
console.log( round(2.175, 2) ); // 2.18
console.log( round(5.015, 2) ); // 5.02
console.log( round(-1.005, 2) ); // -1.01
console.log( round(-2.175, 2) ); // -2.18
console.log( round(-5.015, 2) ); // -5.02
501.49999999999994
)将被修正为501.50000000000006
,这将四舍五入为502,最终结果为5.02。toPrecision()
方法来消除中间计算中的浮点舍入误差。简单地说,我们将结果四舍五入到15个有效数字,以消除第16位有效数字的舍入误差。这种将结果预先四舍五入到有效数字的技术也被PHP 7的round
函数所使用。501.49999999999994
,首先会被四舍五入到15个有效数字,变成501.500000000000
,然后再次四舍五入到502,最终结果为5.02。
// Round half away from zero
function round(num, decimalPlaces = 0) {
if (num < 0)
return -round(-num, decimalPlaces);
var p = Math.pow(10, decimalPlaces);
var n = (num * p).toPrecision(15);
return Math.round(n) / p;
}
// rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // -1
// testing edge cases
console.log( round(1.005, 2) ); // 1.01
console.log( round(2.175, 2) ); // 2.18
console.log( round(5.015, 2) ); // 5.02
console.log( round(-1.005, 2) ); // -1.01
console.log( round(-2.175, 2) ); // -2.18
console.log( round(-5.015, 2) ); // -5.02
// Round half away from zero
function round(num, decimalPlaces = 0) {
return new Decimal(num).toDecimalPlaces(decimalPlaces).toNumber();
}
// rounding of half
console.log( round(0.5) ); // 1
console.log( round(-0.5) ); // -1
// testing edge cases
console.log( round(1.005, 2) ); // 1.01
console.log( round(2.175, 2) ); // 2.18
console.log( round(5.015, 2) ); // 5.02
console.log( round(-1.005, 2) ); // -1.01
console.log( round(-2.175, 2) ); // -2.18
console.log( round(-5.015, 2) ); // -5.02
<script src="https://cdnjs.cloudflare.com/ajax/libs/decimal.js/10.2.1/decimal.js" integrity="sha512-GKse2KVGCCMVBn4riigHjXE8j5hCxYLPXDw8AvcjUtrt+a9TbZFtIKGdArXwYOlZvdmkhQLWQ46ZE3Q1RIa7uQ==" crossorigin="anonymous"></script>
解决方案1:指数表示的字符串
受到KFish在这里提供的解决方案的启发:https://dev59.com/Smgt5IYBdhLWcg3wwwO-#55521592
这是一个简单易用的解决方案,可以对特定小数位数进行准确的四舍五入、向下取整和向上取整,而无需添加整个库。它将浮点数视为十进制数,通过修复二进制舍入问题来避免意外结果:例如,floor((0.1+0.7)*10)将返回预期结果8。
数字将四舍五入到特定的小数位数。指定负精度将会四舍五入到小数点左侧的任意位数。
// Solution 1
var DecimalPrecision = (function() {
if (Math.trunc === undefined) {
Math.trunc = function(v) {
return v < 0 ? Math.ceil(v) : Math.floor(v);
};
}
var decimalAdjust = function myself(type, num, decimalPlaces) {
if (type === 'round' && num < 0)
return -myself(type, -num, decimalPlaces);
var shift = function(value, exponent) {
value = (value + 'e').split('e');
return +(value[0] + 'e' + (+value[1] + (exponent || 0)));
};
var n = shift(num, +decimalPlaces);
return shift(Math[type](n), -decimalPlaces);
};
return {
// Decimal round (half away from zero)
round: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces);
},
// Decimal ceil
ceil: function(num, decimalPlaces) {
return decimalAdjust('ceil', num, decimalPlaces);
},
// Decimal floor
floor: function(num, decimalPlaces) {
return decimalAdjust('floor', num, decimalPlaces);
},
// Decimal trunc
trunc: function(num, decimalPlaces) {
return decimalAdjust('trunc', num, decimalPlaces);
},
// Format using fixed-point notation
toFixed: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
}
};
})();
// test rounding of half
console.log(DecimalPrecision.round(0.5)); // 1
console.log(DecimalPrecision.round(-0.5)); // -1
// testing very small numbers
console.log(DecimalPrecision.ceil(1e-8, 2) === 0.01);
console.log(DecimalPrecision.floor(1e-8, 2) === 0);
// testing simple cases
console.log(DecimalPrecision.round(5.12, 1) === 5.1);
console.log(DecimalPrecision.round(-5.12, 1) === -5.1);
console.log(DecimalPrecision.ceil(5.12, 1) === 5.2);
console.log(DecimalPrecision.ceil(-5.12, 1) === -5.1);
console.log(DecimalPrecision.floor(5.12, 1) === 5.1);
console.log(DecimalPrecision.floor(-5.12, 1) === -5.2);
console.log(DecimalPrecision.trunc(5.12, 1) === 5.1);
console.log(DecimalPrecision.trunc(-5.12, 1) === -5.1);
// testing edge cases for round
console.log(DecimalPrecision.round(1.005, 2) === 1.01);
console.log(DecimalPrecision.round(39.425, 2) === 39.43);
console.log(DecimalPrecision.round(-1.005, 2) === -1.01);
console.log(DecimalPrecision.round(-39.425, 2) === -39.43);
// testing edge cases for ceil
console.log(DecimalPrecision.ceil(9.13, 2) === 9.13);
console.log(DecimalPrecision.ceil(65.18, 2) === 65.18);
console.log(DecimalPrecision.ceil(-2.26, 2) === -2.26);
console.log(DecimalPrecision.ceil(-18.15, 2) === -18.15);
// testing edge cases for floor
console.log(DecimalPrecision.floor(2.26, 2) === 2.26);
console.log(DecimalPrecision.floor(18.15, 2) === 18.15);
console.log(DecimalPrecision.floor(-9.13, 2) === -9.13);
console.log(DecimalPrecision.floor(-65.18, 2) === -65.18);
// testing edge cases for trunc
console.log(DecimalPrecision.trunc(2.26, 2) === 2.26);
console.log(DecimalPrecision.trunc(18.15, 2) === 18.15);
console.log(DecimalPrecision.trunc(-2.26, 2) === -2.26);
console.log(DecimalPrecision.trunc(-18.15, 2) === -18.15);
// testing round to tens and hundreds
console.log(DecimalPrecision.round(1262.48, -1) === 1260);
console.log(DecimalPrecision.round(1262.48, -2) === 1300);
// testing toFixed()
console.log(DecimalPrecision.toFixed(1.005, 2) === "1.01");
// Solution 2
var DecimalPrecision2 = (function() {
if (Number.EPSILON === undefined) {
Number.EPSILON = Math.pow(2, -52);
}
if (Math.sign === undefined) {
Math.sign = function(x) {
return ((x > 0) - (x < 0)) || +x;
};
}
return {
// Decimal round (half away from zero)
round: function(num, decimalPlaces) {
var p = Math.pow(10, decimalPlaces || 0);
var n = (num * p) * (1 + Number.EPSILON);
return Math.round(n) / p;
},
// Decimal ceil
ceil: function(num, decimalPlaces) {
var p = Math.pow(10, decimalPlaces || 0);
var n = (num * p) * (1 - Math.sign(num) * Number.EPSILON);
return Math.ceil(n) / p;
},
// Decimal floor
floor: function(num, decimalPlaces) {
var p = Math.pow(10, decimalPlaces || 0);
var n = (num * p) * (1 + Math.sign(num) * Number.EPSILON);
return Math.floor(n) / p;
},
// Decimal trunc
trunc: function(num, decimalPlaces) {
return (num < 0 ? this.ceil : this.floor)(num, decimalPlaces);
},
// Format using fixed-point notation
toFixed: function(num, decimalPlaces) {
return this.round(num, decimalPlaces).toFixed(decimalPlaces);
}
};
})();
// test rounding of half
console.log(DecimalPrecision2.round(0.5)); // 1
console.log(DecimalPrecision2.round(-0.5)); // -1
// testing very small numbers
console.log(DecimalPrecision2.ceil(1e-8, 2) === 0.01);
console.log(DecimalPrecision2.floor(1e-8, 2) === 0);
// testing simple cases
console.log(DecimalPrecision2.round(5.12, 1) === 5.1);
console.log(DecimalPrecision2.round(-5.12, 1) === -5.1);
console.log(DecimalPrecision2.ceil(5.12, 1) === 5.2);
console.log(DecimalPrecision2.ceil(-5.12, 1) === -5.1);
console.log(DecimalPrecision2.floor(5.12, 1) === 5.1);
console.log(DecimalPrecision2.floor(-5.12, 1) === -5.2);
console.log(DecimalPrecision2.trunc(5.12, 1) === 5.1);
console.log(DecimalPrecision2.trunc(-5.12, 1) === -5.1);
// testing edge cases for round
console.log(DecimalPrecision2.round(1.005, 2) === 1.01);
console.log(DecimalPrecision2.round(39.425, 2) === 39.43);
console.log(DecimalPrecision2.round(-1.005, 2) === -1.01);
console.log(DecimalPrecision2.round(-39.425, 2) === -39.43);
// testing edge cases for ceil
console.log(DecimalPrecision2.ceil(9.13, 2) === 9.13);
console.log(DecimalPrecision2.ceil(65.18, 2) === 65.18);
console.log(DecimalPrecision2.ceil(-2.26, 2) === -2.26);
console.log(DecimalPrecision2.ceil(-18.15, 2) === -18.15);
// testing edge cases for floor
console.log(DecimalPrecision2.floor(2.26, 2) === 2.26);
console.log(DecimalPrecision2.floor(18.15, 2) === 18.15);
console.log(DecimalPrecision2.floor(-9.13, 2) === -9.13);
console.log(DecimalPrecision2.floor(-65.18, 2) === -65.18);
// testing edge cases for trunc
console.log(DecimalPrecision2.trunc(2.26, 2) === 2.26);
console.log(DecimalPrecision2.trunc(18.15, 2) === 18.15);
console.log(DecimalPrecision2.trunc(-2.26, 2) === -2.26);
console.log(DecimalPrecision2.trunc(-18.15, 2) === -18.15);
// testing round to tens and hundreds
console.log(DecimalPrecision2.round(1262.48, -1) === 1260);
console.log(DecimalPrecision2.round(1262.48, -2) === 1300);
// testing toFixed()
console.log(DecimalPrecision2.toFixed(1.005, 2) === "1.01");
toPrecision()
方法来消除浮点数的舍入误差。
// Solution 3
var DecimalPrecision3 = (function() {
if (Math.trunc === undefined) {
Math.trunc = function(v) {
return v < 0 ? Math.ceil(v) : Math.floor(v);
};
}
var powers = [
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
];
var intpow10 = function(power) {
/* Not in lookup table */
if (power < 0 || power > 22) {
return Math.pow(10, power);
}
return powers[power];
};
// Eliminate binary floating-point inaccuracies.
var stripError = function(num) {
if (Number.isInteger(num))
return num;
return parseFloat(num.toPrecision(15));
};
var decimalAdjust = function myself(type, num, decimalPlaces) {
if (type === 'round' && num < 0)
return -myself(type, -num, decimalPlaces);
var p = intpow10(decimalPlaces || 0);
var n = stripError(num * p);
return Math[type](n) / p;
};
return {
// Decimal round (half away from zero)
round: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces);
},
// Decimal ceil
ceil: function(num, decimalPlaces) {
return decimalAdjust('ceil', num, decimalPlaces);
},
// Decimal floor
floor: function(num, decimalPlaces) {
return decimalAdjust('floor', num, decimalPlaces);
},
// Decimal trunc
trunc: function(num, decimalPlaces) {
return decimalAdjust('trunc', num, decimalPlaces);
},
// Format using fixed-point notation
toFixed: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
}
};
})();
// test rounding of half
console.log(DecimalPrecision3.round(0.5)); // 1
console.log(DecimalPrecision3.round(-0.5)); // -1
// testing very small numbers
console.log(DecimalPrecision3.ceil(1e-8, 2) === 0.01);
console.log(DecimalPrecision3.floor(1e-8, 2) === 0);
// testing simple cases
console.log(DecimalPrecision3.round(5.12, 1) === 5.1);
console.log(DecimalPrecision3.round(-5.12, 1) === -5.1);
console.log(DecimalPrecision3.ceil(5.12, 1) === 5.2);
console.log(DecimalPrecision3.ceil(-5.12, 1) === -5.1);
console.log(DecimalPrecision3.floor(5.12, 1) === 5.1);
console.log(DecimalPrecision3.floor(-5.12, 1) === -5.2);
console.log(DecimalPrecision3.trunc(5.12, 1) === 5.1);
console.log(DecimalPrecision3.trunc(-5.12, 1) === -5.1);
// testing edge cases for round
console.log(DecimalPrecision3.round(1.005, 2) === 1.01);
console.log(DecimalPrecision3.round(39.425, 2) === 39.43);
console.log(DecimalPrecision3.round(-1.005, 2) === -1.01);
console.log(DecimalPrecision3.round(-39.425, 2) === -39.43);
// testing edge cases for ceil
console.log(DecimalPrecision3.ceil(9.13, 2) === 9.13);
console.log(DecimalPrecision3.ceil(65.18, 2) === 65.18);
console.log(DecimalPrecision3.ceil(-2.26, 2) === -2.26);
console.log(DecimalPrecision3.ceil(-18.15, 2) === -18.15);
// testing edge cases for floor
console.log(DecimalPrecision3.floor(2.26, 2) === 2.26);
console.log(DecimalPrecision3.floor(18.15, 2) === 18.15);
console.log(DecimalPrecision3.floor(-9.13, 2) === -9.13);
console.log(DecimalPrecision3.floor(-65.18, 2) === -65.18);
// testing edge cases for trunc
console.log(DecimalPrecision3.trunc(2.26, 2) === 2.26);
console.log(DecimalPrecision3.trunc(18.15, 2) === 18.15);
console.log(DecimalPrecision3.trunc(-2.26, 2) === -2.26);
console.log(DecimalPrecision3.trunc(-18.15, 2) === -18.15);
// testing round to tens and hundreds
console.log(DecimalPrecision3.round(1262.48, -1) === 1260);
console.log(DecimalPrecision3.round(1262.48, -2) === 1300);
// testing toFixed()
console.log(DecimalPrecision3.toFixed(1.005, 2) === "1.01");
// Solution 4
var DecimalPrecision4 = (function() {
if (Math.trunc === undefined) {
Math.trunc = function(v) {
return v < 0 ? Math.ceil(v) : Math.floor(v);
};
}
var powers = [
1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7,
1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
];
var intpow10 = function(power) {
/* Not in lookup table */
if (power < 0 || power > 22) {
return Math.pow(10, power);
}
return powers[power];
};
var toPrecision = function(num, significantDigits) {
// Return early for ±0, NaN and Infinity.
if (!num || !Number.isFinite(num))
return num;
// Compute shift of the decimal point (sf - leftSidedDigits).
var shift = significantDigits - 1 - Math.floor(Math.log10(Math.abs(num)));
// Return if rounding to the same or higher precision.
var decimalPlaces = 0;
for (var p = 1; num != Math.round(num * p) / p; p *= 10) decimalPlaces++;
if (shift >= decimalPlaces)
return num;
// Round to "shift" fractional digits
var scale = intpow10(Math.abs(shift));
return shift > 0 ?
Math.round(num * scale) / scale :
Math.round(num / scale) * scale;
};
// Eliminate binary floating-point inaccuracies.
var stripError = function(num) {
if (Number.isInteger(num))
return num;
return toPrecision(num, 15);
};
var decimalAdjust = function myself(type, num, decimalPlaces) {
if (type === 'round' && num < 0)
return -myself(type, -num, decimalPlaces);
var p = intpow10(decimalPlaces || 0);
var n = stripError(num * p);
return Math[type](n) / p;
};
return {
// Decimal round (half away from zero)
round: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces);
},
// Decimal ceil
ceil: function(num, decimalPlaces) {
return decimalAdjust('ceil', num, decimalPlaces);
},
// Decimal floor
floor: function(num, decimalPlaces) {
return decimalAdjust('floor', num, decimalPlaces);
},
// Decimal trunc
trunc: function(num, decimalPlaces) {
return decimalAdjust('trunc', num, decimalPlaces);
},
// Format using fixed-point notation
toFixed: function(num, decimalPlaces) {
return decimalAdjust('round', num, decimalPlaces).toFixed(decimalPlaces);
}
};
})();
// test rounding of half
console.log(DecimalPrecision4.round(0.5)); // 1
console.log(DecimalPrecision4.round(-0.5)); // -1
// testing very small numbers
console.log(DecimalPrecision4.ceil(1e-8, 2) === 0.01);
console.log(DecimalPrecision4.floor(1e-8, 2) === 0);
// testing simple cases
console.log(DecimalPrecision4.round(5.12, 1) === 5.1);
console.log(DecimalPrecision4.round(-5.12, 1) === -5.1);
console.log(DecimalPrecision4.ceil(5.12, 1) === 5.2);
console.log(DecimalPrecision4.ceil(-5.12, 1) === -5.1);
console.log(DecimalPrecision4.floor(5.12, 1) === 5.1);
console.log(DecimalPrecision4.floor(-5.12, 1) === -5.2);
console.log(DecimalPrecision4.trunc(5.12, 1) === 5.1);
console.log(DecimalPrecision4.trunc(-5.12, 1) === -5.1);
// testing edge cases for round
console.log(DecimalPrecision4.round(1.005, 2) === 1.01);
console.log(DecimalPrecision4.round(39.425, 2) === 39.43);
console.log(DecimalPrecision4.round(-1.005, 2) === -1.01);
console.log(DecimalPrecision4.round(-39.425, 2) === -39.43);
// testing edge cases for ceil
console.log(DecimalPrecision4.ceil(9.13, 2) === 9.13);
console.log(DecimalPrecision4.ceil(65.18, 2) === 65.18);
console.log(DecimalPrecision4.ceil(-2.26, 2) === -2.26);
console.log(DecimalPrecision4.ceil(-18.15, 2) === -18.15);
// testing edge cases for floor
console.log(DecimalPrecision4.floor(2.26, 2) === 2.26);
console.log(DecimalPrecision4.floor(18.15, 2) === 18.15);
console.log(DecimalPrecision4.floor(-9.13, 2) === -9.13);
console.log(DecimalPrecision4.floor(-65.18, 2) === -65.18);
// testing edge cases for trunc
console.log(DecimalPrecision4.trunc(2.26, 2) === 2.26);
console.log(DecimalPrecision4.trunc(18.15, 2) === 18.15);
console.log(DecimalPrecision4.trunc(-2.26, 2) === -2.26);
console.log(DecimalPrecision4.trunc(-18.15, 2) === -18.15);
// testing round to tens and hundreds
console.log(DecimalPrecision4.round(1262.48, -1) === 1260);
console.log(DecimalPrecision4.round(1262.48, -2) === 1300);
// testing toFixed()
console.log(DecimalPrecision4.toFixed(1.005, 2) === "1.01");
http://jsbench.github.io/#31ec3a8b3d22bd840f8e6822e681a3ac
这里是一个基准测试,比较了上述解决方案在Chrome 109.0.0.0上的每秒操作次数。使用Number.EPSILON
进行舍入函数至少快10倍到20倍。显然,所有浏览器都有所不同,所以结果可能会有所不同。解决方案2
因为速度快。我注意到,如果删除对isRound
的早期返回检查,则可以将速度提高约5-10%。 它比仅运行decimalAdjust
函数添加了更多操作。实际上使用isRound进行早期返回需要更长的时间。 - GollyJer您应该使用:
Math.round( num * 100 + Number.EPSILON ) / 100
似乎没有人意识到Number.EPSILON
。
值得注意的是,这并不像一些人所说的JavaScript怪异。
这只是计算机浮点数运算的工作原理。就像99%的编程语言一样,JavaScript没有自己制造的浮点数;它依赖于CPU/FPU来完成。计算机使用二进制,在二进制中,并不存在像0.1
这样的数字,而只是一个近似值。为什么?跟1/3不能用小数表示的原因一样:其值是0.33333333...有无限多个三。
于是就有了Number.EPSILON
。该数字是双精度浮点数中存在的下一个数字与1之间的差异。就是这样:在1
和1 + Number.EPSILON
之间没有任何数字。
编辑:
正如评论中所要求的那样,让我们澄清一件事情:仅当要舍入的值是算术运算的结果时,添加Number.EPSILON
才是相关的,因为它可以吞噬一些浮点误差。
当值来自直接来源(例如文字、用户输入或传感器)时,它是无用的。
编辑(2019年):
正如@maganap和一些人指出的那样,在乘法之前最好先添加Number.EPSILON
:
Math.round( ( num + Number.EPSILON ) * 100 ) / 100
编辑(2019年12月):
最近,我使用了一个类似于这个函数的函数来进行 epsilon 比较:
const ESPILON_RATE = 1 + Number.EPSILON ;
const ESPILON_ZERO = Number.MIN_VALUE ;
function epsilonEquals( a , b ) {
if ( Number.isNaN( a ) || Number.isNaN( b ) ) {
return false ;
}
if ( a === 0 || b === 0 ) {
return a <= b + EPSILON_ZERO && b <= a + EPSILON_ZERO ;
}
return a <= b * EPSILON_RATE && b <= a * EPSILON_RATE ;
}
我的使用案例是我多年开发的一个断言+数据验证库。
实际上,在我的代码中,我使用了EPSILON_RATE = 1 + 4 * Number.EPSILON
和EPSILON_ZERO = 4 * Number.MIN_VALUE
(四倍epsilon),因为我希望等号检查器足够宽松,可以累积浮点误差。
到目前为止,对我来说看起来非常完美。我希望它会有所帮助。
roundTo2DP(num)
, 它接收一个浮点数参数并返回保留2位小数的值。对于下列每个表达式,应该求出什么结果?
roundTo2DP(0.014999999999999999)
roundTo2DP(0.0150000000000000001)
roundTo2DP(0.015)
roundTo2DP
来给出那些明显的答案,因为传递给它的所有三个数字都是同一个数字。 IEEE 754二进制浮点数(JavaScript使用的一种类型)无法精确表示大多数非整数数字,因此上述所有三个数字字面量都会被四舍五入到附近的有效浮点数。恰巧这个数字是精确的。
0.01499999999999999944488848768742172978818416595458984375
这个数更接近于0.01而不是0.02。
你可以在浏览器控制台、Node shell或其他JavaScript解释器中看到这三个数字都是相同的。只需进行比较:
> <b><i>0.014999999999999999 === 0.0150000000000000001</i></b>
true
m = 0.0150000000000000001
时,最终得到的 m
的精确值 更接近于 0.01
而不是 0.02
。然而,如果我将 m
转换为字符串...> <b><i>var m = 0.0150000000000000001;</i></b>
> <b><i>console.log(String(m));</i></b>
0.015
> <b><i>var m = 0.014999999999999999;</i></b>
> <b><i>console.log(String(m));</i></b>
0.015
我得到了0.015,应该四舍五入为0.02,这明显不是我之前说的所有数字都完全相等的56位小数。那么这是什么黑魔法呢?
答案可以在ECMAScript规范中找到,在7.1.12.1:应用于Number类型的ToString部分。在这里,规定了将某些数字m转换为字符串的规则。关键部分是第5点,其中生成一个整数s,其数字将用于m的字符串表示形式:
让n、k和s为整数,其中k ≥ 1,10k-1 ≤ s < 10k,s × 10n-k的数字值为m,且k尽可能小。请注意,k是s十进制表示中的位数,s不能被10整除,并且s的最低有效数字不一定由这些条件唯一确定。m
,String(m)
的值必须具有“最少数量的数字”,同时仍满足Number(String(m)) === m
的要求。由于我们已经知道0.015 === 0.0150000000000000001
,现在很清楚为什么String(0.0150000000000000001) === '0.015'
必须是真的了。roundTo2DP(m)
应该返回什么。如果m
的确切值是0.01499999999999999944488848768742172978818416595458984375,但它的字符串表示是'0.015',那么当我们将其舍入到两个小数位时,从数学、实际、哲学或任何其他方面来看,什么是“正确”的答案呢?另一方面,如果您的值来自固有连续刻度(例如,传感器读数),则可能希望尊重二进制浮点值并向下舍入。
这两种方法需要不同的代码。为了尊重数字的字符串表示,我们可以(通过相当微妙的代码)实现自己的四舍五入,直接在字符串表示上逐位进行操作,使用你在学校学习如何四舍五入数字时使用的相同算法。下面是一个示例,它遵循OP的要求,在小数点后仅在必要时将数字表示为2个小数位,通过去除小数点后的尾随零;当然,你可能需要根据你的实际需求进行微调。/**
* Converts num to a decimal string (if it isn't one already) and then rounds it
* to at most dp decimal places.
*
* For explanation of why you'd want to perform rounding operations on a String
* rather than a Number, see https://dev59.com/Smgt5IYBdhLWcg3wwwO-#38676273
*
* @param {(number|string)} num
* @param {number} dp
* @return {string}
*/
function roundStringNumberWithoutTrailingZeroes (num, dp) {
if (arguments.length != 2) throw new Error("2 arguments required");
num = String(num);
if (num.indexOf('e+') != -1) {
// Can't round numbers this large because their string representation
// contains an exponent, like 9.99e+37
throw new Error("num too large");
}
if (num.indexOf('.') == -1) {
// Nothing to do
return num;
}
if (num[0] == '-') {
return "-" + roundStringNumberWithoutTrailingZeroes(num.slice(1), dp)
}
var parts = num.split('.'),
beforePoint = parts[0],
afterPoint = parts[1],
shouldRoundUp = afterPoint[dp] >= 5,
finalNumber;
afterPoint = afterPoint.slice(0, dp);
if (!shouldRoundUp) {
finalNumber = beforePoint + '.' + afterPoint;
} else if (/^9+$/.test(afterPoint)) {
// If we need to round up a number like 1.9999, increment the integer
// before the decimal point and discard the fractional part.
// We want to do this while still avoiding converting the whole
// beforePart to a Number (since that could cause loss of precision if
// beforePart is bigger than Number.MAX_SAFE_INTEGER), so the logic for
// this is once again kinda complicated.
// Note we can (and want to) use early returns here because the
// zero-stripping logic at the end of
// roundStringNumberWithoutTrailingZeroes does NOT apply here, since
// the result is a whole number.
if (/^9+$/.test(beforePoint)) {
return "1" + beforePoint.replaceAll("9", "0")
}
// Starting from the last digit, increment digits until we find one
// that is not 9, then stop
var i = beforePoint.length - 1;
while (true) {
if (beforePoint[i] == '9') {
beforePoint = beforePoint.substr(0, i) +
'0' +
beforePoint.substr(i+1);
i--;
} else {
beforePoint = beforePoint.substr(0, i) +
(Number(beforePoint[i]) + 1) +
beforePoint.substr(i+1);
break;
}
}
return beforePoint
} else {
// Starting from the last digit, increment digits until we find one
// that is not 9, then stop
var i = dp-1;
while (true) {
if (afterPoint[i] == '9') {
afterPoint = afterPoint.substr(0, i) +
'0' +
afterPoint.substr(i+1);
i--;
} else {
afterPoint = afterPoint.substr(0, i) +
(Number(afterPoint[i]) + 1) +
afterPoint.substr(i+1);
break;
}
}
finalNumber = beforePoint + '.' + afterPoint;
}
// Remove trailing zeroes from fractional part before returning
return finalNumber.replace(/0+$/, '')
}
示例用法:
> <b><i>roundStringNumberWithoutTrailingZeroes(1.6, 2)</i></b>
'1.6'
> <b><i>roundStringNumberWithoutTrailingZeroes(10000, 2)</i></b>
'10000'
> <b><i>roundStringNumberWithoutTrailingZeroes(0.015, 2)</i></b>
'0.02'
> <b><i>roundStringNumberWithoutTrailingZeroes('0.015000', 2)</i></b>
'0.02'
> <b><i>roundStringNumberWithoutTrailingZeroes(1, 1)</i></b>
'1'
> <b><i>roundStringNumberWithoutTrailingZeroes('0.015', 2)</i></b>
'0.02'
> <b><i>roundStringNumberWithoutTrailingZeroes(0.01499999999999999944488848768742172978818416595458984375, 2)</i></b>
'0.02'
> <b><i>roundStringNumberWithoutTrailingZeroes('0.01499999999999999944488848768742172978818416595458984375', 2)</i></b>
'0.01'
> <b><i>roundStringNumberWithoutTrailingZeroes('16.996', 2)</i></b>
'17'</code>
</pre>
将数字四舍五入到指定的小数位数,并返回字符串形式的结果,不包括尾随的零。
上面的函数<em>可能</em>是您想要使用的,以避免用户看到他们输入的数字被错误地四舍五入。
(作为替代方案,您还可以尝试<a rel="nofollow noreferrer" href="https://github.com/jhohlfeld/round10">round10</a>库,它提供了一个行为类似但实现方式完全不同的函数。)
但是,如果您有第二种类型的数字——从连续比例尺中获取的值,在这种情况下,没有理由认为具有较少小数位数的近似十进制表示比具有更多小数位数的表示更<em>准确</em>,因为该表示(如规范中所述)已经进行了某种程度的四舍五入;我们不想犯“0.014999999...375向上取整为0.015,向上取整为0.02,因此0.014999999...375向上取整为0.02”的错误。
<p>在这里,我们可以简单地使用内置的<a rel="nofollow noreferrer" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed"><code>toFixed</code></a>方法。请注意,通过在<code>toFixed</code>返回的字符串上调用<code>Number()</code>,我们得到一个数字,其字符串表示形式没有尾随零(感谢JavaScript计算数字的字符串表示形式的方式,在本回答中进行了讨论)。</p>
<pre><code>/**
* Takes a float and rounds it to at most dp decimal places. For example
*
* roundFloatNumberWithoutTrailingZeroes(1.2345, 3)
*
* returns 1.234
*
* Note that since this treats the value passed to it as a floating point
* number, it will have counterintuitive results in some cases. For instance,
*
* roundFloatNumberWithoutTrailingZeroes(0.015, 2)
*
* gives 0.01 where 0.02 might be expected. For an explanation of why, see
* https://dev59.com/Smgt5IYBdhLWcg3wwwO-#38676273. You may want to consider using the
* roundStringNumberWithoutTrailingZeroes function there instead.
*
* @param {number} num
* @param {number} dp
* @return {number}
*/
function roundFloatNumberWithoutTrailingZeroes (num, dp) {
var numToFixedDp = Number(num).toFixed(dp);
return Number(numToFixedDp);
}
考虑 .toFixed()
和 .toPrecision()
:
3.9935.toFixed(3) → "3.994"
,3.9945.toFixed(3) → "3.994"
,3.9955.toFixed(3) → "3.995"
,3.9965.toFixed(3) → "3.997"
。这是预期的行为吗?例如,3.9945.toFixed(3)
不应该返回"3.995"
或者3.9955.toFixed(3)
返回"3.996"
吗? - Yeheshuah可以使用.toFixed(NumberOfDecimalPlaces)
方法。
var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23
这里有一个简单的方法:
Math.round(value * 100) / 100
你可能想要单独创建一个函数来为你执行它:
function roundToTwo(value) {
return(Math.round(value * 100) / 100);
}
然后你只需传入这个值。
如果添加第二个参数,可以使其四舍五入到任意十进制位数。
function myRound(value, places) {
var multiplier = Math.pow(10, places);
return (Math.round(value * multiplier) / multiplier);
}
const formattedNumber = new Intl.NumberFormat('en', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(myNumber); const formattedNumberInNumber = parseFloat(formattedNumber);``` // #=> 1.28。[更多信息](https://dev59.com/anI-5IYBdhLWcg3wsKl4#1726662)
- tinystone