JS:金额的千位分隔,且保留两位小数(四舍五入)--正则/非正则

如果你写的方法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(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

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

一:方法详述

思路:

  • 参数只支持数字(支持负数)的字符串和数字形式,别的暂不支持。
  • 参数四舍五入处理,最多保留两位小数
  • 参数整数位千分位添加分隔符(,),小数位不足两位的尾部用0补足。

1、参数只支持数字

有些同学喜欢上来先 参数.toString(),先别急撒。

万一参数是null/undefined,有时候是后台返给你的。当然,报错也在悄悄等着你。

你又默默的传了一个[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 ((typeof x === 'string' || typeof x === 'number')
    && bool) {
    return addCommas(round(x, num), num)
  } else {
    return "invalid value"
  }
}

2、参数四舍五入处理,最多保留两位小数

四舍五入保留两位,有同学举手说我会!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;
}

二:方法验证

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

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

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

The end.

猜你喜欢

转载自blog.csdn.net/weixin_43932309/article/details/128334277
今日推荐