使用JavaScript将数字格式化为仅保留两位小数

647

我有一行代码可以将数字四舍五入到两位小数。但是,我得到的数字像这样:10.8、2.4等。这不是我认为的两位小数,请问如何改进以下代码?

Math.round(price*Math.pow(10,2))/Math.pow(10,2);

我希望得到像10.80、2.40这样的数字。使用jQuery也可以。


1
你的代码正是我所需要的(用于将浮点数精度减少到7位小数以生成较小的JSON文件)。为了提高速度,跳过Math.pow,使用以下代码:val = Math.round(val * 10000000) / 10000000); - Pawel
由于当前接受的答案(https://dev59.com/anI-5IYBdhLWcg3wsKl4#1726662)由于数字固有的严重不精确性(0.565、0.575、1.005)在广泛数值范围内给出了一个错误的结果,我可以建议再次查看这个答案(https://dev59.com/anI-5IYBdhLWcg3wsKl4#21323330),它可以得到正确的结果。 - T.J. Crowder
也许你想为JavaScript包含一个sprintf库。https://dev59.com/YXRB5IYBdhLWcg3weXOX - Thilo
3
在使用十进制位移和四舍五入方法正确地进行舍入后,您可以使用number.toFixed(x)方法将其转换为具有所需零数量的字符串。例如,使用跨浏览器方法将1.34舍入为1.3,然后添加1个零并使用1.3.toFixed(2)转换为字符串(以获取"1.30")。 - Edward
到了2020年,JavaScript中没有一种简单的本地方法可以轻松地将数字四舍五入。哇。 - Sliq
const formattedNumber = Math.round(number * 100) / 100; 将数字乘以100并四舍五入,然后再除以100,得到格式化后的数字。 - Hamza Dahmoun
32个回答

4

这是一个简单的例子

function roundFloat(num,dec){
    var d = 1;
    for (var i=0; i<dec; i++){
        d += "0";
    }
    return Math.round(num * d) / d;
}

使用方法如下:alert(roundFloat(1.79209243929,4));

点击此处查看示例。


3
浮点数的问题在于它们试图使用固定的位数来表示无限数量(连续)的值。因此,自然会有一些损失,并且某些值会受到影响。
当计算机将1.275存储为浮点值时,它实际上不会记住它是1.275还是1.27499999999999993,甚至是1.27500000000000002。这些值应该在保留两位小数后给出不同的结果,但由于对计算机而言它们看起来完全相同,所以它们不会,也没有办法恢复丢失的数据。任何进一步的计算都只会累积这种不精确性。
因此,如果精度很重要,就必须从一开始就避免使用浮点数。最简单的选择是:
  • 使用专用库
  • 使用字符串来存储和传递值(伴随字符串操作)
  • 使用整数(例如,您可以传递实际值的百分之一,例如以美分而不是美元的金额)
例如,当使用整数存储百分之一的数量时,查找实际值的函数非常简单:
function descale(num, decimals) {
    var hasMinus = num < 0;
    var numString = Math.abs(num).toString();
    var precedingZeroes = '';
    for (var i = numString.length; i <= decimals; i++) {
        precedingZeroes += '0';
    }
    numString = precedingZeroes + numString;
    return (hasMinus ? '-' : '') 
        + numString.substr(0, numString.length-decimals) 
        + '.' 
        + numString.substr(numString.length-decimals);
}

alert(descale(127, 2));

使用字符串,您仍然需要进行四舍五入,但它仍然可操作:

function precise_round(num, decimals) {
    var parts = num.split('.');
    var hasMinus = parts.length > 0 && parts[0].length > 0 && parts[0].charAt(0) == '-';
    var integralPart = parts.length == 0 ? '0' : (hasMinus ? parts[0].substr(1) : parts[0]);
    var decimalPart = parts.length > 1 ? parts[1] : '';
    if (decimalPart.length > decimals) {
        var roundOffNumber = decimalPart.charAt(decimals);
        decimalPart = decimalPart.substr(0, decimals);
        if ('56789'.indexOf(roundOffNumber) > -1) {
            var numbers = integralPart + decimalPart;
            var i = numbers.length;
            var trailingZeroes = '';
            var justOneAndTrailingZeroes = true;
            do {
                i--;
                var roundedNumber = '1234567890'.charAt(parseInt(numbers.charAt(i)));
                if (roundedNumber === '0') {
                    trailingZeroes += '0';
                } else {
                    numbers = numbers.substr(0, i) + roundedNumber + trailingZeroes;
                    justOneAndTrailingZeroes = false;
                    break;
                }
            } while (i > 0);
            if (justOneAndTrailingZeroes) {
                numbers = '1' + trailingZeroes;
            }
            integralPart = numbers.substr(0, numbers.length - decimals);
            decimalPart = numbers.substr(numbers.length - decimals);
        }
    } else {
        for (var i = decimalPart.length; i < decimals; i++) {
            decimalPart += '0';
        }
    }
    return (hasMinus ? '-' : '') + integralPart + (decimals > 0 ? '.' + decimalPart : '');
}

alert(precise_round('1.275', 2));
alert(precise_round('1.27499999999999993', 2));

请注意,此函数四舍五入到最近的整数,向远离零的方向,而IEEE 754建议浮点运算的默认行为是四舍五入到最近的整数,向偶数方向取整。这样的修改留给读者自己去尝试 :)

