我想用JavaScript格式化价格。我想要一个函数,它将浮点作为参数,并返回如下格式的字符串:

"$ 2,500.00"

我该怎么做?


当前回答

许多答案都有有益的想法,但没有一个能满足我的需求。所以我使用了所有的想法,并构建了这个示例:

function Format_Numb(fmt){
    var decimals = isNaN(decimals) ? 2 : Math.abs(decimals);
    if(typeof decSgn === "undefined") decSgn = ".";
    if(typeof kommaSgn === "undefined") kommaSgn= ",";

    var s3digits = /(\d{1,3}(?=(\d{3})+(?=[.]|$))|(?:[.]\d*))/g;
    var dflt_nk = "00000000".substring(0, decimals);

    //--------------------------------
    // handler for pattern: "%m"
    var _f_money = function(v_in){
                       var v = v_in.toFixed(decimals);
                       var add_nk = ",00";
                       var arr = v.split(".");
                       return arr[0].toString().replace(s3digits, function ($0) {
                           return ($0.charAt(0) == ".")
                                   ? ((add_nk = ""), (kommaSgn + $0.substring(1)))
                                   : ($0 + decSgn);
                           })
                           + ((decimals > 0)
                                 ? (kommaSgn
                                       + (
                                           (arr.length > 1)
                                           ? arr[1]
                                           : dflt_nk
                                       )
                                   )
                                 : ""
                           );
                   }

    // handler for pattern: "%<len>[.<prec>]f"
    var _f_flt = function(v_in, l, prec){
        var v = (typeof prec !== "undefined") ? v_in.toFixed(prec) : v_in;
        return ((typeof l !== "undefined") && ((l=l-v.length) > 0))
                ? (Array(l+1).join(" ") + v)
                : v;
    }

    // handler for pattern: "%<len>x"
    var _f_hex = function(v_in, l, flUpper){
        var v = Math.round(v_in).toString(16);
        if(flUpper) v = v.toUpperCase();
        return ((typeof l !== "undefined") && ((l=l-v.length) > 0))
                ? (Array(l+1).join("0") + v)
                : v;
    }

    //...can be extended..., just add the function, for example:    var _f_octal = function( v_in,...){
    //--------------------------------

    if(typeof(fmt) !== "undefined"){
        //...can be extended..., just add the char, for example "O":    MFX -> MFXO
        var rpatt = /(?:%([^%"MFX]*)([MFX]))|(?:"([^"]*)")|("|%%)/gi;
        var _qu = "\"";
        var _mask_qu = "\\\"";
        var str = fmt.toString().replace(rpatt, function($0, $1, $2, $3, $4){
                      var f;
                      if(typeof $1 !== "undefined"){
                          switch($2.toUpperCase()){
                              case "M": f = "_f_money(v)";    break;

                              case "F": var n_dig0, n_dig1;
                                        var re_flt =/^(?:(\d))*(?:[.](\d))*$/;
                                        $1.replace(re_flt, function($0, $1, $2){
                                            n_dig0 = $1;
                                            n_dig1 = $2;
                                        });
                                        f = "_f_flt(v, " + n_dig0 + "," + n_dig1 + ")";    break;

                              case "X": var n_dig = "undefined";
                                        var re_flt = /^(\d*)$/;
                                        $1.replace(re_flt, function($0){
                                            if($0 != "") n_dig = $0;
                                        });
                                        f = "_f_hex(v, " + n_dig + "," + ($2=="X") + ")";    break;
                              //...can be extended..., for example:    case "O":
                          }
                          return "\"+"+f+"+\"";
                      } else if(typeof $3 !== "undefined"){
                          return _mask_qu + $3 + _mask_qu;
                      } else {
                          return ($4 == _qu) ? _mask_qu : $4.charAt(0);
                      }
                  });

        var cmd =     "return function(v){"
                +     "if(typeof v === \"undefined\")return \"\";"  // null returned as empty string
                +     "if(!v.toFixed) return v.toString();"         // not numb returned as string
                +     "return \"" + str + "\";"
                + "}";

        //...can be extended..., just add the function name in the 2 places:
        return new Function("_f_money,_f_flt,_f_hex", cmd)(_f_money,_f_flt,_f_hex);
    }
}

首先,我需要一个C样式格式的字符串定义,它应该是灵活的,但非常容易使用,我用以下方式定义它:;模式:

%[<len>][.<prec>]f   float, example "%f", "%8.2d", "%.3f"
%m                   money
%[<len>]x            hexadecimal lower case, example "%x", "%8x"
%[<len>]X            hexadecimal upper case, example "%X", "%8X"

因为对我来说,除了欧元之外,没有任何其他格式的需要,所以我只实现了“%m”。

但这很容易扩展。。。与C中一样,格式字符串是包含模式的字符串。例如,对于欧元:“%m€”(返回类似“8.129,33€”的字符串)

除了灵活性之外,我还需要一个处理表的快速解决方案。这意味着,在处理数千个单元格时,格式字符串的处理不能重复一次。我不接受类似“format(value,fmt)”的调用,但这必须分为两个步骤:

// var formatter = Format_Numb( "%m €");
// simple example for Euro...

//   but we use a complex example:

var formatter = Format_Numb("a%%%3mxx \"zz\"%8.2f°\"  >0x%8X<");

// formatter is now a function, which can be used more than once (this is an example, that can be tested:)

var v1 = formatter(1897654.8198344);

var v2 = formatter(4.2);

... (and thousands of rows)

同样为了提高性能,_f_money包含正则表达式;

第三,类似“format(value,fmt)”的调用是不可接受的,因为:

虽然可以用不同的掩码格式化不同的对象集合(例如,列的单元格),但我不想在处理时处理格式化字符串。此时,我只想使用格式,如

for(单元格中的var单元格){do_something(cell.col.formatter(cell.value));}

什么格式-可能是在.ini文件中,在XML中为每一列或其他地方定义的。。。,但是分析和设置格式或处理国际化完全是在另一个地方进行的,在那里我想将格式化程序分配给集合,而不考虑性能问题:

col.formatter=格式_字符串(_getFormatForColumn(…));

第四,我想要一个“宽容”的解决方案,因此传递例如字符串而不是数字应该只返回字符串,但“null”应该返回一个空字符串。

(如果值太大,格式化“%4.2f”也不能剪切。)

最后,但并非最不重要的是,它应该是可读的,易于扩展,而不会对性能产生任何影响。。。例如,如果某人需要“八进制值”,请参考带有“…可以扩展…”的行-我认为这应该是一项非常简单的任务。

我的整体重点是表现。每个“处理例程”(例如,_f_money)都可以被封装优化,或者在这个或其他线程中与其他思想进行交换,而无需更改“准备例程”(分析格式字符串和创建函数),这些例程只能被处理一次,从这个意义上说,与数千个数字的转换调用相比,性能并不那么关键。

对于所有喜欢数字方法的人来说:

Number.prototype.format_euro = (function(formatter){
    return function(){ return formatter(this); }})
(Format_Numb( "%m €"));

var v_euro = (8192.3282).format_euro(); // results: 8.192,33 €

Number.prototype.format_hex = (function(formatter){
    return function(){ return formatter(this); }})
(Format_Numb( "%4x"));

var v_hex = (4.3282).format_hex();

虽然我测试了一些,但代码中可能有很多错误。因此,它不是一个现成的模块,只是像我这样的非JavaScript专家的一个想法和起点。

该代码包含了许多StackOverflow帖子中修改过的想法;很抱歉,我不能引用所有这些,但感谢所有的专家。

其他回答

一个简单的选项,通过先反转字符串和基本正则表达式来正确放置逗号。

String.prototype.reverse = function() {
    return this.split('').reverse().join('');
};

Number.prototype.toCurrency = function( round_decimal /*boolean*/ ) {       
     // format decimal or round to nearest integer
     var n = this.toFixed( round_decimal ? 0 : 2 );

     // convert to a string, add commas every 3 digits from left to right 
     // by reversing string
     return (n + '').reverse().replace( /(\d{3})(?=\d)/g, '$1,' ).reverse();
};

这里有一些解决方案,都通过了测试套件。包括测试套件和基准测试。如果你想复制和粘贴来测试,试试这个要点。

方法0(RegExp)

它是基于VisioN的答案,但如果没有小数点,它会修复。

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

方法1

if (typeof Number.prototype.format === 'undefined') {
    Number.prototype.format = 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('.');
    };
}

方法2(拆分到阵列)

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]
            .split('').reverse().join('')
            .replace(/\d{3}(?=\d)/g, '$&,')
            .split('').reverse().join('');

        return a.join('.');
    };
}

方法3(循环)

if (typeof Number.prototype.format === 'undefined') {
    Number.prototype.format = 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('');
    };
}

