小墨の博客

梦想需要付诸行动,否则只能是梦

JavaScript 数字添加千分位

前几天,在公司项目中遇到一个给金额添加千分位的需求,于是一直想着有空来整理一下,一直没抽出时间,今天有空,整理一下。


2023.09.11更新:前端有自带的方法,我是看到这篇博客的时候才知道的:

js处理金额显示格式(最简单)_js 金额格式_@前端小菜的博客-CSDN博客

整理如下:

function moneyFormats(value) {
    return Number(value).toLocaleString('zh', { style: 'currency', currency: 'CNY', minimumFractionDigits: 2 });
}
moneyFormats('555555.445')
// '¥555,555.45'
moneyFormats('88888888888888888888888888888')
'¥88,888,888,888,888,900,000,000,000,000.00'

/*
 * Number.toLocaleString() 第二个参数:
 * style: 格式化时使用的样式(默认"decimal")
 *   👉 "decimal"表示纯数字格式, "currency"表示货币格式, "percent"表示百分比格式 
 * currency: 在货币格式化中使用的货币符号(没有默认值,如果样式是"currency",必须提供货币属性)
 *   👉 "USD" 表示美元, "EUR" 表示欧元, "CNY" 表示人民币
 * minimumFractionDigits: 使用的小数位数的最小数目
 *   👉 可能的值是从0到20,默认为普通的数字和百分比格式为0
 */

这个方法有个小问题,Number(value)转为数字格式的时候,会有有效位数的问题。

不过换个角度想想,好像也没这么大金额的,如果确认金额不带小数的话,可以这样(当然带小数转BigInt就会报错了):

function moneyFormats(value) {
    return BigInt(value).toLocaleString('zh', { style: 'currency', currency: 'CNY', minimumFractionDigits: 2 });
}
// moneyFormats('555555.445')
// 报错 Uncaught SyntaxError: Cannot convert 555555.445 to a BigInt
moneyFormats('88888888888888888888888888888')
// '¥88,888,888,888,888,888,888,888,888,888.00'

那么,可以灵活处理一下,反正小数部分也是照搬下来(四舍五入一下),所以只用处理整数

(上班中午休息的时候手写的,简单测试了下没发现问题,如果有问题再说)

const fractionalCount = 3 // 保留x位小数
function moneyFormats(num) {
    if (isNaN(num)) {
        return '-'
    }
    // 将 数字/字符串类型的数字 转换为 字符串,并按小数点拆分为整数部分和小数部分
    let parts = (num + "").split(".")
    if (parts.length > 2) {
        return "-" // 不是数字
    }
    // 整数部分
    let integerPart = BigInt(parts[0]).toLocaleString('zh', { style: 'currency', currency: 'CNY', minimumFractionDigits: 0 })
    // 小数部分(考虑精度问题就稍微麻烦一点,核心思路就是按要保留小数位数截断,然后多取1位判断是否需要进位)
    let fractionalPart = ""
    if (fractionalCount > 0) {
        if (parts.length > 1) {
            let fLen = parts[1].length
            if (fLen > fractionalCount) { // 需要四舍五入
                if (parts[1][fractionalCount] >= 5) { // 需要进位
                    let fractionalBigInt = BigInt(parts[1].substring(0, fractionalCount))
                    fractionalPart = (fractionalBigInt + 1n).toString()
                } else { // 不需要进位
                    fractionalPart = parts[1].substring(0, fractionalCount)
                }
            } else { // 需要在后面补 0 
                let zeroCount = fractionalCount - fLen
                fractionalPart = parts[1] + new Array(zeroCount).fill('0').join('')
            }
        } else {
            // 小数部分全是 0
            fractionalPart = parts[1] + new Array(fractionalCount).fill('0').join('')
        }
        fractionalPart = '.' + fractionalPart
    }
    // console.log('fractionalPart', fractionalPart)

    return integerPart + fractionalPart
}
moneyFormats('888888888888888888888888888888888888888888.444444444444444444445')
// '¥888,888,888,888,888,888,888,888,888,888,888,888,888,888.444'
moneyFormats('00000111111.4')
// '¥111,111.400'
moneyFormats('00000111111.95')
// '¥111,111.950'




