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


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

输入:

10
1.7777777
9.1

输出:

10
1.78
9.1

我怎么能这样做 JavaScript


1845
2017-08-06 17:17


起源


我在这里提供了许多作为解决方案提供的技术的小提琴...所以你可以比较: 小提琴 - dsdsdsdsd
似乎没有人知道 Number.EPSILON。使用 Math.round( num * 100 + Number.EPSILON ) / 100。 - cronvel
对于新读者,你不能这样做 除非你有一个字符串类型的结果。二进制内部数字表示的浮点数学意味着存在 总是 不能表示为小数的数字。 - Walf


答案:


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


2292
2017-08-06 17:20



虽然这适用于大多数情况,但它不适用于1.005,最终会变为1而不是1.01 - James
@James哇,这真的很奇怪 - 我在Chrome开发控制台工作,我注意到1.005 * 100 = 100.49999999999999。 Math.round(100.49999999999999)的计算结果为100,而Math.round(100.5)的计算结果为101. IE9的作用相同。这是因为 javascript中的浮点怪异 - stinkycheeseman
一个简单的解决方法。 2 d.p.,使用 Math.round((num + 0.00001) * 100) / 100。尝试 Math.round((1.005 + 0.00001) * 100) / 100 和 Math.round((1.0049 + 0.00001) * 100) / 100 - mrkschan
@mrkschan为什么会这样,所有数字都是万无一失的? - CMCDragonkai
对于那些没有得到它的人来说,这种技术称为缩放。基本上这里的答案是将小数点上的两个数字转换成一个整数,以避免所有疯狂的浮点问题,绕过它然后将其转换回之前的除以100并且你有你的回答2dp。 - Alex_Nabu


如果值是文本类型:

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 是避免前端计算并从后端服务器中提取舍入值。


2327
2017-10-11 00:27



这个(toFixed)方法很好,并且适用于我,但它具体 不 遵守“仅在必要时”的原始要求。 (它绕1.5到1.50,这打破了规范。) - Per Lundberg
对于“必要时”要求,请执行以下操作: parseFloat(number.toFixed(decimalPlaces));   @PerLundberg - Onur Yıldırım
parseFloat("55.555").toFixed(2) 回报 "55.55" 在Chrome开发者控制台中。 - Levi Botelho
使用toFixed而不是Math.round没有任何优势; toFixed导致完全相同的舍入问题(尝试使用5.555和1.005),但就像500x(没有开玩笑)慢于Math.round ...似乎@MarkG答案在这里更准确。 - Pierre
为什么js如此奇怪? - D.Deriso


您可以使用

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

324
2017-08-21 12:56



@Redsandro, +(val) 是强制等同于使用 Number(val)。将“e-2”连接到一个数字会产生一个需要转换回数字的字符串。 - Jack
请注意,对于会产生NaN的大型和小型浮标,例如+“1e-21 + 2”将无法正确解析。 - Pierre
你已经“解决了”1.005'问题',但引入了一个新问题:现在,在Chrome控制台中, roundToTwo(1.0049999999999999) 出现了1.01(不可避免地,因为 1.0049999999999999 == 1.005)。在我看来,如果你输入你得到的浮动 num = 1.005 '显然''应该'舍入到1.00,因为num的确切值小于1.005。当然,在我看来,字符串'1.005''显然''应该'舍入到1.01。事实上,不同的人似乎对这里的实际正确行为有不同的直觉,这是其复杂性的部分原因。 - Mark Amery
中间没有(浮点)数字 1.0049999999999999 和 1.005,根据定义,它们是相同的数字。这被称为dedekind cut。 - Azmisov
@Azmisov是对的。而 1.00499 < 1.005 是 true, 1.0049999999999999 < 1.005 评估为 false。 - falconepl


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

115
2017-11-01 07:40



