如何使用逗号将数字格式化为千位分隔符?

2655

我想在 JavaScript 中使用逗号作为千位分隔符打印整数。例如,我想将数字 1234567 显示为“1,234,567”。我该如何做到这一点?

我是这样做的:

function numberWithCommas(x) {
    x = x.toString();
    var pattern = /(-?\d+)(\d{3})/;
    while (pattern.test(x))
        x = x.replace(pattern, "$1,$2");
    return x;
}

console.log(numberWithCommas(1000))

有没有更简单或更优雅的方法来做到这一点?如果它也可以处理浮点数,那就太好了,但这并不是必需的。它不需要是特定于区域设置的,以决定使用句点还是逗号。


70
值得注意的是,即使在2016年,Number.prototype.toLocaleString 在Safari浏览器中仍然不起作用。它只是返回数字本身,而没有错误抛出。由于这个问题,今天我感到非常无语... #goodworkApple - aendra
toLocaleString 不一致,不应使用。例如,在 Firefox 上,它将返回 1,234,但在 IE 上,它将添加小数:1,234.00。 - Bort
截至2023年,“toLocaleString”在Safari上似乎工作正常,我在Chrome、Safari和Firefox之间看到了一致的结果。 - Andrew
51个回答

5

如果您正在寻找简短而有效的解决方案:

const number = 12345678.99;

const numberString = String(number).replace(
    /^\d+/,
    number => [...number].map(
        (digit, index, digits) => (
            !index || (digits.length - index) % 3 ? '' : ','
        ) + digit
    ).join('')
);

// numberString: 12,345,678.99

这是告诉我“未捕获的类型错误:数字不可迭代”。也许您需要在数字上调用toString - Elias Zamaria
@EliasZamaria 抱歉,我在我的情况下使用了一个字符串。我更新了我的答案以将其转换为字符串。 - Robo Robok
我尝试使用12.34作为数字,但它返回了12,.34 - Elias Zamaria
我原以为它只能处理小数。已经为您更新。 - Robo Robok

4

已经有很多好的答案了。这里再来一个,只是为了好玩:

function format(num, fix) {
    var p = num.toFixed(fix).split(".");
    return p[0].split("").reduceRight(function(acc, num, i, orig) {
        if ("-" === num && 0 === i) {
            return num + acc;
        }
        var pos = orig.length - i - 1
        return  num + (pos && !(pos % 3) ? "," : "") + acc;
    }, "") + (p[1] ? "." + p[1] : "");
}

一些例子:

format(77.03453, 2); // "77.03"
format(78436589374); // "78,436,589,374"
format(784, 4);      // "784.0000"
format(-123456);     // "-123,456"

这个无法处理 format(-123456),它会输出 -,123,456 - wrossmck
1
固定的(虽然可能有更优雅的方法来做,而不需要每次都检查符号)。无论如何,更新使得它可以处理负数。 - Wayne

3

与 @elias-zamaria 和 @t.j.crowder 相关

针对 Safari 浏览器的负向回顾不能使用 <。所以,应该使用 (?!\.\d*)

function numberWithCommas(n) {
  return n.toString().replace(/\B(?!\.\d*)(?=(\d{3})+(?!\d))/g, ",");
}

它适用于Safari和Firefox浏览器


3

我在Aki143S的解决方案中添加了 toFixed 方法。该解决方案使用点作为千位分隔符,使用逗号表示小数精度。

function formatNumber( num, fixed ) { 
    var decimalPart;

    var array = Math.floor(num).toString().split('');
    var index = -3; 
    while ( array.length + index > 0 ) { 
        array.splice( index, 0, '.' );              
        index -= 4;
    }

    if(fixed > 0){
        decimalPart = num.toFixed(fixed).split(".")[1];
        return array.join('') + "," + decimalPart; 
    }
    return array.join(''); 
};

示例:

formatNumber(17347, 0)  = 17.347
formatNumber(17347, 3)  = 17.347,000
formatNumber(1234563.4545, 3)  = 1.234.563,454

3

我使用正则表达式true实现了仅有的解决方案适合那些喜欢一行代码的人

你看到上面那些充满热情的选手了吗?也许你可以从中学习。这是我的方法。

n => `${n}`.replace(/(?<!\.\d+)\B(?=(\d{3})+\b)/g, " ").replace(/(?<=\.(\d{3})+)\B/g, " ")

在技术领域,使用千分位分隔符时建议采用国际单位制标准的第八版《国际单位制(SI)手册》第5.3.4节规定的细间距(U+2009)。但是,第九版《SI手册》第5.4.4节建议使用空格,不过也可以使用逗号或其他符号。


