Skip to content

金融格式化

为什么需要金融格式化

  • 金额、大数字需要更清晰地展示给用户
  • 例如 1234567.89 → 1,234,567.89

新建文件

  • utils/moneyformat.ts
ts
/**
 * 金融金额格式化工具
 * 将数字金额转换为符合金融规范的格式:千分位分隔,保留两位小数
 */

/** 可接受的金额输入类型 */
type MoneyInput = number | string;

/** 非法输入时的默认返回值 */
const INVALID_RESULT = "0.00";

/**
 * 校验输入是否为有效数字
 * @param input 输入值
 * @returns 合法则返回 number,否则返回 null
 */
function toValidNumber(input: MoneyInput): number | null {
  const num = typeof input === "string" ? Number(input.trim()) : input;
  if (typeof num !== "number" || Number.isNaN(num) || !Number.isFinite(num)) {
    return null;
  }
  return num;
}

/**
 * 方案一:正则表达式实现金额格式化
 *
 * @param amount - 金额,支持 number 或 string 类型(如 12345.678 / "12345.678")
 * @returns 格式化后的字符串,如 "12,345.68";非法输入返回 "0.00"
 *
 * 使用场景:需要简洁实现的常规金额展示,依赖正则完成千分位分隔。
 */
export function formatByRegex(amount: MoneyInput): string {
  const num = toValidNumber(amount);
  if (num === null) return INVALID_RESULT;

  // toFixed(2):自动补全两位小数、超长小数四舍五入
  const fixed = num.toFixed(2);

  // 正则:在整数部分从右往左每三位前插入逗号(不匹配负号和小数点后的位置)
  return fixed.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

/**
 * 方案二:JavaScript 原生 API 实现金额格式化(不使用正则)
 *
 * @param amount - 金额,支持 number 或 string 类型
 * @returns 格式化后的字符串,如 "12,345.68";非法输入返回 "0.00"
 *
 * 使用场景:不希望依赖正则的场景,或需要清晰控制整数/小数处理逻辑的场景。
 */
export function formatByNative(amount: MoneyInput): string {
  const num = toValidNumber(amount);
  if (num === null) return INVALID_RESULT;

  // 处理负号
  const isNegative = num < 0;
  const absStr = Math.abs(num).toFixed(2); // 自动补0 / 四舍五入到两位小数

  // 拆分整数部分和小数部分
  const dotIndex = absStr.indexOf(".");
  const intPart = dotIndex === -1 ? absStr : absStr.slice(0, dotIndex);
  const decimalPart = dotIndex === -1 ? "" : absStr.slice(dotIndex + 1);

  // 手动遍历,从右往左每三位插入千分位分隔符
  let formattedInt = "";
  for (let i = intPart.length - 1, count = 0; i >= 0; i--, count++) {
    if (count > 0 && count % 3 === 0) {
      formattedInt = "," + formattedInt;
    }
    formattedInt = intPart[i] + formattedInt;
  }

  const sign = isNegative ? "-" : "";
  return decimalPart
    ? `${sign}${formattedInt}.${decimalPart}`
    : `${sign}${formattedInt}`;
}

/* ============================== 测试用例 ============================== */

/** 简单断言:两函数输出一致且等于期望值 */
function assertFormat(input: MoneyInput, expected: string): void {
  const regexResult = formatByRegex(input);
  const nativeResult = formatByNative(input);
  console.assert(
    regexResult === expected && nativeResult === expected,
    `输入 ${input}: 期望 "${expected}",实际 regex="${regexResult}", native="${nativeResult}"`,
  );
  console.log(
    `输入 ${String(input).padEnd(18)} => "${regexResult}" ${regexResult === expected && nativeResult === expected ? "✔" : "✘"}`,
  );
}

// 普通正整数:1234567 → "1,234,567.00"
assertFormat(1234567, "1,234,567.00");
// 带小数的金额(四舍五入):12345.678 → "12,345.68"
assertFormat(12345.678, "12,345.68");
// 负数金额:-987654.32 → "-987,654.32"
assertFormat(-987654.32, "-987,654.32");
// 小于1000的小额金额(补0):123.4 → "123.40"
assertFormat(123.4, "123.40");
// 超大金额:1234567890123.45 → "1,234,567,890,123.45"
assertFormat(1234567890123.45, "1,234,567,890,123.45");
// 字符串输入
assertFormat("9876543210.5", "9,876,543,210.50");
// 字符串负数输入
assertFormat("-12345.1", "-12,345.10");
// 异常输入:NaN / Infinity / 非数字字符串
assertFormat(NaN, "0.00");
assertFormat(Infinity, "0.00");
assertFormat("abc", "0.00");