TypeScript中按范围保留数字小数位数(字符串避精度问题)
解决方案
在TypeScript中处理这类「保留最少n位、最多m位小数,同时避免浮点数精度误差」的需求,公认的方法有两种:使用高精度小数库(推荐)或原生字符串精确处理。
方法一:使用decimal.js(推荐)
decimal.js是业界广泛使用的高精度小数运算库,能完美避免JavaScript浮点数的精度问题,同时提供丰富的格式化API。
实现步骤
- 安装依赖:
npm install decimal.js
- 编写TypeScript函数:
import { Decimal } from 'decimal.js'; function tx(num: number | string, precision: [number, number]): string { const [minDecimals, maxDecimals] = precision; const decimal = new Decimal(num); // 先截断到最多maxDecimals位小数,去除末尾无效零 let formatted = decimal.toDecimalPlaces(maxDecimals, Decimal.ROUND_DOWN).toString(); const [integerPart, decimalPart = ''] = formatted.split('.'); let currentDecimalLen = decimalPart.length; // 确保最少保留minDecimals位小数 if (currentDecimalLen < minDecimals) { return `${integerPart}.${decimalPart.padEnd(minDecimals, '0')}`; } // 判断是否是浮点数精度误差导致的多余小数位 const roundedToMin = decimal.toDecimalPlaces(minDecimals, Decimal.ROUND_DOWN); const diff = decimal.minus(roundedToMin).abs(); const epsilon = new Decimal(10).pow(-(maxDecimals + 1)); if (diff.lessThanOrEqualTo(epsilon)) { // 属于精度误差,保留最少位数 return `${integerPart}.${decimalPart.slice(0, minDecimals).padEnd(minDecimals, '0')}`; } // 正常保留有效小数位(不超过maxDecimals) return formatted; }
测试示例
const precision = [2, 6]; console.log(tx(0.000000, precision)); // "0.00" console.log(tx(0.123123, precision)); // "0.123123" console.log(tx(2.00, precision)); // "2.00" console.log(tx(2, precision)); // "2.00" console.log(tx(2.01, precision)); // "2.01" console.log(tx(2.001, precision)); // "2.001" console.log(tx(2.000000001, precision)); // "2.00" console.log(tx(2.000001, precision)); // "2.000001"
方法二:原生TypeScript字符串处理(无依赖)
如果不想引入第三方库,可以通过字符串操作实现精确处理,但需要自行处理浮点数转精确字符串的逻辑,覆盖科学计数法等边缘情况。
实现代码
// 辅助函数:将数字转换为精确的十进制字符串(避免科学计数法和精度丢失) function numberToExactString(num: number): string { if (Number.isInteger(num)) return num.toString(); const str = num.toString(); if (!str.includes('e')) return str; // 处理科学计数法格式 const [significand, exponentStr] = str.split('e'); const exp = parseInt(exponentStr, 10); const [intPart, decPart = ''] = significand.split('.'); const digits = intPart + decPart; if (exp >= 0) { const padded = digits.padEnd(digits.length + exp, '0'); if (intPart.length + exp >= digits.length) { return padded; } return `${padded.slice(0, intPart.length + exp)}.${padded.slice(intPart.length + exp)}`; } else { return `0.${'0'.repeat(-exp - 1)}${digits}`; } } function tx(num: number | string, precision: [number, number]): string { const [minDecimals, maxDecimals] = precision; const numStr = typeof num === 'string' ? num : numberToExactString(num); let [integerPart, decimalPart = ''] = numStr.split('.'); const originalDecimal = numStr.split('.')[1] || ''; // 截断到最多maxDecimals位小数 decimalPart = decimalPart.slice(0, maxDecimals); // 检查是否存在超出max位的小数部分,判断是否为精度误差 const extraPart = originalDecimal.slice(maxDecimals); let isPrecisionError = false; if (extraPart.length > 0) { const extraValue = parseInt(extraPart, 10) / Math.pow(10, extraPart.length); if (extraValue <= Math.pow(10, -(maxDecimals + 1))) { isPrecisionError = true; } } // 确定最终保留的小数位 let finalDecimals: string; if (isPrecisionError) { finalDecimals = decimalPart.slice(0, minDecimals).padEnd(minDecimals, '0'); } else { // 去掉末尾无效零,但至少保留min位 const trimmed = decimalPart.replace(/0+$/, ''); finalDecimals = trimmed.length < minDecimals ? decimalPart.slice(0, minDecimals).padEnd(minDecimals, '0') : trimmed; } return `${integerPart}.${finalDecimals}`; }
测试结果
和方法一的测试示例输出一致,能满足需求。
核心逻辑说明
两种方法的核心思路一致:
- 避免浮点数直接运算:要么用高精度库封装,要么转成字符串处理,从根源上解决精度误差问题。
- 双精度约束:确保结果最少保留
min位小数,最多不超过max位。 - 精度误差判断:通过比较差值或额外小数部分的数值,区分真实的高精度输入和浮点数运算导致的误差,避免错误保留无效小数位。
内容的提问来源于stack exchange,提问作者John
相关产品推荐
相关产品推荐

