舍入到最多2位小数(仅在必要时)

问题描述 投票:2271回答:66

我想最多舍入2位小数,但只在必要时。

输入:

10
1.7777777
9.1

输出:

10
1.78
9.1

我怎样才能在JavaScript中执行此操作?

javascript decimal rounding decimal-point
66个回答
2868
投票

使用Math.round(num * 100) / 100


50
投票

你应该使用:

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之间没有数字。

编辑:

正如评论中所要求的那样,让我们​​澄清一点:只有当round的值是算术运算的结果时才添加Number.EPSILON,因为它可以吞下一些浮点误差delta。

当值来自直接来源(例如:文字,用户输入或传感器)时,它没有用。


45
投票

这是一个简单的方法:

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

36
投票
+(10).toFixed(2); // = 10
+(10.12345).toFixed(2); // = 10.12

(10).toFixed(2); // = 10.00
(10.12345).toFixed(2); // = 10.12

36
投票

对我来说,Math.round()没有给出正确答案。我发现toFixed(2)效果更好。以下是两者的示例:

console.log(Math.round(43000 / 80000) * 100); // wrong answer

console.log(((43000 / 80000) * 100).toFixed(2)); // correct answer

33
投票

2017年 只需使用本机代码.toFixed()

number = 1.2345;
number.toFixed(2) // "1.23"

如果您需要严格并且只在需要时添加数字,它可以使用replace

number = 1; // "1"
number.toFixed(5).replace(/\.?0*$/g,'');

33
投票

使用此功能Number(x).toFixed(2);


31
投票

试试这个轻量级解决方案:

function round(x, digits){
  return parseFloat(x.toFixed(digits))
}

 round(1.222,  2) ;
 // 1.22
 round(1.222, 10) ;
 // 1.222

28
投票

有几种方法可以做到这一点。像我这样的人,Lodash的变种

function round(number, precision) {
    var pair = (number + 'e').split('e')
    var value = Math.round(pair[0] + 'e' + (+pair[1] + precision))
    pair = (value + 'e').split('e')
    return +(pair[0] + 'e' + (+pair[1] - precision))
}

用法:

round(0.015, 2) // 0.02
round(1.005, 2) // 1.01

如果你的项目使用jQuery或lodash,你也可以在库中找到合适的round方法。

更新1

我删除了变体n.toFixed(2),因为它不正确。谢谢@ avalanche1


22
投票

MarkG和Lavamantis提供了一个比被接受的解决方案更好的解决方案。遗憾的是他们没有得到更多的赞成!

这是我用来解决浮点小数问题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

20
投票

如果您使用的是lodash库,则可以使用lodash的圆形方法,如下所示。

_.round(number, precision)

例如:

_.round(1.7777777, 2) = 1.78

2689
投票

如果值是文本类型:

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 * 1000)/1000 // 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实际上就像浮动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);
  }
}

https://plnkr.co/edit/uau8BlS1cqbvWPCHJeOy?p=preview

解决方案2是避免前端计算并从后端服务器中提取舍入值。


18
投票

这可能对您有所帮助:

var result = (Math.round(input*100)/100);

有关更多信息,您可以查看此链接

Math.round(num) vs num.toFixed(0) and browser inconsistencies


16
投票
var roundUpto = function(number, upto){
    return Number(number.toFixed(upto));
}
roundUpto(0.1464676, 2);

toFixed(2)这里2是我们想要对这个数字进行舍入的数字位数。


14
投票

它可能适合你,

Math.round(num * 100)/100;

知道toFixed和round之间的区别。你可以看看Math.round(num) vs num.toFixed(0) and browser inconsistencies


13
投票

最简单的方法:

+num.toFixed(2)

它将它转换为字符串,然后返回到整数/浮点数。


13
投票

使用类似“parseFloat(parseFloat(value).toFixed(2))”的内容

parseFloat(parseFloat("1.7777777").toFixed(2))-->1.78 
parseFloat(parseFloat("10").toFixed(2))-->10 
parseFloat(parseFloat("9.1").toFixed(2))-->9.1

13
投票

自ES6以来,有一种“正确的”方式(没有覆盖静态和创建变通方法)来执行此操作using toPrecision

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

然后你可以只是parseFloat和零将“消失”。