在网上简单搜了一下,主要有几种方式,比较常规的代码判断这里就不再重复了,主要是想整理一下正则的处理方案。


项目中用到了echarts,感觉应该echarts中应该会有相关的功能。大胆猜想,如果echarts也使用了正则处理这个逻辑,那\d肯定会出现在正则中,同时每三位添加一个逗号,那正则中很有可能出现\d{3}之类的部分。果不其然,一搜,发现真有。

image.png

这两个就是从echarts中摘出来的代码:

o.addCommas=function(t){return isNaN(t)?"-":(t=(t+"").split("."),t[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g,"$1,")+(t.length>1?"."+t[1]:""))}
function tu(t){return isNaN(t)?"-":(t=(t+"").split("."))[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g,"$1,")+(1<t.length?"."+t[1]:"")}

image.png

image.png

分析了一下,其实这两个代码功能是一样的,只是可能用的打包工具有些差异,所以最终压缩后的代码不完全一样。


我把上面这个函数美化了一下,加了点注释,代码逻辑是等价的

function addCommas(num) {
    if (isNaN(num)) {
        return '-'
    }
    // 将 数字/字符串类型的数字 转换为 字符串,并按小数点拆分为整数部分和小数部分
    let parts = (num + "").split(".")
    // 整数部分 从右往左匹配
    let integerPart = parts[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g, "$1,")
    // 小数部分
    let fractionalPart = parts.length > 1 ? "." + parts[1] : ""
    return integerPart + fractionalPart
}

这样就一目了然了,如果想炫技的话,可以写到一行:

function addCommas(num) {
    return isNaN(num) ? "-" : (num = (num + "").split("."))[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g, "$1,") + (num.length > 1 ? "." + num[1] : "")
}


上面这种更适合严谨一点的需求,因为他同时带一些校验,比如非数字字符串等等。


如果能够确保入参是数字,数字字符串或者是null,undefined,那么可以简单一些,像这样:

function addCommas(val) {
    if (typeof val === 'undefined' || val === null) {
        return ''
    }
    return (val + "").replace(/(\d)(?=(\d{3})+\.)/g, '$1,');
}
// console.log(addCommas(''))          // ''
// console.log(addCommas('0'))         // '0'
// console.log(addCommas(null))        // ''
// console.log(addCommas(undefined))   // ''


再进一步,如果是前端展示金额的话,可以像这样:(注意toFixed会四舍五入,这里要按照实际业务来决定是四舍五入还是向下取整)

2023.09.11备注:前端 toFixed() 并不是四舍五入,也不是向下舍入,所以涉及到金额显示,请谨慎使用!

参考这篇博客:https://www.only4.work/blog/?id=519

function priceFormat(val) {
    val = Number(val)
    if (val) {
        return "¥" + val.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, '$1,');
    } else {
        return '¥0.00'
    }
}


到这里整理的就差不多了,希望能够帮到大家。


本文由张小弟之家原创,转载请注明出处。


张小弟之家

本文链接:
文章标题:

本站文章除注明转载/出处外,均为原创,若要转载请务必注明出处。转载后请将转载链接通过邮件告知我站,谢谢合作。本站邮箱:admin@only4.work

尊重他人劳动成果,共创和谐网络环境。点击版权声明查看本站相关条款。

    发表评论:

    搜索
    本文二维码
    标签列表
    站点信息
    • 文章总数:509
    • 页面总数:20
    • 分类总数:92
    • 标签总数:208
    • 评论总数:62
    • 浏览总数:228531

    | | |
    | |  Z-Blog PHP