數字格式化的 js 庫

彭加李 發表於 2022-06-26

數字格式化的 js 庫

Numeral.js,是一個用於格式化數字處理數字的 js 庫。

Tip:目前 Star 有 9.2k,5年以前就沒有在更新。其文件寫得不很清晰,比如它提供了多語言,但如何切換成中文,怎麼使用卻沒有說,對於某些問題(abbreviations 報錯)筆者只有從原始碼、更新日誌和 Issues 中尋找答案。

使用它

node 中通過 npm 安裝即可:

PS E:\react-project> npm i numeral

Tip:也可以在瀏覽器中直接通過 src 的方式使用。

建立例項

通過 numeral() 建立一個例項:

var myNumeral = numeral(1000);
var value = myNumeral.value();
// value 1000
console.log('value', value)

感覺好雞肋,但可以取消格式化。比如將 1,000 處理成 1000

var myNumeral2 = numeral('1,000');
var value2 = myNumeral2.value();
// value2 1000
console.log('value2', value2)

格式化

這部分是我們最關心的。比如筆者的需求有:

  • 將後臺的位元組數根據數值大小自動轉為 KBMBGB等對應的單位
  • 數字太長,顯示區域有限,比如將 123456789 轉為 123.5m123.5百萬

format()

通過 format() 可以將 1000 格式化成 1,000,將 123456789 格式化成 123,456,789。請看示例:

var number = numeral(1000).format('0,0');
// number:  1,000
console.log('number: ', number);
var number2 = numeral(123456789).format('0,0');
// number2:  123,456,789
console.log('number2: ', number2);
// 四捨五入
var number3 = numeral(1.93).format('0,0');
// number3:  2
console.log('number3: ', number3);
// 四捨五入
var number4 = numeral(1.23).format('0,0');
// number4:  1
console.log('number4: ', number4);

Tip:上面我們用了 0,0 這種格式,其他格式請直接看 numeral.js:

// node_modules\numeral\tests\numeral.js
describe('Format', function() {
    it('should format to a number', function() {
        var tests = [
                [0, null, '0'],
                [0, '0.00', '0.00'],
                [null, null, '0'],
                [NaN, '0.0', '0.0'],
                [1.23,'0,0','1'],
                [10000,'0,0.0000','10,000.0000'],
                [10000.23,'0,0','10,000'],
                [-10000,'0,0.0','-10,000.0'],
                [10000.1234,'0.000','10000.123'],
                [10000,'0[.]00','10000'],
                [10000.1,'0[.]00','10000.10'],
                [10000.123,'0[.]00','10000.12'],
                [10000.456,'0[.]00','10000.46'],
                [10000.001,'0[.]00','10000'],
                [10000.45,'0[.]00[0]','10000.45'],
                [10000.456,'0[.]00[0]','10000.456'],
                [10000,'(0,0.0000)','10,000.0000'],
                [-10000,'(0,0.0000)','(10,000.0000)'],
                [-12300,'+0,0.0000','-12,300.0000'],
                [1230,'+0,0','+1,230'],
                [1230,'-0,0','1,230'],
                [-1230,'-0,0','-1,230'],
                [-1230.4,'0,0.0+','1,230.4-'],
                [-1230.4,'0,0.0-','1,230.4-'],
                [1230.4,'0,0.0-','1,230.4'],
                [100.78, '0', '101'],
                [100.28, '0', '100'],
                [1.932,'0.0','1.9'],
                [1.9687,'0','2'],
                [1.9687,'0.0','2.0'],
                [-0.23,'.00','-.23'],
                [-0.23,'(.00)','(.23)'],
                [0.23,'0.00000','0.23000'],
                [0.67,'0.0[0000]','0.67'],
                [3162.63,'0.0[00000000000000]','3162.63'],
                [1.99,'0.[0]','2'],
                [1.0501,'0.00[0]','1.05'],
                [1.005,'0.00','1.01'],
                // leading zero
                [0, '00.0', '00.0'],
                [0.23, '000.[00]', '000.23'],
                [4, '000', '004'],
                [10, '00000', '00010'],
                [1000, '000,0', '1,000'],
                [1000, '00000,0', '01,000'],
                [1000, '0000000,0', '0,001,000'],
                // abbreviations
                [2000000000,'0.0a','2.0b'],
                [1230974,'0.0a','1.2m'],
                [1460,'0a','1k'],
                [-104000,'0 a','-104 k'],
                [999950,'0.0a','1.0m'],
                [999999999,'0a','1b'],
                // forced abbreviations
                [-5444333222111, '0,0 ak', '-5,444,333,222 k'],
                [5444333222111, '0,0 am', '5,444,333 m'],
                [-5444333222111, '0,0 ab', '-5,444 b'],
                [-5444333222111, '0,0 at', '-5 t'],
                [123456, '0.0[0] ak', '123.46 k'],
                [150,'0.0 ak','0.2 k']
            ],
            i,
            n,
            output;

        for (i = 0; i < tests.length; i++) {
            n = numeral(tests[i][0]);
            output = n.format(tests[i][1]);

            expect(output).to.equal(tests[i][2]);

            expect(typeof output).to.equal('string');
        }
    });
});