请参阅。

const integer_part_only = n => `${n}`.replace(/(?<!\.\d+)\B(?=(\d{3})+\b)/g, " I ");
const fractional_part_only = n => `${n}`.replace(/(?<=\.(\d{3})+)\B/g, " F ");
const both = n => fractional_part_only(integer_part_only(n));

function demo(number) { // I’m using Chrome 74.
 console.log(`${number}
  → "${integer_part_only(number)}" (integer part only)
  → "${fractional_part_only(number)}" (fractional part only)
  → "${both(number)}" (both)
 `);
}
demo(Math.random() * 10e5);
demo(123456789.01234567);
demo(123456789);
demo(0.0123456789);

它是如何工作的?

对于整数部分

.replace(/(?<!\.\d+)\B(?=(\d{3})+\b)/g, " I ")
  • .replace(……, " I ") 将其替换为"I"
  • /……/g 逐个搜索
    • \B 两个相邻数字之间的位置
      • (?=……)正向前瞻 其右侧部分为
        • (\d{3})+ 一个或多个三位数字块
        • \b 后面是非数字字符,如句号、字符串结尾等
      • (?<!……)负向后顾 排除左侧部分为
        • \.\d+ 跟随小数点的数字(“有小数点”)。

对于小数部分

.replace(/(?<=\.(\d{3})+)\B/g, " F ")
  • .replace(……, " F ") 将匹配到的内容替换为“ F ”。
  • /……/g 在每个匹配项中使用。
    • \B 表示两个相邻数字之间的间隔。
      • (?<=……)正向先行断言,其左侧部分是:
        • \. 十进制分隔符
        • (\d{3})+ 由一个或多个三位数字块组成。

字符类边界

\d

匹配任意数字(阿拉伯数字)。等效于 [0-9]

例如,

  • /\d//[0-9]/ 可以匹配 B2 is the suite number 中的 2

\b

匹配单词边界。即一个单词字符的前面或后面没有另一个单词字符,例如在字母和空格之间。注意,匹配的单词边界不包括在匹配结果中。换句话说,匹配的单词边界长度为零。

例如:

  • /\bm/ 匹配 moon 中的 m
  • /oo\b/ 不匹配 moon 中的 oo,因为 oo 后面跟着一个单词字符 n
  • /oon\b/ 匹配 moon 中的 oon,因为 oon 是字符串结尾,后面没有单词字符;
  • /\w\b\w/ 永远不会匹配任何内容,因为单词字符不能同时跟着非单词字符和单词字符。

\B

匹配非单词边界。即前一个字符和后一个字符都是同一类型:要么都是单词字符,要么都不是。例如两个字母之间或两个空格之间。字符串的开头和结尾都被视为非单词。与匹配的单词边界一样,匹配的非单词边界也不包括在匹配结果中。

例如:

  • /\Bon/ 匹配 at noon 中的 on
  • /ye\B/ 匹配 possibly yesterday 中的 ye

浏览器兼容性


2

在这里没有找到现代和全面的解决方案后,我编写了一个箭头函数(无需正则表达式)来解决格式问题,并允许调用者提供小数位数以及欧洲和世界其他地区的千位分隔符和小数点分隔符。

Examples:

numberFormatter(1234567890.123456) => 1,234,567,890
numberFormatter(1234567890.123456, 4) => 1,234,567,890.1235
numberFormatter(1234567890.123456, 4, '.', ',') => 1.234.567.890,1235 Europe

这是用ES6(现代语法)编写的函数:

const numberFormatter = (number, fractionDigits = 0, thousandSeperator = ',', fractionSeperator = '.') => {
    if (number!==0 && !number || !Number.isFinite(number)) return number
    const frDigits = Number.isFinite(fractionDigits)? Math.min(Math.max(fractionDigits, 0), 7) : 0
    const num = number.toFixed(frDigits).toString()

    const parts = num.split('.')
    let digits = parts[0].split('').reverse()
    let sign = ''
    if (num < 0) {sign = digits.pop()}
    let final = []
    let pos = 0

    while (digits.length > 1) {
        final.push(digits.shift())
        pos++
        if (pos % 3 === 0) {final.push(thousandSeperator)}
    }
    final.push(digits.shift())
    return `${sign}${final.reverse().join('')}${frDigits > 0 ? fractionSeperator : ''}${frDigits > 0 && parts[1] ? parts[1] : ''}`
}

它已经被测试过对于负面、不良输入和 NaN 情况。如果输入是 NaN,则简单返回它。


2

来自@user1437663的解决方案非常棒。

真正理解该解决方案的人正在准备理解复杂的正则表达式。

