我想最多四舍五入小数点后两位,但仅限于必要时。
输入:
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!
在某些情况下,toFixed() 也不会正确舍入(在 Chrome v.55.0.2883.87 中测试)!
例子:
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 实际上在幕后类似于 float 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);
}
}
它也是at 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; }
。我可以知道这个条件的目的是什么吗?
Math.round()
快得多。 jsbin.com/kikocecemu/edit?js,output
var a = parseFloat(1/3).toFixed(2);
的事情,当您执行 var c = a + someNumber;
时它似乎不喜欢它 - 它会像您尝试添加一样对待它一个字符串(那里的新 a
)到一个数字(someNumber
)。所以可能需要做var c = eval(a) + someNumber;
。
Number(a)
、parseFloat(a)
(实际上与 stackoverflow.com/a/11988612/16940 的行为相同)而不是 eval(a)
。您甚至可以只使用 +a
。我更喜欢Number(a)
。
您可以使用
function roundToTwo(num) {
return +(Math.round(num + "e+2") + "e-2");
}
我找到了这个 on MDN。他们的方式避免了 1.005 was mentioned 的问题。
roundToTwo(1.005)
1.01
roundToTwo(10)
10
roundToTwo(1.7777777)
1.78
roundToTwo(9.1)
9.1
roundToTwo(1234.5678)
1234.57
+(val)
是使用 Number(val)
的强制等效项。将“e-2”连接到数字会导致需要将字符串转换回数字。
-2.9e-7
,则 +(Math.round(num + "e+2") + "e-2")
返回 NaN
,这不是所需的结果。至少在 Chrome 101 上
MarkG's answer 是正确的。这是任意小数位数的通用扩展。
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);
function round(val, decimals) { return +(Math.round(+(val.toFixed(decimals) + "e+" + decimals)) + "e-" + decimals); }
你应该使用:
Math.round( num * 100 + Number.EPSILON ) / 100
似乎没有人知道 Number.EPSILON
。
另外值得注意的是,这并不是某些人所说的 JavaScript 怪异。
这就是浮点数在计算机中的工作方式。 与 99% 的编程语言一样,JavaScript 没有自制浮点数。它依赖于 CPU/FPU。计算机使用二进制,而在二进制中,没有像 0.1
这样的数字,而只是一个二进制近似值。为什么?出于同样的原因,1/3 不能写成十进制:它的值是 0.33333333... 有无穷多个三。
来了Number.EPSILON
。该数字是 1 与双精度浮点数中存在的 next 数字之间的差。 就是这样: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 ;
}
我的用例是an assertion + data validation lib我已经开发了很多年。
事实上,在代码中我使用了 ESPILON_RATE = 1 + 4 * Number.EPSILON
和 EPSILON_ZERO = 4 * Number.MIN_VALUE
(四倍 epsilon),因为我想要一个足够松散的相等检查器来累积浮点错误。
到目前为止,它看起来对我来说很完美。我希望它会有所帮助。
通常,小数舍入是通过缩放完成的:round(num * p) / p
幼稚的实现
使用以下带有中间数字的函数,您将获得预期的上舍入值,或者有时取决于输入的下舍入值。
此 inconsistency
舍入可能会在客户端代码中引入难以检测的错误。
函数 naiveRound(num, decimalPlaces = 0) { var p = Math.pow(10, decimalPlaces);返回 Math.round(num * p) / p; } console.log( naiveRound(1.245, 2) ); // 1.25 正确(按预期四舍五入) console.log( naiveRound(1.255, 2) ); // 1.25 不正确(应该是 1.26) // 测试边缘情况 console.log( naiveRound(1.005, 2) ); // 1 不正确(应该是 1.01) console.log( naiveRound(2.175, 2) ); // 2.17 不正确(应该是 2.18) console.log( naiveRound(5.015, 2) ); // 5.01 不正确(应该是 5.02)
为了判断舍入操作是否涉及中点值,Round 函数将要舍入的原始值乘以 10 ** n,其中 n 是返回值中所需的小数位数,然后判断剩余的小数是否值的一部分大于或等于 0.5。由于浮点格式在二进制表示和精度方面存在问题,因此具有浮点值的 "Exact Testing for Equality"
存在问题。这意味着任何略小于 0.5 的小数部分(由于精度损失)都不会向上舍入。
在前面的例子中,5.015
是一个中点值,如果要四舍五入到小数点后两位,5.015 * 100的值实际上是501.49999999999994
。因为 .49999999999994 小于 0.5,所以向下舍入为 501,最终结果为 5.01。
更好的实现
指数符号
通过将数字转换为指数符号中的字符串,正数按预期四舍五入。但是,请注意负数与正数的舍入方式不同。
事实上,它执行的规则基本上等同于 "round half up",您将看到 round(-1.005, 2)
的计算结果为 -1
,即使 round(1.005, 2)
的计算结果为 1.01
。 lodash _.round 方法使用了这种技术。
/** * 向上舍入一半('向正无穷方向舍入一半') * 负数舍入不同于正数。 */ function round(num, decimalPlaces = 0) { num = Math.round(num + "e" + decimalPlaces);返回数字(num +“e”+ -decimalPlaces); } // 测试一半的四舍五入 console.log( round(0.5) ); // 1 个控制台.log( round(-0.5) ); // 0 // 测试边缘案例 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
示例中显示的舍入问题,我们可以定义一个自定义舍入函数,该函数执行“几乎相等”测试以确定小数值是否足够接近中点值以进行中点舍入。
// 从零开始舍入一半 function round(num, decimalPlaces = 0) { if (num < 0) return -round(-num, decimalPlaces); var p = Math.pow(10, decimalPlaces);变量 n = 数量 * p; var f = n - Math.floor(n); var e = Number.EPSILON * n; // 判断这个分数是否为中点值。返回 (f >= .5 - e) ? Math.ceil(n) / p : Math.floor(n) / p; } // 测试一半的四舍五入 console.log( round(0.5) ); // 1 个控制台.log( round(-0.5) ); // -1 // 测试边缘情况 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
编号.EPSILON
有一种不同的纯数学技术来执行舍入到最近(使用 "round half away from zero"),其中在调用舍入函数之前应用 epsilon 校正。
简单地说,我们在舍入之前将最小可能的浮点值(= 1.0 ulp;最后一个单位)添加到产品中。这将移动到下一个可表示的浮点值,远离零,因此它将抵消在乘以 10 ** n
期间可能出现的二进制 round-off error。
/** * 从零舍入一半(“商业”舍入) * 使用校正来抵消浮点不准确性。 * 对正数和负数对称地工作。 */ function round(num, decimalPlaces = 0) { var p = Math.pow(10, decimalPlaces); var n = (num * p) * (1 + Number.EPSILON);返回 Math.round(n) / p; } // 取整一半 console.log(round(0.5) ); // 1 个控制台.log( round(-0.5) ); // -1 // 测试边缘情况 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
添加 1 ulp 后,5.015 * 100 的值即 501.49999999999994
将被修正为 501.50000000000006
,这将四舍五入为 502,最终结果为 5.02。
请注意,unit in last place(“ulp”)的大小由 (1) 数字的大小和 (2) 相对机器 epsilon (2^-52) 确定。 Ulp 在具有较大量级的数字上比在具有较小量级的数字上相对较大。
双舍入
在这里,我们使用 toPrecision() 方法去除中间计算中的浮点舍入误差。简单地说,我们舍入到 15 significant figures 以去除第 16 位有效数字的舍入误差。 PHP 7 round 函数也使用了这种将结果预舍入为有效数字的技术。
5.015 * 100 的值即 501.49999999999994
将首先四舍五入到 15 位有效数字为 501.500000000000
,然后再次四舍五入到 502,最后结果为 5.02。
// 从零开始舍入一半 function round(num, decimalPlaces = 0) { if (num < 0) return -round(-num, decimalPlaces); var p = Math.pow(10, decimalPlaces); var n = (num * p).toPrecision(15);返回 Math.round(n) / p; } // 取整一半 console.log(round(0.5) ); // 1 个控制台.log( round(-0.5) ); // -1 // 测试边缘情况 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
任意精度的 JavaScript 库 - decimal.js
// 从零开始舍入一半 function round(num, decimalPlaces = 0) { return new Decimal(num).toDecimalPlaces(decimalPlaces).toNumber(); } // 取整一半 console.log(round(0.5) ); // 1 个控制台.log( round(-0.5) ); // -1 // 测试边缘情况 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
(1.005).toPrecision(3)
实际上仍然返回 1.00
而不是 1.01
。
toPrecision
返回一个更改所需输出类型的字符串。
.toPrecision
方法的缺陷,它是浮点数的特殊性(JS 中的数字是)-尝试 1.005 - 0.005
,它将返回 0.9999999999999999
。
(1).toPrecision(3)
返回“1.00”,但在这种情况下发问者希望有 1
。
toPrecision
执行格式,而不是后者,并且不是对 OP 问题的回答,尽管乍一看它可能看起来很相关,但它有很多错误。请参阅en.wikipedia.org/wiki/Significant_figures。例如,Number(123.4).toPrecision(2)
返回 "1.2e+2"
,Number(12.345).toPrecision(2)
返回 "12"
。我也同意@adamduren 的观点,即它返回一个不可取的字符串(不是一个大问题,但不可取)。
最简单的方法是使用 toFixed,然后使用 Number 函数去除尾随零:
const number = 15.5;
Number(number.toFixed(2)); // 15.5
const number = 1.7777777;
Number(number.toFixed(2)); // 1.78
MarkG and Lavamantis offered 比已被接受的解决方案要好得多。可惜他们没有得到更多的支持!
这是我用来解决浮点小数问题的函数 also based on MDN。它比 Lavamantis 的解决方案更通用(但不太简洁):
function round(value, exp) {
if (typeof exp === 'undefined' || +exp === 0)
return Math.round(value);
value = +value;
exp = +exp;
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0))
return NaN;
// Shift
value = value.toString().split('e');
value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)));
// Shift back
value = value.toString().split('e');
return +(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp));
}
使用它:
round(10.8034, 2); // Returns 10.8
round(1.275, 2); // Returns 1.28
round(1.27499, 2); // Returns 1.27
round(1.2345678e+2, 2); // Returns 123.46
与 Lavamantis 的解决方案相比,我们可以做...
round(1234.5678, -2); // Returns 1200
round("123.45"); // Returns 123
仅在必要时实现这种舍入的一种方法是使用 Number.prototype.toLocaleString():
myNumber.toLocaleString('en', {maximumFractionDigits:2, useGrouping:false})
这将提供您期望的输出,但作为字符串。如果这不是您期望的数据类型,您仍然可以将它们转换回数字。
toLocaleString
。
它可能对你有用,
Math.round(num * 100)/100;
要知道 toFixed 和 round 之间的区别。你可以看看Math.round(num) vs num.toFixed(0) and browser inconsistencies。
这是最简单、更优雅的解决方案(我是世界上最好的;):
function roundToX(num, X) {
return +(Math.round(num + "e+"+X) + "e-"+X);
}
//roundToX(66.66666666,2) => 66.67
//roundToX(10,2) => 10
//roundToX(10.904,2) => 10.9
具有后备值的现代语法替代方案
const roundToX = (num = 0, X = 20) => +(Math.round(num + `e${X}`) + `e-${X}`)
E
表示法重写接受的答案以接受参数的好方法。
roundToX(362.42499999999995, 2)
。预期结果(如 PHP echo round(362.42499999999995, 2)
):362.43
。实际结果:362.42
var roundUpto = function(number, upto){
return Number(number.toFixed(upto));
}
roundUpto(0.1464676, 2);
toFixed(2)
:这里 2 是我们希望将此数字四舍五入的位数。
1.005
。来自 the Help Center:“...总是解释为什么你提出的解决方案是合适的以及它是如何工作的”。请通过 editing (changing) your answer 回复,而不是在评论中(没有“编辑:”、“更新:”或类似的 - 答案应该看起来好像是今天写的)。
另一个简单的解决方案(不编写任何函数)可以使用 toFixed() 然后再次转换为浮点数:
例如:
var objNumber = 1201203.1256546456;
objNumber = parseFloat(objNumber.toFixed(2))
请参阅@AmrAli 的答案,以更全面地了解此解决方案的所有各种调整和性能细分。
var DecimalPrecision = (function(){ if (Number.EPSILON === undefined) { Number.EPSILON = Math.pow(2, -52); } if(Number.isInteger === undefined){ Number.isInteger = function (value) { return typeof value === 'number' && isFinite(value) && Math.floor(value) === value; }; } this.isRound = function(n,p){ let l = n.toString ().split('.')[1].length; return (p >= l); } this.round = function(n, p=2){ if(Number.isInteger(n) || this.isRound (n,p)) 返回 n; 让 r = 0.5 * Number.EPSILON * n; 让 o = 1; 而(p-- > 0) o *= 10; if(n<0) o *= -1;返回 Math.round((n + r) * o) / o; } this.ceil = function(n, p=2){ if(Number.isInteger(n) || this.isRound(n,p)) 返回n; let r = 0.5 * Number.EPSILON * n; let o = 1; while(p-- > 0) o *= 10; return Math.ceil((n + r) * o) / o; } this. floor = function(n, p=2){ if(Number.isInteger(n) || this.isRound(n,p)) return n; 让 r = 0.5 * Number.EPSILON * n; 让 o = 1; 而(p-- > 0) o *= 10; return Math.floor((n + r) * o) / o; } return this; })(); console.log(DecimalPrecision.round(1.005)); console.log(DecimalPrecision.ceil(1.005)); console.log(DecimalPrecision.floor(1.005)); console.log(DecimalPrecision.round(1.0049999)); console.log(DecimalPrecision.ceil(1.0049999)); console.log(DecimalPrecision.floor(1.0049999)); console.log(DecimalPrecision.round(2.175495134384,7)); console.log(DecimalPrecision.round(2.1753543549,8)); console.log(DecimalPrecision.round(2.1755465135353,4)); console.log(DecimalPrecision.ceil(17,4)); console.log(DecimalPrecision.ceil(17.1,4)); console.log(DecimalPrecision.ceil(17.1,15));
DecimalPrecision.ceil(17,0); // 18
和 DecimalPrecision.ceil(17,1); // 17.1
DecimalPrecision.ceil(-5.12, 1); // -5.2
和 DecimalPrecision.floor(-5.12, 1); // -5.1
最简单的方法:
+num.toFixed(2)
它将其转换为字符串,然后再转换回整数/浮点数。
toFixed()
的参数更改为 3。所以它会是 +num.toFixed(3)
。这是按预期方式工作的,1.005 舍入为 1.00,等于 1
不定期副业成功案例分享