precise_round("999999999999999999.9999", 2) returns "1000000000000000000.00" - FaizanHussainRabbani
我希望它是 999999999999999999.99 - FaizanHussainRabbani
@FaizanHussainRabbani 1000000000000000000.00 是正确的四舍五入结果——9.9999 比 9.99 更接近 10.00。四舍五入是一个在数学中定义并标准化为 IEEE 754 计算的函数。如果你想得到不同的结果,你需要另一个函数。编写测试以指定各种输入所需的结果,并编写满足这些测试的代码。 - Imre

3

@heridev和我在jQuery中创建了一个小函数。

你可以尝试下面的代码:

HTML

<input type="text" name="one" class="two-digits"><br>
<input type="text" name="two" class="two-digits">​

jQuery

// apply the two-digits behaviour to elements with 'two-digits' as their class
$( function() {
    $('.two-digits').keyup(function(){
        if($(this).val().indexOf('.')!=-1){         
            if($(this).val().split(".")[1].length > 2){                
                if( isNaN( parseFloat( this.value ) ) ) return;
                this.value = parseFloat(this.value).toFixed(2);
            }  
         }            
         return this; //for chaining
    });
});

DEMO 在线演示: http://jsfiddle.net/c4Wqn/

2
我很感激您的贡献,但我认为将DOM元素和jQuery添加到其中似乎超出了问题的范围。 - Chris May
1
你不应该监听keyup事件,因为它看起来很糟糕,并且在使用脚本添加内容时不会激活。我更愿意监听“input”事件。这不会创建闪烁效果,并且在使用JS访问字段时也会触发。 - Le 'nton

2

2

使用这些示例在尝试将数字1.005四舍五入时仍会出现错误,解决方法是使用类似Math.js的库或此函数:

function round(value: number, decimals: number) {
    return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
}

将 9.90 转换为 9.9。 - Bowis

2
通常,十进制舍入是通过缩放实现的:round(num * p) / p 天真的实现
使用以下函数处理中间数字时,您有时会得到预期的上舍入值,有时会得到下舍入值,这取决于输入。
舍入中的不一致可能会在客户端代码中引入难以检测的错误。

function naiveRound(num, decimalPlaces) {
    var p = Math.pow(10, decimalPlaces);
    return Math.round(num * p) / p;
}

console.log( naiveRound(1.245, 2) );  // 1.25 correct (rounded as expected)
console.log( naiveRound(1.255, 2) );  // 1.25 incorrect (should be 1.26)

更好的实现
通过将数字转换为指数计数法的字符串形式,可以按预期四舍五入正数。但是,请注意,负数的舍入方式与正数不同。
实际上,它执行基本等同于"四舍五入"规则,您会发现round(-1.005, 2)计算结果为-1,即使round(1.005, 2)的计算结果为1.01lodash 的 _.round 方法使用此技术。

/**
 * Round half up ('round half towards positive infinity')
 * Uses exponential notation to avoid floating-point issues.
 * Negative numbers round differently than positive numbers.
 */
function round(num, decimalPlaces) {
    num = Math.round(num + "e" + decimalPlaces);
    return Number(num + "e" + -decimalPlaces);
}

// test rounding of half
console.log( round(0.5, 0) );  // 1
console.log( round(-0.5, 0) ); // 0

// testing edge cases
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) {
    num = Math.round(Math.abs(num) + "e" + decimalPlaces) * Math.sign(num);
    return Number(num + "e" + -decimalPlaces);
}

