实现千位分隔符

将数字格式化为千位分隔符形式,如 1234567 转换为 1,234,567

问题

千位分隔符是一种常见的数字格式化方式,用于提高数字的可读性。需要实现一个函数,将普通数字转换为带有千位分隔符的字符串格式,例如将 1234567.89 转换为 1,234,567.89

要求支持:

  • 整数和小数
  • 正数和负数
  • 保留小数部分不变

解答

方法一:正则表达式(推荐)

/**
 * 使用正则表达式实现千位分隔符
 * @param {number|string} num - 需要格式化的数字
 * @returns {string} 格式化后的字符串
 */
function formatNumberWithComma(num) {
  // 转换为字符串
  const str = String(num);
  
  // 分离整数和小数部分
  const parts = str.split('.');
  
  // 处理整数部分:使用正则表达式添加千位分隔符
  // (?=...) 正向预查,(?!^) 排除开头位置
  parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  
  // 拼接整数和小数部分
  return parts.join('.');
}

方法二:循环遍历

/**
 * 使用循环实现千位分隔符
 * @param {number|string} num - 需要格式化的数字
 * @returns {string} 格式化后的字符串
 */
function formatNumberWithLoop(num) {
  const str = String(num);
  const parts = str.split('.');
  
  // 处理整数部分
  let integerPart = parts[0];
  let result = '';
  let count = 0;
  
  // 从右向左遍历
  for (let i = integerPart.length - 1; i >= 0; i--) {
    count++;
    result = integerPart[i] + result;
    
    // 每三位添加逗号,但不在开头添加
    if (count % 3 === 0 && i !== 0 && integerPart[i - 1] !== '-') {
      result = ',' + result;
    }
  }
  
  // 拼接小数部分
  return parts.length > 1 ? result + '.' + parts[1] : result;
}

方法三:使用 toLocaleString(最简单)

/**
 * 使用原生 API 实现千位分隔符
 * @param {number} num - 需要格式化的数字
 * @returns {string} 格式化后的字符串
 */
function formatNumberWithAPI(num) {
  return Number(num).toLocaleString('en-US');
}

方法四:支持负数的完整实现

/**
 * 完整的千位分隔符实现(支持负数)
 * @param {number|string} num - 需要格式化的数字
 * @returns {string} 格式化后的字符串
 */
function formatNumber(num) {
  // 处理非法输入
  if (num === null || num === undefined || num === '') {
    return '';
  }
  
  const str = String(num);
  
  // 判断是否为负数
  const isNegative = str[0] === '-';
  const absoluteStr = isNegative ? str.slice(1) : str;
  
  // 分离整数和小数部分
  const parts = absoluteStr.split('.');
  
  // 格式化整数部分
  parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  
  // 拼接结果
  const result = parts.join('.');
  
  return isNegative ? '-' + result : result;
}

使用示例

// 基本使用
console.log(formatNumber(1234567));           // "1,234,567"
console.log(formatNumber(1234567.89));        // "1,234,567.89"
console.log(formatNumber(-1234567.89));       // "-1,234,567.89"

// 边界情况
console.log(formatNumber(123));               // "123"
console.log(formatNumber(0));                 // "0"
console.log(formatNumber(1000));              // "1,000"
console.log(formatNumber(1000000));           // "1,000,000"

// 字符串输入
console.log(formatNumber('9876543210'));      // "9,876,543,210"
console.log(formatNumber('123.456789'));      // "123.456789"

// 使用原生 API
console.log(formatNumberWithAPI(1234567.89)); // "1,234,567.89"

// 特殊情况
console.log(formatNumber(''));                // ""
console.log(formatNumber(null));              // ""

关键点

  • 正则表达式解析/\B(?=(\d{3})+(?!\d))/g

    • \B:非单词边界,确保不在字符串开头匹配
    • (?=...):正向预查,匹配位置而不消耗字符
    • (\d{3})+:匹配一个或多个三位数字组
    • (?!\d):负向预查,确保后面不再有数字
  • 整数与小数分离:使用 split('.') 分离,只对整数部分添加分隔符

  • 负数处理:需要单独判断并处理负号,避免在负号后添加逗号

  • 性能对比

    • 正则表达式:代码简洁,性能较好
    • 循环遍历:逻辑清晰,易于理解
    • toLocaleString:最简单,但灵活性较低
  • 边界情况:注意处理空值、零、小于千的数字等特殊情况

  • 扩展性:可以根据需求调整分隔符(如空格)或分隔位数(如万位分隔)