Formatting a number with exactly two decimals in JavaScript

后端 未结 30 2341
广开言路
广开言路 2020-11-21 06:29

I have this line of code which rounds my numbers to two decimal places. But I get numbers like this: 10.8, 2.4, etc. These are not my idea of two decimal places so how I can

30条回答
  •  無奈伤痛
    2020-11-21 07:03

    The trouble with floating point values is that they are trying to represent an infinite amount of (continuous) values with a fixed amount of bits. So naturally, there must be some loss in play, and you're going to be bitten with some values.

    When a computer stores 1.275 as a floating point value, it won't actually remember whether it was 1.275 or 1.27499999999999993, or even 1.27500000000000002. These values should give different results after rounding to two decimals, but they won't, since for computer they look exactly the same after storing as floating point values, and there's no way to restore the lost data. Any further calculations will only accumulate such imprecision.

    So, if precision matters, you have to avoid floating point values from the start. The simplest options are to

    • use a devoted library
    • use strings for storing and passing around the values (accompanied by string operations)
    • use integers (e.g. you could be passing around the amount of hundredths of your actual value, e.g. amount in cents instead of amount in dollars)

    For example, when using integers to store the number of hundredths, the function for finding the actual value is quite simple:

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

    With strings, you'll need rounding, but it's still manageable:

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

    Note that this function rounds to nearest, ties away from zero, while IEEE 754 recommends rounding to nearest, ties to even as the default behavior for floating point operations. Such modifications are left as an exercise for the reader :)

提交回复
热议问题