That is simply the way floating point numbers works in a computer. 与99%的编程语言一样,JavaScript没有自制的浮点数;它依赖于CPU / FPU . 计算机使用二进制,并且在二进制中,没有像 0.1 这样的数字,但仅仅是二进制近似 . 为什么?出于同样的原因,1/3不能用十进制写:它的值是0.33333333 ......无穷大的三分 .
来这里Number.EPSILON . 该数字是1和双精度浮点数中存在的下一个数字之间的差值 . That's it: There is no number between 1 and 1 + Number.EPSILON.
var x = 1.49999999999;
console.log(x.toPrecision(4));
console.log(x.toPrecision(3));
console.log(x.toPrecision(2));
var y = Math.PI;
console.log(y.toPrecision(6));
console.log(y.toPrecision(5));
console.log(y.toPrecision(4));
var z = 222.987654
console.log(z.toPrecision(6));
console.log(z.toPrecision(5));
console.log(z.toPrecision(4));
/**
* MidpointRounding away from zero ('arithmetic' rounding)
* Uses a half-epsilon for correction. (This offsets IEEE-754
* half-to-even rounding that was applied at the edge cases).
*/
function RoundCorrect(num, precision = 2) {
// half epsilon to correct edge cases.
var c = 0.5 * Number.EPSILON * num;
// var p = Math.pow(10, precision); //slow
var p = 1; while (precision--> 0) p *= 10;
if (num < 0)
p *= -1;
return Math.round((num + c) * p) / p;
}
// testing some edge cases
console.log(RoundCorrect(1.005, 2)); // 1.01 correct
console.log(RoundCorrect(2.175, 2)); // 2.18 correct
console.log(RoundCorrect(5.015, 2)); // 5.02 correct
console.log(RoundCorrect(-1.005, 2)); // -1.01 correct
console.log(RoundCorrect(-2.175, 2)); // -2.18 correct
console.log(RoundCorrect(-5.015, 2)); // -5.02 correct
9
如果值是文本类型:
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 是一个更好的解决方案 . But it is not! 在某些情况下,它将 NOT 正确舍入:
Math.round(1.005 * 1000)/1000 // Returns 1 instead of expected 1.01!
toFixed()在某些情况下也会 NOT 圆正确(在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实际上就像浮动1.55499994幕后 .
Solution 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);
}
}
当然,这一讨论都没有直接回答 roundTo2DP(m) 应该返回的内容 . 如果 m 's exact value is 0.01499999999999999944488848768742172978818416595458984375, but its String representation is ' 0.015',那么当我们将它舍入到小数点后两位时,什么是正确的答案 - 数学,实际,哲学或其他什么?
/**
* 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 http://stackoverflow.com/a/38676273/1709587
*
* @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;
}
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.
finalNumber = Number(beforePoint)+1;
} 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+$/, '')
}
但是,如果你有第二种数字 - 从连续尺度中取得的值,那么没有理由认为具有较少小数位的近似十进制表示比具有更多数字的数字更准确?在这种情况下,我们不希望尊重String表示,因为该表示(如规范中所述)已经是排序的;我们不想犯错误说"0.014999999...375 rounds up to 0.015, which rounds up to 0.02, so 0.014999999...375 rounds up to 0.02" .
/**
* 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
* http://stackoverflow.com/a/38676273/1709587. 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);
}
17
可以使用 .toFixed(NumberOfDecimalPlaces) .
var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23
42
None of the answers found here is correct . @stinkycheeseman要求 round up ,你们全部舍入了这个数字 .
(function(){
/**
* Decimal adjustment of a number.
*
* @param {String} type The type of adjustment.
* @param {Number} value The number.
* @param {Integer} exp The exponent (the 10 logarithm of the adjustment base).
* @returns {Number} The adjusted value.
*/
function decimalAdjust(type, value, exp) {
// If the exp is undefined or zero...
if (typeof exp === 'undefined' || +exp === 0) {
return Math[type](value);
}
value = +value;
exp = +exp;
// If the value is not a number or the exp is not an integer...
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
return NaN;
}
// Shift
value = value.toString().split('e');
value = Math[type](+(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));
}
// Decimal round
if (!Math.round10) {
Math.round10 = function(value, exp) {
return decimalAdjust('round', value, exp);
};
}
// Decimal floor
if (!Math.floor10) {
Math.floor10 = function(value, exp) {
return decimalAdjust('floor', value, exp);
};
}
// Decimal ceil
if (!Math.ceil10) {
Math.ceil10 = function(value, exp) {
return decimalAdjust('ceil', value, exp);
};
}
})();
30 回答
这可能对您有所帮助:
有关更多信息,您可以查看此链接
Math.round(num) vs num.toFixed(0) and browser inconsistencies
使用类似这样的东西“parseFloat(parseFloat(value).toFixed(2))”
你应该使用:
似乎没有人知道Number.EPSILON .
另外值得注意的是,这并不是像某些人所说的JavaScript怪异 .
That is simply the way floating point numbers works in a computer. 与99%的编程语言一样,JavaScript没有自制的浮点数;它依赖于CPU / FPU . 计算机使用二进制,并且在二进制中,没有像
0.1
这样的数字,但仅仅是二进制近似 . 为什么?出于同样的原因,1/3不能用十进制写:它的值是0.33333333 ......无穷大的三分 .来这里Number.EPSILON . 该数字是1和双精度浮点数中存在的下一个数字之间的差值 . That's it: There is no number between 1 and 1 + Number.EPSILON.
EDIT:
正如评论中所要求的那样,让我们澄清一点:只有当round的值是算术运算的结果时才添加
Number.EPSILON
,因为它可以吞下一些浮点误差delta .当值来自直接源(例如:文字,用户输入或传感器)时,它没有用 .
要不处理多个0,请使用以下变体:
您可以使用
我在MDN发现了这个 . 他们的方式避免了1.005的问题mentioned .
由于ES6有'proper'方式(没有覆盖静态和创建变通方法),所以using toPrecision
使用
Math.round(num * 100) / 100
最简单的方法:
+num.toFixed(2)
它将它转换为字符串,然后返回到整数/浮点数 .
考虑
.toFixed()
和.toPrecision()
:http://www.javascriptkit.com/javatutors/formatnumber.shtml
通常,舍入是通过缩放来完成的:
round(num / p) * p
使用指数表示法正确处理ve数的舍入 . 但是,此方法无法正确舍入边缘情况 .
在这里,我也写了一个函数来正确地进行算术舍入 . 你可以自己测试一下 .
如果值是文本类型:
如果值是数字:
有一个缺点,像1.5这样的值会给出“1.50”作为输出 . @minitech建议修复:
似乎
Math.round
是一个更好的解决方案 . But it is not! 在某些情况下,它将 NOT 正确舍入:toFixed()在某些情况下也会 NOT 圆正确(在Chrome v.55.0.2883.87中测试)!
例子:
我想,这是因为1.555实际上就像浮动1.55499994幕后 .
Solution 1 是使用具有所需舍入算法的脚本,例如:
https://plnkr.co/edit/uau8BlS1cqbvWPCHJeOy?p=preview
Solution 2 是为了避免前端计算并从后端服务器中提取舍入值 .
2017年
只需使用本机代码
.toFixed()
如果您需要严格并且只在需要时添加数字,它可以使用
replace
这个问题很复杂 .
假设我们有一个函数
roundTo2DP(num)
,它将float作为参数并返回一个舍入到2位小数的值 . 每个表达式应该评估什么?roundTo2DP(0.014999999999999999)
roundTo2DP(0.0150000000000000001)
roundTo2DP(0.015)
“明显”的答案是第一个例子应该舍入到0.01(因为它接近0.01而不是0.02),而另外两个应该舍入到0.02(因为0.0150000000000000001接近0.02而不是0.01,因为0.015恰好在中间他们并且有一个数学约定,这些数字被四舍五入) .
您可能已经猜到的问题是,无法实现
roundTo2DP
来提供那些明显的答案,因为传递给它的所有三个数字都是相同的数字 . IEEE 754二进制浮点数(JavaScript使用的类型)不能精确地表示大多数非整数数字,因此上面的所有三个数字文字都会四舍五入到附近的有效浮点数 . 事实上,这个数字正是如此0.01499999999999999944488848768742172978818416595458984375
它接近0.01而不是0.02 .
您可以在浏览器控制台,Node shell或其他JavaScript解释器中看到所有三个数字都相同 . 只是比较它们:
因此,当我写
m = 0.0150000000000000001
时,我最终得到的m
的确切值更接近0.01
而不是0.02
. 然而,如果我将m
转换为String ......我得到0.015,它应该舍入到0.02,这显然不是我之前说的所有这些数字完全相等的56位小数位数 . 那么黑魔法是什么?
答案可以在ECMAScript规范的7.1.12.1: ToString applied to the Number type部分找到 . 这里规定了将一些数字m转换为字符串的规则 . 关键部分是第5点生成一个整数s,其数字将用于m的字符串表示:
这里的关键部分是要求“k尽可能小” . 该要求相当于一个要求,给定一个数字
m
,String(m)
的值必须具有尽可能少的位数,同时仍满足Number(String(m)) === m
的要求 . 既然我们已经知道0.015 === 0.0150000000000000001
,那么现在很清楚为什么String(0.0150000000000000001) === '0.015'
必须是真的 .当然,这一讨论都没有直接回答
roundTo2DP(m)
应该返回的内容 . 如果m
's exact value is 0.01499999999999999944488848768742172978818416595458984375, but its String representation is ' 0.015',那么当我们将它舍入到小数点后两位时,什么是正确的答案 - 数学,实际,哲学或其他什么?对此没有一个正确的答案 . 这取决于您的使用案例 . 在下列情况下,您可能希望尊重String表示并向上舍入:
所表示的值本质上是离散的,例如以第纳尔为单位的三位小数货币的货币数量 . 在这种情况下,像0.015这样的Number的真值是0.015,而它在二进制浮点中获得的0.0149999999 ...表示是一个舍入误差 . (当然,许多人会合理地争辩说,你应该使用十进制库来处理这些值,并且从不将它们首先表示为二进制浮点数 . )
该值由用户键入 . 在这种情况下,再次输入的确切十进制数比最近的二进制浮点表示更多'true' .
另一方面,当您的值来自固有的连续比例时,您可能希望尊重二进制浮点值并向下舍入 - 例如,如果它是来自传感器的读数 .
这两种方法需要不同的代码 . 为了尊重Number的String表示,我们可以(通过相当多一些相当微妙的代码)实现我们自己的舍入,它直接作用于String表示,逐个数字,使用你在学校时使用的相同算法被教导如何围绕数字 . 下面是一个例子,它尊重OP要求通过剥离小数点后面的尾随零来“仅在必要时”将数字表示为2位小数的要求;当然,您可能需要根据您的具体需求进行调整 .
用法示例:
上面的函数可能是你想要用来避免用户看到他们输入的数字被错误舍入的内容 .
(作为替代方案,您也可以尝试使用round10库,该库提供具有相似功能的函数,具有完全不同的实现 . )
但是,如果你有第二种数字 - 从连续尺度中取得的值,那么没有理由认为具有较少小数位的近似十进制表示比具有更多数字的数字更准确?在这种情况下,我们不希望尊重String表示,因为该表示(如规范中所述)已经是排序的;我们不想犯错误说"0.014999999...375 rounds up to 0.015, which rounds up to 0.02, so 0.014999999...375 rounds up to 0.02" .
在这里我们可以简单地使用内置的toFixed方法 . 请注意,通过在
toFixed
返回的String上调用Number()
,我们得到一个Number,其String表示没有尾随零(由于JavaScript计算数字的String表示的方式,在本回答前面讨论过) .可以使用
.toFixed(NumberOfDecimalPlaces)
.None of the answers found here is correct . @stinkycheeseman要求 round up ,你们全部舍入了这个数字 .
要整理,请使用:
它可能适合你,
知道toFixed和round之间的区别 . 你可以看看Math.round(num) vs num.toFixed(0) and browser inconsistencies .
这是一个原型方法:
试试这个 light weight 解决方案:
如果你碰巧已经在使用d3库,那么它们有一个强大的数字格式库:https://github.com/mbostock/d3/wiki/Formatting
具体舍入在这里:https://github.com/mbostock/d3/wiki/Formatting#d3_round
在您的情况下,答案是:
更简单的ES6方式是
此模式还返回所要求的精度 .
例如:
toFixed(2)
这里2是我们想要对这个数字进行舍入的数字位数 .有几种方法可以做到这一点 . 像我这样的人,Lodash的变种
Usage:
如果你的项目使用jQuery或lodash,你也可以在库中找到合适的
round
方法 .更新1
我删除了变体
n.toFixed(2)
,因为它不正确 . 谢谢@ avalanche1对我来说Math.round()没有给出正确答案 . 我发现toFixed(2)效果更好 . 以下是两个例子:
MarkG和Lavamantis提供了一个比被接受的解决方案更好的解决方案 . 遗憾的是他们没有得到更多的赞成!
这是我用来解决浮点小数问题also based on MDN的函数 . 它比Lavamantis的解决方案更通用(但更简洁):
使用它:
与Lavamantis的解决方案相比,我们可以......
精确的舍入方法 . 资料来源:Mozilla
例子:
MarkG的答案是正确的 . 这是任意数量小数位的通用扩展 .
用法:
单元测试:
如果您使用的是lodash库,则可以使用lodash的圆形方法,如下所示 .
例如:
这是一个简单的方法:
您可能希望继续执行单独的功能来为您执行此操作:
然后你只需传入值 .
您可以通过添加第二个参数来增强它以舍入到任意数量的小数 .
只在必要时才实现这种舍入的一种方法是使用Number.prototype.toLocaleString():
这将提供您期望的输出,但作为字符串 . 如果这不是您期望的数据类型,您仍然可以将它们转换回数字 .