用法示例

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

分离器

如果我们需要自定义千位分隔符或小数分隔符,请使用replace():

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

测试套件

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

基准

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

// If not using async, the browser will stop responding while running.
// This will create a new thread to benchmark
async = [];
function next() {
    setTimeout(function () {
        f = async.shift();
        f && f();
        next();
    }, 10);
}

console.log('======== Benchmark ========');
async.push(function () { benchmark_format(Number.prototype.format); });
next();

我喜欢VisionN的最短答案,除非我需要修改一个没有小数点的数字(123美元而不是123.00美元)。它不起作用,所以我需要破解JavaScript正则表达式的神秘语法,而不是快速复制/粘贴。

这是最初的解决方案

n.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');

我会再延长一点:

var re = /\d(?=(\d{3})+\.)/g;
var subst = '$&,';
n.toFixed(2).replace(re, subst);

此处的re部分(字符串替换中的搜索部分)表示

查找所有数字(\d)后跟(?=…)(展望)一个或多个组(…)+正好三位数(\d{3})以点(\.)结尾对所有事件执行此操作(g)

这里的子部分是指:

每次有匹配项时,将其替换为自身($&),后跟逗号。

当我们使用string.replace时,字符串中的所有其他文本保持不变,只有找到的数字(后面跟着3、6、9等其他数字的数字)才会得到一个额外的逗号。