有一种不同的纯数学技术可以执行四舍五入(使用"远离零半调整"),在调用舍入函数之前应用epsilon校正
简单地说,我们在舍入之前将最小可能浮点值(= 1.0 ulp;最后一位单位)加到数字上。这将使数字后面的下一个可表示值向远离零的方向移动。

/**
 * Round half away from zero ('commercial' rounding)
 * Uses correction to offset floating-point inaccuracies.
 * Works symmetrically for positive and negative numbers.
 */
function round(num, decimalPlaces) {
    var p = Math.pow(10, decimalPlaces);
    var e = Number.EPSILON * num * p;
    return Math.round((num * p) + e) / p;
}

// test rounding of half
console.log( round(0.5, 0) );  // 1
console.log( round(-0.5, 0) ); // -1

// testing edge cases
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

这是为了抵消在编码十进制数时可能发生的隐式舍入误差,特别是那些最后一位小数为“5”的数字,例如1.005、2.675和16.235。实际上,在十进制系统中,1.005被编码为64位二进制浮点数的1.0049999999999999;而1234567.005被编码为64位二进制浮点数的1234567.0049999998882413
值得注意的是,最大二进制舍入误差取决于(1)数值的大小和(2)相对机器epsilon(2^-52)。

2

将您的小数值四舍五入,然后对其使用toFixed(x)函数以得到您期望的数字。

function parseDecimalRoundAndFixed(num,dec){
  var d =  Math.pow(10,dec);
  return (Math.round(num * d) / d).toFixed(dec);
}

调用

parseDecimalRoundAndFixed(10.800243929,4) => 10.80 parseDecimalRoundAndFixed(10.807243929,2) => 10.81


2

这是我的一行解决方案:Number((yourNumericValueHere).toFixed(2));

以下是具体步骤:

1)首先,将.toFixed(2)应用于您想要舍入小数位的数字。请注意,这将把该值从数字转换为字符串。因此,如果您正在使用Typescript,则会出现如下错误:

"Type 'string' is not assignable to type 'number'"

2)要获取数字值或将字符串转换为数字值,只需对所谓的“字符串”值应用Number()函数即可。

为了更好地理解,请看以下示例:

示例: 我有一个金额,其小数点后有5位数字,我想将其缩短到最多2位小数。我可以这样做:

var price = 0.26453;
var priceRounded = Number((price).toFixed(2));
console.log('Original Price: ' + price);
console.log('Price Rounded: ' + priceRounded);


1
我从几个月前的帖子中得到了一些想法,但是这里的答案以及其他帖子/博客的答案都无法处理所有情况(例如负数和一些我们的测试人员发现的“幸运数字”)。最终,我们的测试人员没有发现此方法存在任何问题。以下是我的代码片段:
fixPrecision: function (value) {
    var me = this,
        nan = isNaN(value),
        precision = me.decimalPrecision;

    if (nan || !value) {
        return nan ? '' : value;
    } else if (!me.allowDecimals || precision <= 0) {
        precision = 0;
    }

    //[1]
    //return parseFloat(Ext.Number.toFixed(parseFloat(value), precision));
    precision = precision || 0;
    var negMultiplier = value < 0 ? -1 : 1;

    //[2]
    var numWithExp = parseFloat(value + "e" + precision);
    var roundedNum = parseFloat(Math.round(Math.abs(numWithExp)) + 'e-' + precision) * negMultiplier;
    return parseFloat(roundedNum.toFixed(precision));
},

我也有一些代码注释(抱歉,我已经忘记了所有的细节)...我在这里发布我的答案供将来参考:

9.995 * 100 = 999.4999999999999
Whereas 9.995e2 = 999.5
This discrepancy causes Math.round(9.995 * 100) = 999 instead of 1000.
Use e notation instead of multiplying /dividing by Math.Pow(10,precision).

1
将以下内容放在某个全局作用域中:

在某个全局作用域中放置以下内容

Number.prototype.getDecimals = function ( decDigCount ) {
   return this.toFixed(decDigCount);
}

然后尝试

var a = 56.23232323;
a.getDecimals(2); // will return 56.23

更新

请注意,toFixed() 只能用于小数位数在 0-20 之间的数字,例如 a.getDecimals(25) 可能会生成 JavaScript 错误,因此您可以添加一些额外的检查来适应这种情况。

Number.prototype.getDecimals = function ( decDigCount ) {
   return ( decDigCount > 20 ) ? this : this.toFixed(decDigCount);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接