Tools
Numbro
基于 BigNumber.js 封装的精确数字处理工具,提供格式化、货币格式化和精确运算功能
Numbro
Numbro
是基于 BigNumber.js 封装的精确数字处理工具,专为解决 JavaScript 浮点数运算精度问题而设计。提供了强大的数字格式化、多语言货币格式化以及精确的数值运算功能。
特性
- 🔢 精确运算: 基于 BigNumber.js,避免浮点数精度问题
- 🌍 多语言支持: 内置中文、英文、印尼等多种语言的货币格式
- 📊 智能格式化: 支持百分比、千分位分隔、平均值简化(K/M/B/T)
- 🎯 精度控制: 灵活的小数位数控制和四舍五入模式
- 💰 货币格式化: 完整的货币格式化支持,包括符号位置、间距等
- 🛡️ 类型安全: 完整的 TypeScript 类型支持
- ⚙️ 全局配置: 支持全局默认格式配置
安装和导入
import { numbro, Numbro } from "@dune2/tools";
基本用法
创建 Numbro 实例
// 使用 numbro 函数(推荐)
const num1 = numbro(123.456);
const num2 = numbro("1,234.56"); // 自动处理千分位分隔符
const num3 = numbro(null); // 自动转为 0
// 使用 new Numbro()
const num4 = new Numbro(123.456);
支持的输入类型
// 数字
numbro(123.456);
// 字符串(自动移除逗号)
numbro("1,234.56");
// BigNumber 实例
numbro(new BigNumber("123.456"));
// Numbro 实例
numbro(numbro(123));
// null/undefined/boolean/bigint
numbro(null); // 转为 0
numbro(undefined); // 转为 0
numbro(true); // 转为 1
numbro(100n); // BigInt 转为数字
数字格式化
基础格式化
// 基本格式化
numbro(1234.5678).format(); // "1234.5678"
numbro(1234.5678).format({ mantissa: 2 }); // "1234.57"
// 千分位分隔
numbro(1234567).format({ thousandSeparated: true }); // "1,234,567"
// 百分比格式
numbro(0.1234).format({ output: "percent" }); // "12.34%"
numbro(0.1234).format({
output: "percent",
mantissa: 1,
}); // "12.3%"
平均值格式化(K/M/B/T)
// 自动简化大数字
numbro(1500).format({ average: true }); // "1K"
numbro(1234567).format({ average: true }); // "1M"
numbro(1500000000).format({ average: true }); // "1B"
numbro(2500000000000).format({ average: true }); // "2T"
// 结合小数位数
numbro(1234567).format({
average: true,
mantissa: 2,
}); // "1.23M"
四舍五入模式
const num = numbro(1.23456);
// 默认向下取整
num.format({ mantissa: 2 }); // "1.23"
// 向上取整
num.format({
mantissa: 2,
roundingMode: Numbro.RoundingMode.RoundUp,
}); // "1.24"
// 四舍五入
num.format({
mantissa: 2,
roundingMode: Numbro.RoundingMode.RoundHalfUp,
}); // "1.23"
// 向下取整(向负无穷)
numbro(-1.23456).format({
mantissa: 2,
roundingMode: Numbro.RoundingMode.RoundFloor,
}); // "-1.24"
正负号控制
// 强制显示正负号
numbro(123).format({ forceSign: true }); // "+123"
numbro(-123).format({ forceSign: true }); // "-123"
numbro(0).format({ forceSign: true }); // "0" (零不显示符号)
// 绝对值显示
numbro(-123).format({ absoluteValue: true }); // "123"
尾零处理
// deleteInvalidZero: 智能删除无效尾零
numbro("1.00100").format({
mantissa: 3,
deleteInvalidZero: true,
}); // "1.001" (原始精度 > 指定精度时删除尾零)
numbro("1.20000").format({
mantissa: 3,
deleteInvalidZero: true,
}); // "1.2" (原始精度 <= 指定精度时删除尾零)
// deleteEndZero: 强制删除所有尾零
numbro("1.20000").format({
mantissa: 3,
deleteEndZero: true,
}); // "1.2" (直接删除格式化后的尾零)
NaN 值处理
// 默认将 NaN 当作 0 处理
numbro(NaN).format(); // "0"
numbro(null).format(); // "0"
numbro(undefined).format(); // "0"
// 自定义 NaN 格式
numbro(NaN).format({ NaNFormat: "-" }); // "-"
numbro(NaN).format({ NaNFormat: "N/A" }); // "N/A"
// 禁用 NaN 格式化
numbro(NaN).format({ NaNFormat: false }); // "0"
后缀和前缀
// 使用 postfix 添加后缀
numbro(100).format({ postfix: "px" }); // "100px"
numbro(50).format({ postfix: "%" }); // "50%"
// BigNumber.Format 的其他选项
numbro(1234).format({
prefix: "$",
suffix: " USD",
groupSize: 3,
groupSeparator: ",",
decimalSeparator: ".",
}); // "$1,234 USD"
货币格式化
基础货币格式化
// 使用默认语言环境(印尼)
numbro(1234.56).formatCurrency(); // "Rp1,234.56"
// 指定语言环境
numbro(1234.56).formatCurrency({ locale: "en" }); // "$1,234.56"
numbro(1234.56).formatCurrency({ locale: "zh" }); // "¥1,234.56"
自定义货币格式
// 自定义货币符号
numbro(1234.56).formatCurrency({
symbol: "€",
mantissa: 2,
}); // "€1,234.56"
// 货币符号位置
numbro(1234.56).formatCurrency({
symbol: "USD",
position: "postfix",
}); // "1,234.56USD"
// 添加空格分隔
numbro(1234.56).formatCurrency({
symbol: "€",
position: "postfix",
spaceSeparated: true,
}); // "1,234.56 €"
货币格式的正负号处理
// 负数货币格式
numbro(-1234.56).formatCurrency({ locale: "en" }); // "-$1,234.56"
// 强制显示正号
numbro(1234.56).formatCurrency({
locale: "en",
forceSign: true,
}); // "+$1,234.56"
// 使用绝对值(在货币格式中自动处理符号)
numbro(-1234.56).formatCurrency({
symbol: "$",
position: "prefix",
}); // "-$1,234.56"
数值运算
基础算术运算
// 加法 - 解决 0.1 + 0.2 !== 0.3 的问题
numbro(0.1).add(0.2).format(); // "0.3"
numbro(0.1).add(0.2).value(); // 0.3
// 减法
numbro(0.3).subtract(0.1).format(); // "0.2"
// 乘法
numbro(0.1).multiply(3).format(); // "0.3"
// 除法
numbro(1).divide(3).format({ mantissa: 6 }); // "0.333333"
// 差值(绝对值)
numbro(100).difference(150).format(); // "50"
numbro(150).difference(100).format(); // "50"
支持多种输入类型的运算
const num = numbro(100);
// 与数字运算
num.add(50).format(); // "150"
// 与字符串运算
num.add("1,000").format(); // "1,100"
// 与其他 Numbro 实例运算
num.add(numbro(25)).format(); // "125"
// 与 BigNumber 运算
num.add(new BigNumber(75)).format(); // "175"
链式调用
// 复杂运算的链式调用
const result = numbro(100)
.add(50) // 150
.multiply(1.2) // 180
.subtract(30) // 150
.divide(3) // 50
.format({ mantissa: 2 }); // "50.00"
值获取和转换
获取数值
const num = numbro(123.456);
// 获取数字值
num.value(); // 123.456
num.valueOf(); // 123.456
num.num; // 123.456 (getter 简写)
// 获取字符串
num.toString(); // "123.456"
num.str; // "123.456" (getter 简写)
// NaN 值处理
numbro(NaN).value(); // 0 (自动转为 0)
numbro(NaN).valueOf(); // 0
实例克隆
const original = numbro(123.456);
const cloned = original.clone();
// 修改克隆不影响原实例
cloned.add(100);
console.log(original.format()); // "123.456"
console.log(cloned.format()); // "223.456"
全局配置
默认格式化配置
// 设置全局默认格式
Numbro.setDefaultFormat({
thousandSeparated: true,
mantissa: 2,
roundingMode: Numbro.RoundingMode.RoundHalfUp,
});
// 之后的格式化会应用默认配置
numbro(1234.5678).format(); // "1,234.57"
// 仍可在调用时覆盖默认配置
numbro(1234.5678).format({ mantissa: 0 }); // "1,235"
语言环境配置
// 查看当前语言环境
console.log(Numbro.locale); // "id" (默认印尼)
// 设置语言环境
Numbro.setLocale("en"); // 英文
Numbro.setLocale("zh"); // 中文
// 语言环境必须在 defaultCurrencies 中存在
try {
Numbro.setLocale("fr"); // 会抛出错误
} catch (error) {
console.log(error.message); // "在 defaultCurrencies 中,找不到 fr,请先检查"
}
货币配置
// 查看默认货币配置
console.log(Numbro.defaultCurrencies);
// {
// "en": { symbol: "$", position: "prefix", mantissa: 2 },
// "id": { symbol: "Rp", position: "prefix", mantissa: 2 },
// "zh": { symbol: "¥", position: "prefix", mantissa: 2 }
// }
// 扩展货币配置
Numbro.setDefaultCurrencies({
...Numbro.defaultCurrencies,
eur: {
symbol: "€",
position: "postfix",
mantissa: 2,
spaceSeparated: true,
},
jpy: {
symbol: "¥",
position: "prefix",
mantissa: 0, // 日元通常不显示小数
},
});
// 使用新的货币配置
numbro(1234.56).formatCurrency({ locale: "eur" }); // "1,234.56 €"
numbro(1234.56).formatCurrency({ locale: "jpy" }); // "¥1,235"
枚举和常量
RoundingMode 枚举
// 可用的四舍五入模式
Numbro.RoundingMode.RoundDown; // 向下取整(默认)
Numbro.RoundingMode.RoundUp; // 向上取整
Numbro.RoundingMode.RoundFloor; // 向负无穷取整
Numbro.RoundingMode.RoundCeil; // 向正无穷取整
Numbro.RoundingMode.RoundHalfUp; // 四舍五入
// 也可以通过 numbro 访问
numbro.RoundingMode.RoundDown;
类型定义
Format 接口
interface Format extends BigNumber.Format {
output?: "percent"; // 输出百分比格式
postfix?: string; // 后缀(同 suffix)
average?: boolean; // 启用 K/M/B/T 简化
mantissa?: number | string | null; // 小数位数
thousandSeparated?: boolean; // 千分位分隔
forceSign?: boolean; // 强制显示正负号
roundingMode?: RoundingMode; // 四舍五入模式
deleteInvalidZero?: boolean; // 删除无效尾零
deleteEndZero?: boolean; // 删除所有尾零
NaNFormat?: string | false; // NaN 值格式
absoluteValue?: boolean; // 显示绝对值
}
CurrencyFormat 接口
interface CurrencyFormat extends Format {
symbol?: string; // 货币符号
position?: "prefix" | "postfix"; // 符号位置
locale?: LocalesEnum | string; // 语言环境
spaceSeparated?: boolean; // 符号与数字间是否有空格
}
最佳实践
1. 精确计算
// ✅ 使用 Numbro 避免精度问题
const result = numbro(0.1).add(0.2).value(); // 0.3
const jsResult = 0.1 + 0.2; // 0.30000000000000004
// ✅ 链式调用避免中间精度丢失
const complex = numbro(100)
.multiply(0.1)
.add(0.2)
.divide(3)
.format({ mantissa: 4 });
2. 性能优化
// ✅ 重用配置对象
const currencyConfig = { locale: "zh", mantissa: 2 };
const prices = [99.99, 149.5, 29.99];
const formatted = prices.map((price) =>
numbro(price).formatCurrency(currencyConfig),
);
// ✅ 全局配置减少重复
Numbro.setDefaultFormat({ thousandSeparated: true, mantissa: 2 });
3. 错误处理
// ✅ 使用 NaNFormat 处理空值
const formatWithFallback = (value: any) =>
numbro(value).format({
NaNFormat: "--",
mantissa: 2,
});