Reach out, please jump directly to 【Method Collection】~

This time let's talk about some issues about number processing. The processing of numbers in the project must be unavoidable. After all, data is composed of numbers (big fog). Therefore, for some common digital processing scenarios, proper encapsulation can also effectively simplify the code. The following is from simple to Complicated order to introduce a few.

Convert any value to a valid number

In some cases, we may get some type-unsafe values and need to deal with them as numbers, which is especially common when writing libraries. When we directly use parseFloat to convert, we will get NaN for illegal numbers, which will cause all subsequent operations to become NaN , so we need Roll it back to 0 to ensure that subsequent operations work.

 function toNumber(value: unknown) {
  const number = parseFloat(value as string)

  return Number.isNaN(number) ? 0 : number
}

Limit values to a range

This scenario is very similar to the above, also in order to ensure the correctness of the number, the number needs to be limited to a specific range, for the value of the number.

 function boundRange(number: number | string, min: number, max: number) {
  return Math.max(min, Math.min(max, toNumber(number)))
}

Both of these scenarios are very simple processing, designed to simplify the cumbersome code that occurs many times.

Convert one digit to two

This is too simple, not much to say, for example, such requirements often arise when dealing with dates and times.

 function doubleDigits(number: number) {
  return number < 10 ? `0${number}` : number.toString()
}

Format a number into three order

In other words, it is a common notation that separates numbers in groups of three, which is used more to improve the readability of some large numbers or to display amounts:

 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}`
}

Round a number to a certain number of decimals

This is our heavyweight player this time. It is a technical job to keep numbers in js to a certain number of digits, because the decimals of js have the problem of loss of precision. Let's look at an example first:

When we want to keep 17.275 this decimal is rounded to two digits, elementary school math Everyone should know that the result is 17.28 , let's use the most common method in js toFixed :

 17.275.toFixed(2)

image.png

js engine primary school mathematics did not learn well This is the loss of decimal precision. The specific principle will not be expanded here. If you don't understand it, you should quickly find relevant information.

Through the following code, we can actually find that the performance of toFixed Math.round is similar to that of ---4e3450fad0b8e34b7e1bf54819751f88--- (guess that the underlying implementation is the same, not verified):

 Math.round(17.275 * 10 ** 2)

image.png

The same .5 is simply discarded instead of one.

Then there are some strange processing methods. For example, the number is first expanded on the basis of the target number of digits 10 times and then reduced 10 times, and then rounded to avoid loss of precision:

 parseFloat(`${Math.round(17.275 * 10 ** 3 / 10) / 10 ** 2}`)

image.png

It seems to be quite normal, but unfortunately this is only for 17.275 this one number.

We changed the original number to 1.3335 and the target number to 3 and the problem reoccurs:

image.png

In fact, as long as we still use Math.round or toFixed to directly deal with the rounding problem, the problem of precision loss cannot be avoided.

Why do you say direct , because in fact, we can process the numbers in some ways, and then give these methods to process, we can achieve the effect of indirect processing, thereby avoiding the loss of precision.

In fact, this method is very rude. We all know that the loss of precision is nothing more than a large or a very small decimal. Therefore, on the boundary of 0.5, this very small decimal will lead to an inaccurate judgment of rounding or rounding. We only need to be on the next digit of the reserved target number of digits, once we find that this number is 5 , we can directly change it to 6 , and in other cases, put the digit after this digit. If it is partially cut off, then this small decimal will not affect the judgment of rounding or rounding. Let's look at the code:

 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))
}

image.png

Collection of methods

 /**
 * 将任意值转成数字,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)))
}

Past Portals:

[Encapsulation Tips] Encapsulation of List Processing Functions
[Packaging Tips] Packaging of is series methods

Finally, let me recommend my personal open source project Vexip UI - GitHub

A relatively complete Vue3 component library that supports comprehensive css variables, built-in dark theme, full TypeScript and combined Api, its feature is that almost every property of all components supports modifying its default value through configuration (passing an object). It should be a feature that other component libraries do not have at present~

I am currently recruiting small partners to use or participate in the maintenance and development of this project. My strength is very limited. Documentation, unit testing, server-side rendering support, peripheral plug-ins, use cases, etc., as long as you are interested, you can start from each Click to participate, very welcome~

The content source code of these several issues of [Packaging Tips] is included in the @vexip-ui/utils package, GitHub , this package is also released separately, but there is no Api documentation yet, you may need to directly check the source code to eat~


未觉雨声
1.5k 声望70 粉丝

Vue3 组件库 VexipUI 作者,擅长 js 和 vue 系列技术,主攻前端(交互),稍微会一点点 Java(Spring Boot)。