位元組轉換

比如將 1024 轉為 1KB,將 1024*1024 轉為 1MB。將請看示例:

PS D:\spug-study> node 
Welcome to Node.js v16.14.2.
Type ".help" for more information.
> let numeral = require('numeral')
undefined
> numeral(1024).format('0b')
'1KB'
> numeral(1024*1024).format('0b')
'1MB'
> numeral(1024*1024*1024).format('0b')
'1GB'
> numeral(1024*1024*1024*1024).format('0b')
'1TB'
> numeral(1024*1024*1024*1024*32).format('0b')
'35TB'
>

Tip:筆者直接在 node 環境下執行。更多格式化語法請看 bytes.js 檔案。

// node_modules\numeral\tests\formats\bytes.js
it('should format to bytes', function() {
    var decimal = 1000;
    var binary = 1024;
    var tests = [
            [0,'0b','0B'],
            [null,'0 b','0 B'],
            [100,'0b','100B'],
            [binary * 2,'0 ib','2 KiB'],
            [Math.pow(binary, 2) * 5,'0ib','5MiB'],
            [Math.pow(binary, 3) * 7.343,'0.[0] ib','7.3 GiB'],
            [Math.pow(binary, 4) * 3.1536544,'0.000ib','3.154TiB'],
            [Math.pow(binary, 5) * 2.953454534534,'0ib','3PiB'],
            [decimal * 2,'0 b','2 KB'],
            [Math.pow(decimal, 2) * 5,'0b','5MB'],
            [Math.pow(decimal, 3) * 7.343,'0.[0] b','7.3 GB'],
            [Math.pow(decimal, 4) * 3.1536544,'0.000b','3.154TB'],
            [Math.pow(decimal, 5) * 2.953454534534,'0b','3PB']
        ],
        i;

    for (i = 0; i < tests.length; i++) {
        expect(numeral(tests[i][0]).format(tests[i][1])).to.equal(tests[i][2]);
    }
});
位元組轉數字

比如可以將 1KB 轉為數字,但結果只是 1000,如果要 1024,需要使用 1 KiB。請看示例:

> numeral('1KB').value()
1000
> numeral('1 KiB').value()
1024
> numeral('1MB').value()
1000000

Tip:有關位元組反解析的更多介紹請看 bytes.js

// node_modules\numeral\tests\formats\bytes.js
it('should unformat to number', function() {
    var decimal = 1000;
    var binary = 1024;
    var tests = [
            ['0B', 0],
            ['0 B', 0],
            ['100B', 100],
            ['2 KiB', binary * 2],
            ['5MiB', Math.pow(binary, 2) * 5],
            ['7.3 GiB', Math.pow(binary, 3) * 7.3],
            ['3.154TiB', Math.pow(binary, 4) * 3.154],
            ['3PiB', Math.pow(binary, 5) * 3],
            ['2 KB', decimal * 2],
            ['5MB', Math.pow(decimal, 2) * 5],
            ['7.3 GB', Math.pow(decimal, 3) * 7.3],
            ['3.154TB', Math.pow(decimal, 4) * 3.154],
            ['3PB', Math.pow(decimal, 5) * 3]
        ],
        i;

    for (i = 0; i < tests.length; i++) {
        expect(numeral(tests[i][0]).value()).to.equal(tests[i][1]);
    }
});

