四舍五入至小数点后两位(仅在必要时)

我想最多舍入小数点后两位,但仅在必要时才可以

输入:

10
1.7777777
9.1

输出:

10
1.78
9.1

如何在 JavaScript 中执行此操作?

答案

使用Math.round(num * 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 * 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是避免前端计算,并从后端服务器提取舍入的值。

您可以使用

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

我在MDN上发现了这一点。他们的方法避免了提到的 1.005 问题。

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

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

您应该使用:

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

我的用例是我多年开发的断言 + 数据验证库

实际上,在代码中,我使用的是ESPILON_RATE = 1 + 4 * Number.EPSILONEPSILON_ZERO = 4 * Number.MIN_VALUE (四倍于 epsilon),因为我希望相等检查器足够松散以累积浮点错误。

到目前为止,它对我来说看起来很完美。希望对您有所帮助。

可以使用.toFixed(NumberOfDecimalPlaces)

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

考虑.toFixed().toPrecision()

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

这个问题很复杂。

假设我们有一个函数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.02,因为 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转换为 String ...

> <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的 String 表示形式:

N,K,S是整数,使得ķ≥1,10 K -1≤S <10 K,对于s×10的数量N的值- kmk是尽可能小。请注意,k 是s的十进制表示形式中的位数, s不能被 10 整除,并且s的最低有效位不一定由这些条件唯一确定。

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

当然,这些讨论都没有直接回答roundTo2DP(m) 应该返回什么。如果m的精确值为 0.01499999999999999944488848768742172978818416595458984375,但其字符串表示形式为 '0.015',那么当我们将其四舍五入到小数点后两位时, 正确答案是什么?

没有一个正确的答案。这取决于您的用例。您可能想尊重 String 表示形式,并在以下情况下向上舍入:

  • 所表示的值本质上是离散的,例如,第 3 小数位货币(如第纳尔)中的货币数量。在这种情况下,一些像 0.015 的真正价值 0.015,而 0.0149999999 ...... 表示它获得二进制浮点是一个舍入误差。 (当然,许多人会合理地说,您应该使用十进制库来处理此类值,而从一开始就不要将它们表示为二进制浮点数。)
  • 该值是由用户键入的。同样,在这种情况下,输入的确切十进制数字比最接近的二进制浮点数表示形式更 “真实”。

另一方面,您可能希望尊重二进制浮点值,并在其值本质上是连续的标度时向下舍入(例如,如果它是从传感器读取的值)。

这两种方法需要不同的代码。为了尊重数字的字符串表示形式,我们可以(使用相当多的精巧代码)实现我们自己的舍入,该舍入运算将使用您在学校时使用的相同算法,逐位直接作用于字符串表示形式。被教导如何四舍五入数字。下面是一个示例,该示例尊重 OP 要求 “仅在必要时” 将数字表示为小数点后两位小数的要求;当然,您可能需要根据实际需要进行调整。

/**
 * 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+$/, '')
}

用法示例:

> <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'

上面的功能可能是您要使用的功能,以避免用户看到他们输入的数字被错误舍入。

(作为替代方案,您也可以尝试round10库,该库提供行为类似但功能完全不同的实现。)

但是,如果您拥有第二种数字 - 取自连续刻度的值,而又没有理由认为小数位数较少的近似十进制表示形式比小数位数更为精确 ,该怎么办?在这种情况下,我们希望尊重字符串表示,因为这表示(如规格说明)已经是全面的排序; 我们不想犯这样的错误:“0.014999999 ... 375 舍入为 0.015,它舍入为 0.02,所以 0.014999999 ... 375 舍入为 0.02”。

在这里,我们可以简单地使用内置的toFixed方法。请注意,通过在toFixed返回的 String 上调用Number() ,我们得到一个 Number,其 String 表示形式不带尾随零(这要归功于此答案前面讨论的 JavaScript 计算 Number 的 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);
}

精确的舍入方法。资料来源: 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

这里没有找到正确的答案 。 @stinkycheeseman 要求四舍五入 ,你们都四舍五入了数字。

要向上舍入,请使用以下命令:

Math.ceil(num * 100)/100;