console.log(parseFloat((1.4999).toPrecision(3)));
console.log(parseFloat((1.005).toPrecision(3)));
console.log(parseFloat((1.0051).toPrecision(3)));

12
投票

这是一个原型方法:

Number.prototype.round = function(places){
    places = Math.pow(10, places); 
    return Math.round(this * places)/places;
}

var yournum = 10.55555;
yournum = yournum.round(2);

11
投票

通常,舍入是通过缩放来完成的:round(num / p) * p

使用指数表示法正确处理+ ve数字的舍入。但是,此方法无法正确舍入边缘情况。

function round(num, precision = 2) {
	var scaled = Math.round(num + "e" + precision);
	return Number(scaled + "e" + -precision);
}

// testing some edge cases
console.log( round(1.005, 2) );  // 1.01 correct
console.log( round(2.175, 2) );  // 2.18 correct
console.log( round(5.015, 2) );  // 5.02 correct

console.log( round(-1.005, 2) );  // -1    wrong
console.log( round(-2.175, 2) );  // -2.17 wrong
console.log( round(-5.015, 2) );  // -5.01 wrong

在这里,我也写了一个函数来正确地进行算术舍入。你可以自己测试一下。

/**
 * 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

10
投票

要不处理多个0,请使用以下变体:

Math.round(num * 1e2) / 1e2

9
投票

如果你碰巧已经在使用d3库,那么它们有一个强大的数字格式库:https://github.com/mbostock/d3/wiki/Formatting

特此舍入在这里:https://github.com/mbostock/d3/wiki/Formatting#d3_round

在您的情况下,答案是:

> d3.round(1.777777, 2)
1.78
> d3.round(1.7, 2)
1.7
> d3.round(1, 2)
1

398
投票

您可以使用

function roundToTwo(num) {    
    return +(Math.round(num + "e+2")  + "e-2");
}

我在MDN上找到了这个。他们的方式避免了1.005那个mentioned的问题。

roundToTwo(1.005)
1.01
roundToTwo(10)
10
roundToTwo(1.7777777)
1.78
roundToTwo(9.1)
9.1
roundToTwo(1234.5678)
1234.57

9
投票

只在必要时实现这种舍入的一种方法是使用Number.prototype.toLocaleString()

myNumber.toLocaleString('en', {maximumFractionDigits:2, useGrouping:false})

这将提供您期望的输出,但作为字符串。如果这不是您期望的数据类型,您仍然可以将它们转换回数字。


131
投票

MarkG的答案是正确的。这是任意数量小数位的通用扩展名。

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

77
投票

人们可以使用.toFixed(NumberOfDecimalPlaces)

var str = 10.234.toFixed(2); // => '10.23'
var number = Number(str); // => 10.23

65
投票

这个问题很复杂。

假设我们有一个函数roundTo2DP(num),它将float作为参数并返回一个四舍五入到小数位的值。每个表达式应该评估什么?

  • 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解释器中看到所有三个数字相同。只是比较它们:

> 0.014999999999999999 === 0.0150000000000000001
true

因此,当我写m = 0.0150000000000000001时,我最终得到的m的确切值更接近于0.01而不是0.02。然而,如果我将m转换为String ...

> var m = 0.0150000000000000001;
> console.log(String(m));
0.015
> var m = 0.014999999999999999;
> console.log(String(m));
0.015

...我得到0.015,它应该舍入到0.02,这显然不是我之前说过的所有这些数字完全相等的56位小数位数。那么黑暗魔法是什么呢?

答案可以在ECMAScript规范的7.1.12.1: ToString applied to the Number type部分找到。这里规定了将一些数字m转换为字符串的规则。关键部分是第5点,其中生成一个整数s,其数字将用于m的字符串表示:

令n,k和s为整数,使得k≥1,10k-1≤s<10k,s×10n-k的Number值为m,并且k尽可能小。注意,k是s的十进制表示中的位数,s不能被10整除,并且s的最低有效位不一定由这些标准唯一确定。

这里的关键部分是要求“k尽可能小”。该要求相当于一个要求,给定一个数字mString(m)的值必须具有尽可能少的数字,同时仍满足Number(String(m)) === m的要求。既然我们已经知道0.015 === 0.0150000000000000001,现在很清楚为什么String(0.0150000000000000001) === '0.015'必须是真的。

当然,这一讨论都没有直接回答roundTo2DP(m)应该返回的内容。如果m的确切值是0.0149999999999999944488848768742172978818416595458984375,但它的字符串表示是'0.015',那么当我们将它四舍五入到小数点后两位时,正确的答案是什么 - 数学,实际,哲学或其他什么?

对此没有一个正确的答案。这取决于您的用例。在下列情况下,您可能希望尊重String表示并向上舍入:

  • 所表示的值本质上是离散的,例如,一种3位小数位的货币,如第纳尔。在这种情况下,像0.015这样的Number的真值是0.015,而它在二进制浮点中获得的0.0149999999 ...表示是舍入误差。 (当然,许多人会合理地争辩说,你应该使用十进制库来处理这些值,并且从不将它们首先表示为二进制浮点数。)
  • 该值由用户键入。在这种情况下,再次输入的确切十进制数比最近的二进制浮点表示更“真”。

另一方面,当您的值来自固有的连续标度时,您可能希望尊重二进制浮点值并向下舍入 - 例如,如果它是来自传感器的读数。

这两种方法需要不同的代码。为了尊重Number的String表示,我们可以(通过相当多一些相当微妙的代码)实现我们自己的舍入,它直接作用于String表示,逐位,使用您在学校时使用的相同算法被教导如何围绕数字。下面是一个例子,它尊重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 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+$/, '')
}

用法示例:

> roundStringNumberWithoutTrailingZeroes(1.6, 2)
'1.6'
> roundStringNumberWithoutTrailingZeroes(10000, 2)
'10000'
> roundStringNumberWithoutTrailingZeroes(0.015, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.015000', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(1, 1)
'1'
> roundStringNumberWithoutTrailingZeroes('0.015', 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes(0.01499999999999999944488848768742172978818416595458984375, 2)
'0.02'
> roundStringNumberWithoutTrailingZeroes('0.01499999999999999944488848768742172978818416595458984375', 2)
'0.01'

上面的函数可能就是你想要用来避免用户看到他们输入的数字被错误舍入的内容。

(作为替代方案,您还可以尝试使用round10库,该库提供具有类似行为的函数,具有完全不同的实现。)

但是,如果你有第二种数字 - 从连续尺度中取得的值,那么没有理由认为具有较少小数位的近似十进制表示比具有更多数字的数字更准确?在这种情况下,我们不希望尊重String表示,因为该表示(如规范中所述)已经是排序的;我们不想错误地说“0.014999999 ...... 375轮到0.015,最高为0.02,所以0.014999999 ...... 375轮到0.02”。

在这里,我们可以简单地使用内置的toFixed方法。请注意,通过在Number()返回的字符串上调用toFixed,我们得到一个Number,其String表示没有尾随零(这要归功于JavaScript计算数字的String表示形式的方式,在本回答前面讨论过)。

/**
 * 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);
}

64
投票

考虑.toFixed().toPrecision()

http://www.javascriptkit.com/javatutors/formatnumber.shtml


62
投票

精确的舍入方法。资料来源:Mozilla

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

例子:

// Round
Math.round10(55.55, -1); // 55.6
Math.round10(55.549, -1); // 55.5
Math.round10(55, 1); // 60
Math.round10(54.9, 1); // 50
Math.round10(-55.55, -1); // -55.5
Math.round10(-55.551, -1); // -55.6
Math.round10(-55, 1); // -50
Math.round10(-55.1, 1); // -60
Math.round10(1.005, -2); // 1.01 -- compare this with Math.round(1.005*100)/100 above
// Floor
Math.floor10(55.59, -1); // 55.5
Math.floor10(59, 1); // 50
Math.floor10(-55.51, -1); // -55.6
Math.floor10(-51, 1); // -60
// Ceil
Math.ceil10(55.51, -1); // 55.6
Math.ceil10(51, 1); // 60
Math.ceil10(-55.59, -1); // -55.5
Math.ceil10(-59, 1); // -50

58
投票

这里找到的答案都不正确。 @stinkycheeseman要求围捕,你们四舍五入。

要整理,请使用:

Math.ceil(num * 100)/100;
© www.soinside.com 2019 - 2024. All rights reserved.