皮尔在MarkG的回答中提出了一个严肃的问题。 - dsjoerg
注意:如果您不想更改Number.prototype - 只需将其写为函数: function round(number, decimals) { return +(Math.round(number + "e+" + decimals) + "e-" + decimals); } - Philipp Tsipman
我创建 Math.roundPlaces(num, places),这样它也可以在字符串上工作(以防万一)。 - bradlis7
尝试n:1e + 19 - 它返回NaN - DavidJ
该算法总是向上舍入(而不是从零开始)。因此,如果是负数,输出不是您所期望的: (-1.005).round(2) === -1 - Aleksej Komarov


一个人可以使用 .toFixed(NumberOfDecimalPlaces)

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

71
2017-10-21 17:02



为什么这不是最好的答案? (编辑:哦,因为它转换为字符串:) - Daniel
还因为它添加了尾随零,这是 不是原来的问题要求的。 - Alastair Maw
但是尾随零很容易用正则表达式去掉,即`Number(10.10000.toFixed(2).replace(/ 0 + $ /,''))`=> 10.1 - Chad McElligott
@daniel 最佳答案 是相同的(截至目前),但事实并非如此 alsways 圆正确,试试 +(1.005).toFixed(2) 返回 1 代替 1.01。 - Emile Bergeron
@ChadMcElligott:你的正则表达式不适用于整数: Number(9).toFixed(2).replace(/0+$/, '') =>“9” - Jacob van Lingen


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

54
2017-08-01 08:02



有人把它放在GitHub和npm上: github.com/jhohlfeld/round10 - Jo Liss
酷和工作的代码。谢谢你@Error。 ! - Anahit DEV
不, Math.round10(3544.5249, -2) 返回3544.52而不是3544.53 - Matija
@Matija Wolfram alpha 3544.52也说。您希望最小化当前数字和舍入近似值之间的误差。最近似的 3544.5249 小数点后2位 3544.52 (误差= 0.0049)。如果它是 3544.53,错误是0.0051。你正在进行连续的舍入,即Math.round10(Math.round10(3544.5249,-3), - 2),它给出了更大的舍入误差,因此是不可取的。 - user
@Matija,从数学的角度来看,3544.5249舍入是3544.52,而不是3544.53,所以这段代码是正确的。如果你想要它在这个四舍五入到3544.53和像这样的情况(即使很难是不正确的),做这样的事情: number += 0.00011 - Bozidar Sikanjic


这里找到的答案都不正确。 @stinkycheeseman问道 围捕,你们都把数字四舍五入。

要整理,请使用:

Math.ceil(num * 100)/100;

53
2018-06-13 09:35



示例输入和输出显示,尽管问题是“向上舍入......”,但它实际上是“向...舍入”。 - JayDM
@stinkycheeseman指出了一个特定情况下的错误,他不想总是像ceil那样整理,他只想要0.005来舍入到0.01 - mjaggard
测试时发现奇怪的bug Math.ceil(1.1 * 100)/100;  - 返回 1.11,因为1.1 * 100是 110.00000000000001 根据全新的现代浏览器Firefox,Chrome,Safari和Opera ......旧时尚的IE仍然在思考 1.1*100=1100。 - skobaljic
@skobaljic试试 Math.ceil(num.toFixed(4) * 100) / 100 - treeface
@treeface Math.ceil((1.1).toFixed(4) * 100) / 100 也会回来 1.11 在Firefox中,现代浏览器的问题/错误是乘法运算,人们应该知道它(例如我曾经在彩票游戏中工作)。 - skobaljic


这个问题很复杂。

假设我们有一个功能, 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解释器中看到所有三个数字都相同。只是比较它们:

> 0.014999999999999999 === 0.0150000000000000001
true

所以当我写作 m = 0.0150000000000000001确切的价值 m我最终得到的更接近 0.01 比它更好 0.02。然而,如果我转换 m 到一个字符串......

