Format numbers in JavaScript similar to C#

后端 未结 18 2342
傲寒
傲寒 2020-11-22 12:26

Is there a simple way to format numbers in JavaScript, similar to the formatting methods available in C# (or VB.NET) via ToString(\"format_provider\") or

18条回答
  •  暖寄归人
    2020-11-22 12:48

    Here are some solutions, all pass the test suite, test suite and benchmark included, if you want copy and paste to test, try This Gist.

    Method 0 (RegExp)

    Base on https://stackoverflow.com/a/14428340/1877620, but fix if there is no decimal point.

    if (typeof Number.prototype.format === 'undefined') {
        Number.prototype.format = function (precision) {
            if (!isFinite(this)) {
                return this.toString();
            }
    
            var a = this.toFixed(precision).split('.');
            a[0] = a[0].replace(/\d(?=(\d{3})+$)/g, '$&,');
            return a.join('.');
        }
    }
    

    Method 1

    if (typeof Number.prototype.format1 === 'undefined') {
        Number.prototype.format1 = function (precision) {
            if (!isFinite(this)) {
                return this.toString();
            }
    
            var a = this.toFixed(precision).split('.'),
                // skip the '-' sign
                head = Number(this < 0);
    
            // skip the digits that's before the first thousands separator 
            head += (a[0].length - head) % 3 || 3;
    
            a[0] = a[0].slice(0, head) + a[0].slice(head).replace(/\d{3}/g, ',$&');
            return a.join('.');
        };
    }
    

    Method 2 (Split to Array)

    if (typeof Number.prototype.format2 === 'undefined') {
        Number.prototype.format2 = function (precision) {
            if (!isFinite(this)) {
                return this.toString();
            }
    
            var a = this.toFixed(precision).split('.');
    
            a[0] = a[0]
                .split('').reverse().join('')
                .replace(/\d{3}(?=\d)/g, '$&,')
                .split('').reverse().join('');
    
            return a.join('.');
        };
    }
    

    Method 3 (Loop)

    if (typeof Number.prototype.format3 === 'undefined') {
        Number.prototype.format3 = function (precision) {
            if (!isFinite(this)) {
                return this.toString();
            }
    
            var a = this.toFixed(precision).split('');
            a.push('.');
    
            var i = a.indexOf('.') - 3;
            while (i > 0 && a[i-1] !== '-') {
                a.splice(i, 0, ',');
                i -= 3;
            }
    
            a.pop();
            return a.join('');
        };
    }
    

    Example

    console.log('======== Demo ========')
    var n = 0;
    for (var i=1; i<20; i++) {
        n = (n * 10) + (i % 10)/100;
        console.log(n.format(2), (-n).format(2));
    }
    

    Separator

    If we want custom thousands separator or decimal separator, use replace():

    123456.78.format(2).replace(',', ' ').replace('.', ' ');
    

    Test suite

    function assertEqual(a, b) {
        if (a !== b) {
            throw a + ' !== ' + b;
        }
    }
    
    function test(format_function) {
        console.log(format_function);
        assertEqual('NaN', format_function.call(NaN, 0))
        assertEqual('Infinity', format_function.call(Infinity, 0))
        assertEqual('-Infinity', format_function.call(-Infinity, 0))
    
        assertEqual('0', format_function.call(0, 0))
        assertEqual('0.00', format_function.call(0, 2))
        assertEqual('1', format_function.call(1, 0))
        assertEqual('-1', format_function.call(-1, 0))
        // decimal padding
        assertEqual('1.00', format_function.call(1, 2))
        assertEqual('-1.00', format_function.call(-1, 2))
        // decimal rounding
        assertEqual('0.12', format_function.call(0.123456, 2))
        assertEqual('0.1235', format_function.call(0.123456, 4))
        assertEqual('-0.12', format_function.call(-0.123456, 2))
        assertEqual('-0.1235', format_function.call(-0.123456, 4))
        // thousands separator
        assertEqual('1,234', format_function.call(1234.123456, 0))
        assertEqual('12,345', format_function.call(12345.123456, 0))
        assertEqual('123,456', format_function.call(123456.123456, 0))
        assertEqual('1,234,567', format_function.call(1234567.123456, 0))
        assertEqual('12,345,678', format_function.call(12345678.123456, 0))
        assertEqual('123,456,789', format_function.call(123456789.123456, 0))
        assertEqual('-1,234', format_function.call(-1234.123456, 0))
        assertEqual('-12,345', format_function.call(-12345.123456, 0))
        assertEqual('-123,456', format_function.call(-123456.123456, 0))
        assertEqual('-1,234,567', format_function.call(-1234567.123456, 0))
        assertEqual('-12,345,678', format_function.call(-12345678.123456, 0))
        assertEqual('-123,456,789', format_function.call(-123456789.123456, 0))
        // thousands separator and decimal
        assertEqual('1,234.12', format_function.call(1234.123456, 2))
        assertEqual('12,345.12', format_function.call(12345.123456, 2))
        assertEqual('123,456.12', format_function.call(123456.123456, 2))
        assertEqual('1,234,567.12', format_function.call(1234567.123456, 2))
        assertEqual('12,345,678.12', format_function.call(12345678.123456, 2))
        assertEqual('123,456,789.12', format_function.call(123456789.123456, 2))
        assertEqual('-1,234.12', format_function.call(-1234.123456, 2))
        assertEqual('-12,345.12', format_function.call(-12345.123456, 2))
        assertEqual('-123,456.12', format_function.call(-123456.123456, 2))
        assertEqual('-1,234,567.12', format_function.call(-1234567.123456, 2))
        assertEqual('-12,345,678.12', format_function.call(-12345678.123456, 2))
        assertEqual('-123,456,789.12', format_function.call(-123456789.123456, 2))
    }
    
    console.log('======== Testing ========');
    test(Number.prototype.format);
    test(Number.prototype.format1);
    test(Number.prototype.format2);
    test(Number.prototype.format3);
    

    Benchmark

    function benchmark(f) {
        var start = new Date().getTime();
        f();
        return new Date().getTime() - start;
    }
    
    function benchmark_format(f) {
        console.log(f);
        time = benchmark(function () {
            for (var i = 0; i < 100000; i++) {
                f.call(123456789, 0);
                f.call(123456789, 2);
            }
        });
        console.log(time.format(0) + 'ms');
    }
    
    async = [];
    function next() {
        setTimeout(function () {
            f = async.shift();
            f && f();
            next();
        }, 10);
    }
    
    console.log('======== Benchmark ========');
    async.push(function () { benchmark_format(Number.prototype.format); });
    async.push(function () { benchmark_format(Number.prototype.format1); });
    async.push(function () { benchmark_format(Number.prototype.format2); });
    async.push(function () { benchmark_format(Number.prototype.format3); });
    next();
    

提交回复
热议问题