時間轉換轉化

將數字(秒)轉為時間形式。請看示例:

> numeral(1).format('00:00:00')
'0:00:01'
> numeral(60).format('00:00:00')
'0:01:00'
> numeral(60*60).format('00:00:00')
'1:00:00'

百分比轉化

請看示例:

> numeral(0.974878234).format('0.000%')
'97.488%'
> numeral(0).format('0%')
'0%'
> numeral(1).format('0%')
'100%'

Tip:更多介紹請看 node_modules\numeral\tests\formats\percentage.js

貨幣轉化

請看示例:

> numeral(1000.234).format('$0,0.00')
'$1,000.23'

Tip:更多用法請看 currency.js

// node_modules\numeral\tests\formats\currency.js
describe('Currency', function() {
    after(function() {
        numeral.reset();
    });

    it('should format to currency', function() {
        var tests = [
                [0,'$0.00','$0.00'],
                [null,'$0.00','$0.00'],
                [0.99,'$0,0.00','$0.99'],
                [1000.234,'$0,0.00','$1,000.23'],
                [1001,'$ 0,0.[00]','$ 1,001'],
                [1000.234,'0,0.00 $','1,000.23 $'],
                [-1000.234,'0,0.00 $','-1,000.23 $'],
                [-1000.234,'($0,0)','($1,000)'],
                [-1000.234,'(0,0$)','(1,000$)'],
                [-1000.234,'(0,0 $)','(1,000 $)'],
                [-1000.234,'$0.00','-$1000.23'],
                [-1000.234,'$ 0.00','-$ 1000.23'],
                [1230974,'($0.00 a)','$1.23 m'],
                [-1000.234,'$ (0,0)','$ (1,000)'],
                [-1000.234,'$(0,0)','$(1,000)'],
                [-1000.234,'$ (0,0.00)','$ (1,000.23)'],
                [-1000.234,'$(0,0.00)','$(1,000.23)'],
                [-1000.238,'$(0,0.00)','$(1,000.24)'],
                [-1000.234,'$-0,0','$-1,000'],
                [-1000.234,'$ -0,0','$ -1,000'],
                [1000.234,'$ (0,0)','$ 1,000'],
                [1000.234,'$(0,0)','$1,000'],
                [1000.234,'$ (0,0.00)','$ 1,000.23'],
                [1000.234,'$(0,0.00)','$1,000.23'],
                [1000.238,'$(0,0.00)','$1,000.24'],
                [1000.234,'$-0,0','$1,000'],
                [1000.234,'$ -0,0','$ 1,000']
            ],
            i;

        for (i = 0; i < tests.length; i++) {
            expect(numeral(tests[i][0]).format(tests[i][1])).to.equal(tests[i][2]);
        }
    });

    it('should unformat to currency', function() {
        var tests = [
                ['$0.00', 0],
                ['$0.99', 0.99],
                ['$1,000.23', 1000.23],
                ['1,000.23 $', 1000.23],
                ['($1,000)', -1000],
                ['-1,000$', -1000],
                ['$1.23 m', 1230000],
            ],
            i;

        for (i = 0; i < tests.length; i++) {
            expect(numeral(tests[i][0]).value()).to.equal(tests[i][1]);
        }
    });
});

指數轉化

請看示例:

> numeral(77777777.1234).format('0.0e+0')
'7.8e+7'

Tip:更多用法請看 exponential.js