> var m = 0.0150000000000000001;
> 的console.log(字符串(M));
0.015
> var m = 0.014999999999999999;
> 的console.log(字符串(M));
0.015

...我得到0.015,应该是0.02,这是明显的  我之前说过的56位小数,所有这些数字都完全等于。那么黑魔法是什么?

答案可以在ECMAScript规范的部分中找到 7.1.12.1:ToString应用于Number类型。这里转换一些数字的规则  放下一个字符串。关键部分是第5点,其中是一个整数 小号 生成的数字将在字符串表示中使用

ñķ,和 小号 是这样的整数 ķ ≥1,10ķ-1 ≤ 小号 <10ķ,数字值 小号 ×10ñ - ķ 是 ,和 ķ 尽可能小。请注意,k是十进制表示中的位数 小号那个 小号 不能被10整除,而且是最不重要的数字 小号 不一定由这些标准唯一确定。

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

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

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

  • 所表示的值本质上是离散的,例如,以第纳尔为单位的三位小数货币的货币数量。在这种情况下, 真正 一个数字的值,如0.015  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 库提供具有类似行为的函数,具有完全不同的实现。)

但是,如果你有第二种数字 - 从连续尺度中取得的值,那么没有理由认为具有较少小数位的近似十进制表示更多 准确 比那些更多?在那种情况下,我们  想要尊重字符串表示,因为该表示(如规范中所述)已经是排序的;我们不想错误地说“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);
}

53
2017-07-30 16:47



这在某些边缘情况下不起作用:尝试(的jsfiddle) roundStringNumberWithoutTrailingZeroes(362.42499999999995, 2)。预期结果(如在PHP中 echo round(362.42499999999995, 2)): 362.43。实际结果: 362.42 - Dr. Gianluigi Zane Zanettini
@ Dr.GianluigiZaneZanettini嗯。奇怪的。我不确定PHP的原因 round 给出362.43。这似乎是直觉错误,因为362.42499999999995小于362.425(在数学和代码中 - 362.42499999999995 < 362.425 在JS和PHP中都是如此)。 PHP的答案也没有最小化原始和圆形浮点数之间的距离,因为 362.43 - 362.42499999999995 > 362.42499999999995 - 362.42。根据 php.net/manual/en/function.round.php,PHP的 round 遵循C99标准;我将不得不冒险进入C领域,了解发生了什么。 - Mark Amery
看了之后 在PHP的源代码中实现舍入,我不知道它在做什么。这是一个非常复杂的实现,包括宏,分支和字符串转换以及硬编码魔术精度值的分支。我不确定该说什么,除了“PHP的答案明显错误,我们应该提交错误报告”。顺便问一下,你怎么找到号码362.42499999999995? - Mark Amery
@ Dr.GianluigiZaneZanettini我创建了一个错误报告: bugs.php.net/bug.php?id=75644 - Mark Amery
我实际上认为PHP是正确的,因为362.42499999999995以“5”结尾,所以数字必须“向上”,而不是“向下”。我有意义吗?无论如何,谢谢你的努力!我发现这个号码对价格适用了%折扣。 - Dr. Gianluigi Zane Zanettini


考虑 .toFixed() 和 .toPrecision()

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


51
2017-08-06 17:21



无论如何,toFixed都会将小数点添加到每个值。 - stinkycheeseman
这两个都没用 - Esailija
不幸的是,这两个函数都会添加@stinkycheeseman似乎不想要的额外小数位。 - jackwanders
stackoverflow.com/questions/566564/... - Shreedhar
它们返回字符串而不是数字,所以它们是格式化而不是计算.. - saimiris_devel


这是一个简单的方法:

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

42
2017-08-06 17:27



这应该是 投票最多 回答。 - jose.angel.jimenez
这个解决方案是错的,请参阅 stackoverflow.com/questions/38322372/... 如果您输入156893.145并使用上述功能进行舍入,则得到156893.14而不是156893.15 !!! - saimiris_devel