为了使其更易读,可以进行小小的改进:

function numberWithCommas(x) {
    var parts = x.toString().split(".");
    return parts[0].replace(/\B(?=(\d{3})+(?=$))/g, ",") + (parts[1] ? "." + parts[1] : "");
}

该模式以 \B 开始,以避免在单词开头使用逗号。有趣的是,该模式返回为空,因为 \B 不会推进“光标”(同样适用于 $)。 \B 后面跟着一个不太常见的资源,但它是 Perl 正则表达式中的一个强大功能。
            Pattern1 (? = (Pattern2) ).

奥妙在于括号中的内容(Pattern2)是一个模式,它遵循先前的模式(Pattern1),但不会推进光标,也不是返回的模式的一部分。这是一种未来模式。就像有人向前看,但实际上并没有走一样!

在这种情况下,pattern2

\d{3})+(?=$)

这意味着字符串结尾($)之前有一个或多个三位数字。

最后,Replace 方法将所有找到的模式(空字符串)更改为逗号。只有在剩余部分是三位数的倍数的情况下才会发生这种情况(这种情况是指光标将来到原点的末尾时)。


2

var floatingNumber = 1000000.06665656676709;
var doubleNumber = floatingNumber.toFixed(2);
var athousand = parseFloat(doubleNumber).toLocaleString();
document.write(athousand);


1
我已经修改了您的代码,使其适用于文本框(输入类型为"text"),因此我们可以实时输入和删除数字而不会失去光标。如果您选择范围进行删除,它也可以正常工作。您还可以自由使用箭头和home/end按钮。
感谢您节省我的时间!
//function controls number format as "1,532,162.3264321"
function numberWithCommas(x) {
    var e = e || window.event;
    if (e.keyCode >= '35' && e.keyCode <= '40') return; //skip arrow-keys
    var selStart = x.selectionStart, selEnd = x.selectionEnd; //save cursor positions
    var parts = x.value.toString().split(".");
    var part0len = parts[0].length; //old length to check if new ',' would be added. Need for correcting new cursor position (+1 to right).

    //if user deleted ',' - remove previous number instead (without selection)
    if (x.selectionLength == 0 && (e.keyCode == 8 || e.keyCode == 46)) {//if pressed 8-backspace or 46-delete button
        var delPos = parts[0].search(/\d{4}/);
        if (delPos != -1) {//if found 4 digits in a row (',' is deleted)
            if (e.keyCode == 8) {//if backspace flag
                parts[0] = parts[0].slice(0, selStart - 1) + parts[0].slice(selEnd, parts[0].length);
                selEnd--;
                if (selStart > selEnd) selStart = selEnd;
            } else {
                parts[0] = parts[0].slice(0, selStart) + parts[0].slice(selEnd + 1, parts[0].length);
                selStart++;
                if (selEnd < selStart) selEnd = selStart;
            }
        }
    }

   var hasMinus = parts[0][0] == '-';
   parts[0] = (hasMinus ? '-' : '') + parts[0].replace(/[^\d]*/g, ""); //I'd like to clear old ',' to avoid things like 1,2,3,5,634.443216
   parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); //sets ',' between each 3 digits
   if (part0len < parts[0].length) { //move cursor to right if added new ','
       selStart++;
       selEnd++;
   } else if (part0len > parts[0].length) { //..or if removed last one ','
       selStart--;
       selEnd--;
   }
   x.value = parts.join(".");
   x.setSelectionRange(selStart, selEnd); //restoring cursor position
}
function saveSelectionLength(x) {
    x.selectionLength = x.selectionEnd - x.selectionStart;
}

要使用此功能,只需添加两个事件 - onKeyUp 和 onKeyDown

<asp:TextBox runat="server" ID="val" Width="180px" onKeyUp="numberWithCommas(this);" onKeyDown="saveSelectionLength(this);"/>

1
这是一个少编程的好解决方案...
var y = "";
var arr = x.toString().split("");
for(var i=0; i<arr.length; i++)
{
    y += arr[i];
    if((arr.length-i-1)%3==0 && i<arr.length-1) y += ",";
}

这无法处理“-123456”,它会给出“-1,234,56”。 - wrossmck
当输入为-123456时,仍然无法正常工作,它会输出-,123,456。http://jsfiddle.net/wrossmck/2R8mD/1/ - wrossmck
2
这只是疯狂的 :) - Petr Havlik
哈哈,我同意Petr的看法,虽然它很有趣。 - Shawn J. Molloy
这允许像abcdef等字符,这些字符应该受到限制。 - Debashis
你也可以通过循环 x.toString().length 并使用 x[i] 进行遍历。 - Maor Ben

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