// node_modules\numeral\tests\formats\exponential.js
it('should format to exponential notation', function() {
    var tests = [
            [0,'0e+0','0e+0'],
            [null,'0e+0','0e+0'],
            [1,'0e+0','1e+0'],
            [77.1234,'0.0e+0','7.7e+1'],
            [0.000000771234,'0.0e-0','7.7e-7'],
            [-0.000000771234,'0.00e-0','-7.71e-7'],
            [77.1234,'0.000e+0','7.712e+1'],
            [-1000830298,'0.0[000]e+0','-1.0008e+9']
        ],
        i;

    for (i = 0; i < tests.length; i++) {
        expect(numeral(tests[i][0]).format(tests[i][1])).to.equal(tests[i][2]);
    }
});

位元率轉化

位元率,是指單位時間內傳送的位元(bit)數,單位為bps(bit per second)。

> numeral(.0056).format('0 BPS')
'56 BPS'

Tip:更多用法請看 bps.js

// node_modules\numeral\tests\formats\bps.js
it('should format to bps', function() {
    var tests = [
            [0,'0 BPS','0 BPS'],
            [0.0001, '0 BPS', '1 BPS'],
            [.0056, '0 BPS', '56 BPS'],
            [.25, '0BPS', '2500BPS'],
            [.000001, '0.00 BPS', '0.01 BPS']
        ],
        i;

    for (i = 0; i < tests.length; i++) {
        expect(numeral(tests[i][0]).format(tests[i][1])).to.equal(tests[i][2]);
    }
});

四則運算

numeral 提供加減乘除四則運算的方法,例如 100010 等於 1010。請看示例:

> numeral(1000).add(10).value()
1010

Tip:更多介紹請看 numeral.js

// node_modules\numeral\tests\numeral.js
describe('Manipulate', function() {
    // 加法
    describe('Add', function() {
        it('should add', function() {
            var tests = [
                    [1000,10,1010],
                    [0.5,3,3.5],
                    [-100,200,100],
                    [0.1,0.2,0.3],
                    [0.28,0.01,0.29],
                    [0.289999,0.000001,0.29],
                    [0.29,0.01,0.3]
                ],
                num;

            for (var i = 0; i < tests.length; i++) {
                num = numeral(tests[i][0]);

                num.add(tests[i][1]);

                expect(num.value()).to.equal(tests[i][2]);
            }
        });
    });

    // 減法
    describe('Subtract', function() {
        it('should subtract', function() {
            var tests = [
                    [1000,10,990],
                    [0.5,3,-2.5],
                    [-100,200,-300],
                    [0.3,0.1,0.2],
                    [0.28,0.01,0.27],
                    [0.29,0.01,0.28]
                ],
                num;

            for (var i = 0; i < tests.length; i++) {
                num = numeral(tests[i][0]);

                num.subtract(tests[i][1]);

                expect(num.value()).to.equal(tests[i][2]);
            }
        });
    });

    // 乘法
    describe('Multiply', function() {
        it('should multiply', function() {
            var tests = [
                    [1000,10,10000],
                    [0.5,3,1.5],
                    [-100,200,-20000],
                    [0.1,0.2,0.02],
                    [0.28,0.01,0.0028],
                    [0.29,0.01,0.0029],
                    [0.00000231,10000000,23.1]
                ],
                num;

            for (var i = 0; i < tests.length; i++) {
                num = numeral(tests[i][0]);

                num.multiply(tests[i][1]);

                expect(num.value()).to.equal(tests[i][2]);
            }
        });
    });

    // 除法
    describe('Divide', function() {
        it('should divide', function() {
            var tests = [
                    [1000,10,100],
                    [0.5,3,0.16666666666666666],
                    [-100,200,-0.5],
                    [5.3,0.1,53],
                    [0.28,0.01,28],
                    [0.29,0.01,29]
                ],
                num;

            for (var i = 0; i < tests.length; i++) {
                num = numeral(tests[i][0]);

                num.divide(tests[i][1]);

                expect(num.value()).to.equal(tests[i][2]);
            }
        });
    });

    // 差值。例如 1000 和 10 相差 990。
    describe('Difference', function() {
        it('should find a difference', function() {
            var tests = [
                [1000,10,990],
                [0.5,3,2.5],
                [-100,200,300],
                [0.3,0.2,0.1],
                [0.28,0.01,0.27],
                [0.29,0.01,0.28]
            ],
            num;

            for (var i = 0; i < tests.length; i++) {
                num = numeral(tests[i][0]);

                expect(num.difference(tests[i][1])).to.equal(tests[i][2]);
            }
        });
    });

    // 四捨五入。
    describe('Rounding', function() {
        it('should format with rounding', function() {
            var tests = [
                    // value, format string, expected w/ floor, expected w/ ceil
                    [2280002, '0.00a', '2.28m', '2.29m'],
                    [10000.23,'0,0','10,000', '10,001'],
                    [1000.234,'0,0.00','1,000.23', '1,000.24'],
                    [0.97487823,'0.000','0.974','0.975'],
                    [-0.433,'0.0','-0.5', '-0.4']
                ],
                i;

            for (i = 0; i < tests.length; i++) {
                // floor
                expect(numeral(tests[i][0]).format(tests[i][1], Math.floor)).to.equal(tests[i][2]);

                // ceil
                expect(numeral(tests[i][0]).format(tests[i][1], Math.ceil)).to.equal(tests[i][3]);
            }
        });
    });
});

