You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript中按范围保留数字小数位数(字符串避精度问题)

解决方案

在TypeScript中处理这类「保留最少n位、最多m位小数,同时避免浮点数精度误差」的需求,公认的方法有两种:使用高精度小数库(推荐)或原生字符串精确处理。

方法一:使用decimal.js(推荐)

decimal.js是业界广泛使用的高精度小数运算库,能完美避免JavaScript浮点数的精度问题,同时提供丰富的格式化API。

实现步骤

  1. 安装依赖:
npm install decimal.js
  1. 编写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}`;
}

测试结果

和方法一的测试示例输出一致,能满足需求。

核心逻辑说明

两种方法的核心思路一致:

  1. 避免浮点数直接运算:要么用高精度库封装,要么转成字符串处理,从根源上解决精度误差问题。
  2. 双精度约束:确保结果最少保留min位小数,最多不超过max位。
  3. 精度误差判断:通过比较差值或额外小数部分的数值,区分真实的高精度输入和浮点数运算导致的误差,避免错误保留无效小数位。

内容的提问来源于stack exchange,提问作者John

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 20:15:40