伸手请间接跳到【办法合集】~
这次咱们来聊聊对于数字解决的一些问题。我的项目中对数字的解决肯定是避不开的,毕竟数据就是由数字组成的嘛(大雾),所以对于一下常见的数字解决场景咱们进行适当的封装也能无效简洁代码,上面就由简略到简单的程序来介绍几个。
将任意值转为有效数字
在一些场合,咱们可能会失去一些类型不平安的值,并须要将其作为数字进行解决,这种场景在写库的时候尤为常见。当咱们间接应用 parseFloat
转化时,在不非法数字时会失去 NaN
,这将导致后续所有的运算全副变成 NaN
,因而咱们须要将其回退到 0
确保后续运算失常。
function toNumber(value: unknown) {const number = parseFloat(value as string)
return Number.isNaN(number) ? 0 : number
}
将数值限定在一个范畴
这个场景和下面很类似,同样是为了保障数字的正确性,须要将数字限定在一个特定的范畴中,针对的是数字的值。
function boundRange(number: number | string, min: number, max: number) {return Math.max(min, Math.min(max, toNumber(number)))
}
这两个场景都是很简略的解决,旨在简化屡次呈现的繁冗代码。
将个位数变成两位
这个太简略,不多说了,像是在解决日期、工夫上经常会呈现这样的需要。
function doubleDigits(number: number) {return number < 10 ? `0${number}` : number.toString()}
将数字格式化成三位阶
换而言之,就是常见的将数字依照三位一组分隔开的记数法,在进步一些大数字的可读性上,或者金额的显示上用的比拟多:
function segmentNumber(number: number | string, separator = ',', segment = 3) {if (typeof number !== 'number') {number = parseFloat(number)
}
if (Number.isNaN(number)) return '0'
let [integer, decimal] = String(number).split('.')
const formatRegExp = new RegExp(`(\\d+)(\\d{${segment}})`)
while (formatRegExp.test(integer)) {integer = integer.replace(formatRegExp, `$1${separator}$2`)
}
decimal = decimal ? `.${decimal}` : ''
return `${integer}${decimal}`
}
将数字保留特定位数的小数
这是咱们这一次的重磅选手,在 js 中将数字保留特定位数是个技术活,因为 js 的小数存在精度失落问题,咱们先来看一个例子:
当咱们想把 17.275
这个小数遵循四舍五入保留至两位的时候,学过小学数学的 都应该晓得后果为 17.28
,咱们先来用 js 中最惯例的做法 toFixed
:
17.275.toFixed(2)
js 引擎小学数学没学好 这就是小数精度失落,具体原理这里就不开展了,还不理解的话应该快快地找找相干材料了。
咱们通过上面代码,其实能够发现 toFixed
的体现和 Math.round
是相似的(猜想底层实现是一样的,未验证):
Math.round(17.275 * 10 ** 2)
同样的 .5
被间接舍弃而不是进一。
随后又呈现了一些奇怪的解决办法,比方讲这个数在指标位数的根底上先扩充 10
倍再放大 10
倍,再进行四舍五入从而躲避精度失落:
parseFloat(`${Math.round(17.275 * 10 ** 3 / 10) / 10 ** 2}`)
看上去如同挺失常的,但很惋惜这只是针对 17.275
这个一个数字而已。
咱们将原始数字改为 1.3335
并将指标位数改为 3
,问题从新产生:
其实只有咱们还用 Math.round
或者 toFixed
的形式来间接来解决四舍五入的问题,精度失落的问题就始终无奈躲避。
为什么说 间接 呢,因为其实咱们能够通过一些形式解决一下数字,再给到这些办法解决,就能够达到间接解决的成果,从而躲避精度失落。
其实这个办法很粗犷,咱们都晓得精度失落无非就是大了一个或者小了一个十分小的小数,所以在 0.5 这个界限上会因为这个十分小的小数而导致舍或入的判断失准,那其实咱们只须要在保留的指标位数的下一位上,一旦发现这个数是 5
就间接让他变成 6
,其余状况就把这一位前面的局部裁掉,那这个很小的小数就不会影响到舍或入的判断了,来看代码:
function toFixed(number: number, decimal: number) {if (decimal === 0) return Math.round(number)
let snum = String(number)
const pointPos = snum.indexOf('.')
if (pointPos === -1) return number
const nums = snum.replace('.', '').split('')
const targetPos = pointPos + decimal
const datum = nums[targetPos]
if (!datum) return number
if (snum.charAt(targetPos + 1) === '5') {snum = snum.substring(0, targetPos + 1) + '6'
} else {snum = snum.substring(0, targetPos + 2)
}
return parseFloat(Number(snum).toFixed(decimal))
}
办法合集
/**
* 将任意值转成数字,NaN 的状况将会解决成 0
* @param value - 须要转化的值
*/
export function toNumber(value: unknown) {const number = parseFloat(value as string)
return Number.isNaN(number) ? 0 : number
}
/**
* 讲小于 10 整数 N 变成 `0N` 的字符串,办法不会对入参校验
* @param number - 须要解决的整数
*/
export function doubleDigits(number: number) {return number < 10 ? `0${number}` : number.toString()}
/**
* 将数字格式化为三位阶
* @param number - 须要格式化的数字
* @param segment - 分隔的位数,默认为 3
* @param separator - 分隔的符号,默认为 ','
*/
export function segmentNumber(number: number | string, segment = 3, separator = ','): string {if (typeof number !== 'number') {number = parseFloat(number)
}
if (Number.isNaN(number)) return '0'
let [integer, decimal] = String(number).split('.')
const formatRegExp = new RegExp(`(\\d+)(\\d{${segment}})`)
while (formatRegExp.test(integer)) {integer = integer.replace(formatRegExp, `$1${separator}$2`)
}
decimal = decimal ? `.${decimal}` : ''
return `${integer}${decimal}`
}
/**
* 讲一个实数保留肯定的小数
* @param number - 须要解决的实数
* @param decimal - 须要保留的小数
*/
export function toFixed(number: number, decimal: number) {if (decimal === 0) return Math.round(number)
let snum = String(number)
const pointPos = snum.indexOf('.')
if (pointPos === -1) return number
const nums = snum.replace('.', '').split('')
const targetPos = pointPos + decimal
const datum = nums[targetPos]
if (!datum) return number
if (snum.charAt(targetPos + 1) === '5') {snum = snum.substring(0, targetPos + 1) + '6'
} else {snum = snum.substring(0, targetPos + 2)
}
return parseFloat(Number(snum).toFixed(decimal))
}
/**
* 将一个实数扩充肯定的倍数并保留肯定的小数
* @param number - 要解决的实数
* @param multiple - 要扩充的倍数
* @param decimal - 要保留的小数
*/
export function multipleFixed(number: number, multiple: number, decimal: number) {return toFixed(number * multiple, decimal)
}
/**
* 将一个数字限定在指定的范畴内
* @param number - 须要限定范畴的数
* @param min - 边界最小值,蕴含该值
* @param max - 边界最大值,蕴含该值
*
* @returns 限定了范畴后的数
*/
export function boundRange(number: number | string, min: number, max: number) {return Math.max(min, Math.min(max, parseFloat(number as string)))
}
往期传送门:
【封装小技巧】列表处理函数的封装
【封装小技巧】is 系列办法的封装
最初来举荐一下我的集体开源我的项目 Vexip UI – GitHub
一个比拟齐全的 Vue3 组件库,反对全面的 css 变量,内置暗黑主题,全量 TypeScript 和组合式 Api,其特点是所有组件简直每个属性都反对通过配置(传一个对象)来批改其默认值,这应该是目前其余组件库不具备的个性~
现正招募小伙伴来应用或者参加保护与倒退这个我的项目,我一个人的力量十分无限,文档、单元测试、服务端渲染反对、周边插件、应用案例等等,只有你有趣味都能够从各个切入点参加进来,十分欢送~
这几期【封装小技巧】的内容源码都蕴含在了 @vexip-ui/utils
包上面,GitHub,这个包也有独自公布,不过目前还没有 Api 文档,可能须要间接查阅源码食用~