多語言

需求:數字很長時,由於排版的考慮,需要將數字顯示的更短,轉為指數勉強可以,但對很多人還是不友好,如果能轉為 123.5百萬 這種形式就完美了。

numeral 提供了縮寫,比如可以將 1230974 轉為 1.2m(m,指百萬):

// node_modules\numeral\tests\numeral.js
...
// abbreviations
[2000000000,'0.0a','2.0b'],
[1230974,'0.0a','1.2m'],
[1460,'0a','1k'],
[-104000,'0 a','-104 k'],
[999950,'0.0a','1.0m'],
[999999999,'0a','1b'],

請看示例:

const numeral = require('numeral');
var number5 = numeral(123456789).format('0.0a')
// 123.5m
console.log('number5: ', number5);

123.5m 是多少,能否轉為中文?筆者在 chs.js 中找到如下程式碼:

// node_modules\numeral\locales\chs.js

numeral.register('locale', 'chs', {
    delimiters: {
        thousands: ',',
        decimal: '.'
    },
    abbreviations: {
        thousand: '千',
        million: '百萬',
        billion: '十億',
        trillion: '兆'
    },
    ordinal: function (number) {
        return '.';
    },
    currency: {
        symbol: '¥'
    }
});

通過引入 chs 並切換成中文,成功將 123456789 轉為 123.5百萬。請看示例:

const numeral = require('numeral');
// 載入中文
+ require('numeral/locales/chs')
// 切換成中文
+ numeral.locale('chs')
const number5 = numeral(123456789).format('0.0a')
// number5:  123.5百萬
console.log('number5: ', number5);

Tip:對於單頁面專案,在其他元件中若需要使用英文,比如不要 123.5百萬,而要 123.5m,直接切換語言(numeral.locale('en'))即可。就像這樣:

...
const number5 = numeral(123456789).format('0.0a')
// number5:  123.5百萬
console.log('number5: ', number5);

+ numeral.locale('en')
const number6 = numeral(123456789).format('0.0a')

// number6:  123.5m
console.log('number6: ', number6);

筆者的示例

import React from 'react';
const numeral = require('numeral');
require('numeral/locales/chs')
numeral.locale('chs')

function Test() {
  const number = numeral(1000).format('0,0');
  // number:  1,000
  console.log('number: ', number);
  const number2 = numeral(123456789).format('0,0');
  // number2:  123,456,789
  console.log('number2: ', number2);
  // 四捨五入
  const number3 = numeral(1.93).format('0,0');
  // number3:  2
  console.log('number3: ', number3);
  // 四捨五入
  const number4 = numeral(1.23).format('0,0');
  // number4:  1
  console.log('number4: ', number4);
  
  const number5 = numeral(123456789).format('0.0a')
  // number5:  123.5百萬
  console.log('number5: ', number5);

  numeral.locale('en')
  const number6 = numeral(123456789).format('0.0a')
  
  // number6:  123.5m
  console.log('number6: ', number6);
  return (
    <div>
      react 專案
    </div>
  )
}

export default Test