因此,在数字1234567.89中,数字1和4满足条件(1234567.89),并被替换为“1”和“4”,从而得到1234567.89。

如果我们根本不需要美元金额的小数点(即123美元而不是123.00美元),我们可以这样更改正则表达式:

var re2 = /\d(?=(\d{3})+$)/g;

它依赖于行尾($)而不是点(\.),最后的表达式将是(请注意Fixed(0)):

n.toFixed(0).replace(/\d(?=(\d{3})+$)/g, '$&,');

此表达式将给出

1234567.89 -> 1,234,567

此外,您也可以选择单词边界(\b),而不是上面正则表达式中的行尾($)。

如果我误解了正则表达式处理的任何部分,我会提前道歉。

在将PHP number_format()转换为javascript之后,这对我很有用

function number_format(number, decimals = 0, dec_point = ".",thousands_sep = ",") {
    number = (number + '').replace(/[^0-9+\-Ee.]/g, '');
    var n = !isFinite(+number) ? 0 : +number,
        prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
        s = '',
        toFixedFix = function(n, prec)
        {
            var k = Math.pow(10, prec);
            return '' + (Math.round(n * k) / k).toFixed(prec);
        };
    // Fix for IE parseFloat(0.55).toFixed(0) = 0;
    s = (prec ? toFixedFix(n, prec) : '' + 
        Math.round(n)).split('.');
    if (s[0].length > 3)
    {
        s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, 
            thousands_sep);
    }
    if ((s[1] || '').length < prec)
    {
        s[1] = s[1] || '';
        s[1] += new Array(prec - s[1].length + 1).join('0');
    }
    return s.join(dec_point);
}

我想要一个自动返回小数部分的普通JavaScript解决方案。

function formatDollar(amount) {
    var dollar = Number(amount).toLocaleString("us", "currency");
    // Decimals
    var arrAmount = dollar.split(".");
    if (arrAmount.length==2) {
        var decimal = arrAmount[1];
        if (decimal.length==1) {
            arrAmount[1] += "0";
        }
    }
    if (arrAmount.length==1) {
        arrAmount.push("00");
    }

    return "$" + arrAmount.join(".");
}


console.log(formatDollar("1812.2");