JS:【前端】手写金额的千位分隔,始终保留两位小数(非toFixed方法)

您所在的位置:网站首页 js保留小数点后两位怎么设置 JS:【前端】手写金额的千位分隔,始终保留两位小数(非toFixed方法)

JS:【前端】手写金额的千位分隔,始终保留两位小数(非toFixed方法)

2024-06-16 01:03| 来源: 网络整理| 查看: 265

如果你写的方法 f 能经过下面参数的考验,请跳过此篇文章。

f(null); // invalid value f(undefined); // invalid value f(); // invalid value f([12.888]); // invalid value f('abc') // invalid value f(NaN) // invalid value f('') // '' f(0.1+0.2); // 0.30 f(0.3 - 0.2); // 0.10 f(1e3); // 1,000.00 // f('1e3') // 1,000.00 f(123456) // 123,456.00 f(123456.456) // 123,456.46 f(12.899) // 12.90 f(12.999) // 13.00 f(10.055) // 10.06 f(10.005) // 10.01 f(10.105) // 10.11 f(10.155); // 10.16 f(10.105) // 10.11 f(0.0001) // 0.00 f(-10.105) // -10.11 f(.5) // 0.50 f(0/0) // invalid value f(12/0) // invalid value f(10 ** -4) // 0.00 f('123456789012345.12') // 123,456,789,012,345.12 ---入参不能是number类型 f('123456789012345.13') // 123,456,789,012,345.13 ---入参不能是number类型 f('123456789012345.99') // 123,456,789,012,345.99 ---入参不能是number类型

如果你的方法过不去,请参阅下文。

重要说明:如果你的方法涉及toFixed,必然通不过上述的考验。

思路:

参数只支持数字(支持负数)的字符串和数字形式,别的暂不支持。--补充,空字符串("")应该返回空字符串参数小数位大于两位时四舍五入处理,最多保留两位小数;否则不做处理。考虑入参是字符串 补充:入参整数15位,且包含小数位时 1)number类型的值,精度自己会丢失。这里避免入参是number类型 2)string类型的值,无差别四舍五入,精度还是会丢失。不能直接进行四舍五入参数整数位千分位添加分隔符(,),小数位不足两位的尾部用0补足。 一:方法详述 1、参数只支持数字 有些同学喜欢上来先 参数.toString(),先别急撒。 万一参数是null/undefined,有时候是后台返给你的。当然,报错也在悄悄等着你。又是为bug率添砖加瓦的一天。有同学习惯把null当成0,因为在js中null转为数字时,自动变成0。但在实际的业务场景中,null不一定是0,小tip可忽略。你又默默的传了一个 [12.888],数组自定义了toString方法,结果返回'12.888'。这时候程序反而能正常执行,hh....。

so,我们来约定,参数只支持数字,其余的直接返回'invalid value'。这个数字限定在数值和字符串的类型下,同时,isFinite(参数) 返回true即可(这样就可以整治各种花里花哨)。

isFinite会隐性的先将参数转成数值。它会过滤掉NaN、Infinity、-Infinity。

isFinite(Infinity) // false isFinite(-Infinity) // false isFinite(NaN) // false isFinite(undefined) // false isFinite(null) // true isFinite(2) // true isFinite(-2) // true isFinite(.5) // true isFinite(1e3) // true isFinite([1]) // true isFinite([1,2]) // false isFinite({}) // false isFinite(new Date()) // true isFinite(/x/) // false

Number.isFinite() 也可,需手动将传参转成数值。

起初,计划参数转数值不是NaN就好,结果验证的时候,有个奇怪的 In,fin,ity.00。也是给自己排雷了。so,果断用isFinite。

入参是空字符串时,需要考虑实际的业务含义。本文直接返回空字符串。

所以这第一步如下:

function numberWithCommas(x, num = 2) { // 以下两个方法都可以过滤NaN 和 Infinity/-Infinity,任选其一 let bool = isFinite(x); // 方法一 // let bool = Number.isFinite(Number(x)); // 方法二 if (x === '') { return x } else if ((typeof x === 'string' || typeof x === 'number') && bool) { return addCommas(handlerNum(x, num), num) } else { return "invalid value" } } 2、参数小数位大于两位时四舍五入处理,最多保留两位小数;否则不做处理 123456789012345.12 // 123456789012345.12 123456789012345.13 // 123456789012345.12 123456789012345.99 // 123456789012345.98

number类型的值,精度自己会丢失。且整数位15位时,容易出现精度丢失的问题。这里小数位≤2位时,不再进行四舍五入。

function handlerNum(num, decimal = 2) { num = num + '' if (num.includes('.')) { let decimals = num.split('.')[1] || '' if (decimals.length > decimal) { return round(num, decimal) } else { return num } } else { return num } }

四舍五入保留两位,有同学举手说我会!toFixed方法。当然你说完这个答案后,你就不会了。

来来来,举栗说明。

// 结果挺正常 (10).toFixed(2) // "10.00" 10.005.toFixed(2) // "10.01" // 结果就挺不正常 (10.055).toFixed(2) // "10.05" (10.135).toFixed(2) // "10.13"

so,放弃toFixed()吧。

方式一:手写round方法,方式二:站在巨人的肩膀上(这里计划用number-precision)

手写round方式,思路是先与100相乘(自定义times方法),四舍五入(Math.round())后,再除以100。

参数与100相乘,自定义了times方法。没直接用数学计算(参数 * 100)的原因是,在js中,浮点数的计算太不准确。 10.155*100 // 1015.4999999999999 10**-4 // 0.00009999999999999999 Math.pow(10,-4) // 0.00009999999999999999 0.1+0.2 // 0.30000000000000004 0.3-0.2 // 0.09999999999999998 Math.round对正数很友好,如果你的传参是个负数,可能又有点问题。 console.log(Math.round(1.1)) // 1 console.log(Math.round(1.5)) // 2 console.log(Math.round(1.6)) // 2 console.log(Math.round(-1.1)) // -1 console.log(Math.round(-1.5)) // -1 console.log(Math.round(-1.6)) // -2

so,先把参数取绝对值,计算完,再把符号(如果是负数的话)还原回去。

所以这第二步,是从下面的 round 方法,任选其一。

第一种方式(NP)— 首选

// 安装 number-precision npm install number-precision --save // 使用 import NP from 'number-precision'; // 或 const NP = require('number-precision') function round(num, decimal = 2) { return NP.round(num, decimal) }

 第二种方式(自定义round方法,NP的低配版)

function round(num, decimal = 2) { let base = Math.pow(10, decimal); let result = Math.round(Math.abs(times(num, base))) / base; if (num < 0 && result !== 0) { result *= -1; } return result; } function times (num1, num2) { const {val: val1, len: len1} = farmatVal(num1) const {val: val2, len: len2} = farmatVal(num2) return val1 * val2 / Math.pow(10, len1 + len2); } function farmatVal (num) { // floatNoDot:浮点数去掉小数点 const floatNoDot = num => Number((num + '').replace('.', '')); // decLength:浮点数小数的位数 const decLength = num => (((num + '').split('.'))[1] || '').length; return { val: floatNoDot(num), len: decLength(num) } } 3、格式化返回。整数添加千分位分隔符,小数位不足两位的尾部用0补足

最后的主菜来了。千分位分隔,自己写方法的话,正则是最方便也最简洁的方式。

但正则确是盲区,简单的能看懂,有点难度的不在涉猎范围之内。

这第三步可从下面的addCommas方法,任选其一。当然你如果更会写格式化方法,来,笔给你。

第一种方式(正则)

function addCommas(x, num = 2) { let [integer, decimals = ''] = (x + '').split('.') // 添加千分位分隔符 integer = integer + '' let pattern = /(-?\d+)(\d{3})/; while (pattern.test(integer)) { integer = integer.replace(pattern, '$1,$2') } // 尾部补零 decimals = (decimals + '0'.repeat(num)).slice(0, num) return integer + '.' + decimals; }

第二种方式(正则)

function addCommas(x, num = 2) { // 尾部补零 x = x + '' x = x.replace(/^(-?\d+)$/, '$1.') let reg = new RegExp(`(\\.\\d{${num}})\\d*$`) x = (x + '0'.repeat(num)).replace(reg, '$1') // 添加分隔符 x = x.replace('.', ',') let pattern = /(\d)(\d{3},)/; while (pattern.test(x)) { x = x.replace(pattern, '$1,$2') } let regDec = new RegExp(`,(\\d{${num}})$`) x = x.replace(regDec, '.$1') return x; }

第三种方式(非正则):整数位数组每千位添加一个分隔符,再转为字符串

function addCommas(x, num = 2) { // 符号位 let sign = x < 0 ? '-' : ''; let [ integer = '', decimals = '' ] = (Math.abs(x).toString()).split('.'); // 整数位 let integerArr = [...integer]; for (let len = integerArr.length, i = len - 1; i > 0; i--) { if ((len - 1 - i) % 3 === 2) { integerArr.splice(i, 0, ',') } } // 小数位补零 decimals = decimals.slice(0, num).padEnd(num, '0') return sign + integerArr.join('') + '.' + decimals; }

第四种方式(非正则):利用数组的reduceRight方法,累积的过程。

function addCommas(x, num = 2) { let sign = x < 0 ? '-' : ''; let [ integer = '', decimals = '' ] = (Math.abs(x).toString()).split('.'); // 整数位 let integerStr = ([...integer]).reduceRight((result, int, index, arr) => { return int + ((arr.length - 1 - index) % 3 === 0 ? ',': '') + result }) // 小数位补零 decimals = (decimals + '0'.repeat(num)).slice(0, num); return sign + integerStr + '.' + decimals; }

第五种方式(numeral),精度丢失

// 安装依赖 npm install numeral --save // 使用 const NUMERAL = require('numeral') function addCommas(x, num = 2) { return NUMERAL(x).format(`0,0.${'0'.repeat(num)}`) }

近期在别的项目组看到了 numeral 的使用,这里想补充说一下。

这里没直接用在numberWithCommas方法里,而是写在了addCommas里。是因为numeral(x)方法使用了toFixed方法,直接使用结果并不准确。实际项目中,x是精准的值,经过NUMERAL处理后,精度丢失,可以拿123456789012345.12尝试,结果是123,456,789,012,345.13。

不建议使用。

二:方法验证 console.log(numberWithCommas(null)); // invalid value console.log(numberWithCommas(undefined)); // invalid value console.log(numberWithCommas()); // invalid value console.log(numberWithCommas([12.888])); // invalid value console.log(numberWithCommas('abc')); // invalid value console.log(numberWithCommas(NaN)); // invalid value console.log(numberWithCommas(0.1 + 0.2)); // 0.30 console.log(numberWithCommas(0.3 - 0.2)); // 0.10 console.log(numberWithCommas(1e3)); // 1,000.00 // console.log(numberWithCommas('1e3')); // 1,000.00 console.log(numberWithCommas(123456)); // 123,456.00 console.log(numberWithCommas(123456.456)); // 123,456.46 console.log(numberWithCommas(12.899)); // 12.90 console.log(numberWithCommas(12.999)); // 13.00 console.log(numberWithCommas(10.055)); // 10.06 console.log(numberWithCommas(10.005)); // 10.01 console.log(numberWithCommas(10.105)); // 10.11 console.log(numberWithCommas(10.155)); // 10.16 console.log(numberWithCommas(-10.105)); // -10.11 console.log(numberWithCommas(.5)); // 0.50 console.log(numberWithCommas(0/0)); // invalid value console.log(numberWithCommas(12/0)); // invalid value console.log(numberWithCommas(10 ** -4)) // 0.00 console.log(numberWithCommas('123456789012345.12')) // 123,456,789,012,345.12 console.log(numberWithCommas('123456789012345.13')) // 123,456,789,012,345.13 console.log(numberWithCommas('123456789012345.99')) // 123,456,789,012,345.99

基本实现了要求。唯一的问题是 JS语言中精度最多只能到53个二进制位,绝对值小于2的53次方的整数,即-2^{53}2^{53},都可以精确表示,否则无法保持精度。由于2的53次方是一个16位的十进制数值,所以JavaScript 对15位的十进制数都可以精确处理。如果整数位 加 两位小数大于15位,结果仍然不准确。

其次,金额格式化成2位小数,是常规操作。但对于某些值:罚息等,可能有三位小数的格式化需求。本文提供的格式化方法满足3位小数的处理。

最后,入参是科学计数法的字符串形式,不再支持。

The end.



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3