数字格式化看起来简单,却同时牵扯 Locale、舍入、解析宽松度和线程安全。这篇逐项实测,尤其是共用一个 DecimalFormat 时哪些操作出错、哪些没有。
[pattern]
"#" 1234568 | 0 | -42 | 0 | 1234 |
"0" 1234568 | 0 | -42 | 0 | 1234 |
"#.##" 1234567.89 | 0.5 | -42.1 | 0 | 1234.5 |
"0.00" 1234567.89 | 0.50 | -42.10 | 0.00 | 1234.50 |
"#,##0.00" 1,234,567.89 | 0.50 | -42.10 | 0.00 | 1,234.50 |
"000000" 1234568 | 000000 | -000042 | 000000 | 001234 |
"#,###" 1,234,568 | 0 | -42 | 0 | 1,234 |
"00.0" 1234567.9 | 00.5 | -42.1 | 00.0 | 1234.5 |
"#.00" 1234567.89 | .50 | -42.10 | .00 | 1234.50 |
"0.###E0" 1.235E6 | 5E-1 | -4.21E1 | 0E0 | 1.234E3 |
"##0.#####E0" 1.2345679E6 | 500E-3 | -42.1E0 | 0E0 | 1.2345E3 |
"0.00%" 123456789.10% | 50.00% | -4210.00% | 0.00% | 123450.00% |
"0.0‰" 1234567891.0‰ | 500.0‰ | -42100.0‰ | 0.0‰ | 1234500.0‰ |
"#,##0.00;(#,##0.00)" 1,234,567.89 | 0.50 | (42.10) | 0.00 | 1,234.50 |
"'编号'0000" 编号1234568 | 编号0000 | -编号0042 | 编号0000 | 编号1234 |
"$#,##0.00" $1,234,567.89 | $0.50 | -$42.10 | $0.00 | $1,234.50 |
"#,##,##0.00" 1,234,567.89 | 0.50 | -42.10 | 0.00 | 1,234.50 |
(五列依次是 1234567.891、0.5、-42.1、0、1234.5)
NumberFormat.getInstance(Locale.US) 的默认:最多 3 位小数,分组 true;toPattern = #,##0.###
NumberFormat.getIntegerInstance 的舍入:2.5 → 2,3.5 → 4;getPercentInstance 0.256 → 26%;getCurrencyInstance 1234.5 → $1,234.50
# 与 0 的区别:#.## 对 5 → 5,0.## 对 5 → 5;#.00 对 0.5 → .50,0.00 对 0.5 → 0.50 [round]
默认舍入模式:HALF_EVEN
0 位小数 0.5 → HALF_EVEN 0 | HALF_UP 1 | HALF_DOWN 0 | UP 1 | DOWN 0 | CEILING 1 | FLOOR 0
0 位小数 1.5 → HALF_EVEN 2 | HALF_UP 2 | HALF_DOWN 1 | UP 2 | DOWN 1 | CEILING 2 | FLOOR 1
0 位小数 2.5 → HALF_EVEN 2 | HALF_UP 3 | HALF_DOWN 2 | UP 3 | DOWN 2 | CEILING 3 | FLOOR 2
0 位小数 3.5 → HALF_EVEN 4 | HALF_UP 4 | HALF_DOWN 3 | UP 4 | DOWN 3 | CEILING 4 | FLOOR 3
0 位小数 -2.5 → HALF_EVEN -2 | HALF_UP -3 | HALF_DOWN -2 | UP -3 | DOWN -2 | CEILING -2 | FLOOR -3
两位小数 0.125 精确值 0.125 → HALF_EVEN 0.12 | HALF_UP 0.13
两位小数 0.135 精确值 0.135000000000000008881784 → HALF_EVEN 0.14 | HALF_UP 0.14
两位小数 1.005 精确值 1.004999999999999893418589 → HALF_EVEN 1.00 | HALF_UP 1.00
两位小数 2.675 精确值 2.674999999999999822364316 → HALF_EVEN 2.67 | HALF_UP 2.67
两位小数 0.285 精确值 0.284999999999999975575093 → HALF_EVEN 0.28 | HALF_UP 0.28
两位小数 1.115 精确值 1.114999999999999991118215 → HALF_EVEN 1.11 | HALF_UP 1.11
BigDecimal 输入(HALF_UP):1.005 → 1.01,2.675 → 2.68;double 输入:1.005 → 1.00,2.675 → 2.67
0.1 + 0.2 = 0.30000000000000004;format("0.################") → 0.3;BigDecimal 版 → 0.3
format(Long.MAX_VALUE) = 9,223,372,036,854,775,807;format(1e20 double,0.###) = 100,000,000,000,000,000,000;format(123456789.123456789 double) = 123,456,789.123
-0.0001 用 0.00 → -0.00(负零);0.00 对 -0.0 → -0.00 [parse]
parse("12" ) → 12 Long 消耗 2 / 2 个字符
parse("12abc" ) → 12 Long 消耗 2 / 5 个字符
parse(" 12" ) → null - 消耗 0 / 3 个字符(errorIndex=0)
parse("12 " ) → 12 Long 消耗 2 / 3 个字符
parse("1,234" ) → 1234 Long 消耗 5 / 5 个字符
parse("1,2,3" ) → 123 Long 消耗 5 / 5 个字符
parse("1.5.5" ) → 1.5 Double 消耗 3 / 5 个字符
parse("1e3" ) → 1 Long 消耗 1 / 3 个字符
parse("-5" ) → -5 Long 消耗 2 / 2 个字符
parse("+5" ) → null - 消耗 0 / 2 个字符(errorIndex=0)
parse("(5)" ) → null - 消耗 0 / 3 个字符(errorIndex=0)
parse("" ) → null - 消耗 0 / 0 个字符(errorIndex=0)
parse("abc" ) → null - 消耗 0 / 3 个字符(errorIndex=0)
parse("12," ) → 12 Long 消耗 2 / 3 个字符
parse(".5" ) → 0.5 Double 消耗 2 / 2 个字符
parse("٣٤" ) → 34 Long 消耗 2 / 2 个字符
parse("123" ) → 123 Long 消耗 3 / 3 个字符
parse("1_000" ) → 1 Long 消耗 1 / 5 个字符
parse("NaN" ) → NaN Double 消耗 3 / 3 个字符
parse("∞" ) → Infinity Double 消耗 1 / 1 个字符
nf.parse("abc") 抛 ParseException: Unparseable number: "abc",offset=0
nf.parse("12abc") 不抛异常,返回 12——要整串匹配必须自己检查 ParsePosition.getIndex() == length
默认 parse 返回:Double;parse("1234") 返回 Long;parse("12345678901234567890") 返回 1.2345678901234567E19(Double)
setParseBigDecimal(true):parse("12345678901234567890.123456789") → 12345678901234567890.123456789(BigDecimal)
负数子模式 (…):format(-1234.5) = (1,234.50);parse("(1,234.50)") = -1234.5;parse("-1,234.50") = ParseException(offset 0) [locale]
en_US 1,234,567.891 货币 $1,234,567.89 小数点 .(U+002E) 分组 ,(U+002C)
de_DE 1.234.567,891 货币 1.234.567,89 € 小数点 ,(U+002C) 分组 .(U+002E)
fr_FR 1 234 567,891 货币 1 234 567,89 € 小数点 ,(U+002C) 分组 (U+202F)
de_CH 1’234’567.891 货币 CHF 1’234’567.89 小数点 .(U+002E) 分组 ’(U+2019)
en_IN 1,234,567.891 货币 ₹1,234,567.89 小数点 .(U+002E) 分组 ,(U+002C)
hi_IN 1,234,567.891 货币 ₹1,234,567.89 小数点 .(U+002E) 分组 ,(U+002C)
zh_CN 1,234,567.891 货币 ¥1,234,567.89 小数点 .(U+002E) 分组 ,(U+002C)
ja_JP 1,234,567.891 货币 ¥1,234,568 小数点 .(U+002E) 分组 ,(U+002C)
ar_EG ١٬٢٣٤٬٥٦٧٫٨٩١ 货币 ١٬٢٣٤٬٥٦٧٫٨٩ ج.م. 小数点 ٫(U+066B) 分组 ٬(U+066C)
fa_IR ۱٬۲۳۴٬۵۶۷٫۸۹۱ 货币 ریال۱٬۲۳۴٬۵۶۷٫۸۹ 小数点 ٫(U+066B) 分组 ٬(U+066C)
解析的 Locale 错位:
de_DE 解析 "1.234" → 1234 (消耗 5 / 5)
de_DE 解析 "1,234" → 1.234 (消耗 5 / 5)
en_US 解析 "1.234,56" → 1.234 (消耗 5 / 8)
en_US 解析 "1,234.56" → 1234.56 (消耗 8 / 8)
de_DE 解析 "1.234,56" → 1234.56 (消耗 8 / 8)
fr_FR 解析 "1 234,56" → 1 (消耗 1 / 8)
fr_FR 解析 "1 234,56" → 1234.56 (消耗 8 / 8)
NaN 与无穷的符号:NaN / ∞;format(NaN) = NaN;format(+∞) = ∞;德语 NaN = NaN
货币 Currency.getInstance(Locale.JAPAN) 小数位 = 0;USD = 2;JOD(约旦第纳尔)= 3;CLF = 4 [compact]
en_US SHORT 999 | 1K | 1K | 12K | 1M | 1M | 2M | 123M | 1T |
en_US LONG 999 | 1 thousand | 1 thousand | 12 thousand | 1 million | 1 million | 2 million | 123 million | 1 trillion |
zh_CN SHORT 999 | 1,000 | 1,234 | 1万 | 100万 | 100万 | 150万 | 1亿 | 1万亿 |
zh_CN LONG 999 | 1,000 | 1,234 | 1万 | 100万 | 100万 | 150万 | 1亿 | 1万亿 |
de_DE SHORT 999 | 1.000 | 1.234 | 12.345 | 999.999 | 1 Mio. | 2 Mio. | 123 Mio. | 1 Bio. |
de_DE LONG 999 | 1 Tausend | 1 Tausend | 12 Tausend | 1 Million | 1 Million | 2 Million | 123 Millionen | 1 Billion |
ja_JP SHORT 999 | 1,000 | 1,234 | 1万 | 100万 | 100万 | 150万 | 1億 | 1兆 |
ja_JP LONG 999 | 1,000 | 1,234 | 1万 | 100万 | 100万 | 150万 | 1億 | 1兆 |
(依次是 999、1000、1234、12345、999999、1000000、1500000、123456789、1234567890123)
US SHORT,setMaximumFractionDigits(1):1234 → 1.2K,1500000 → 1.5M,999999 → 1M,1050 → 1.1K,1950 → 1.9K
setRoundingMode(DOWN):999999 → 999.9K,1950 → 1.9K
parse:1500000 / 12000 / 15000 [thread]
8 个线程共用同一个 DecimalFormat 各 100000 次(共 800000 次):结果不对 0 次,抛异常 0 次;例:null
共用同一个 DecimalFormat,输入类型 BigDecimal,8 线程各 100000 次:结果不对 0 次,抛异常 0 次;例:null
共用同一个 DecimalFormat,输入类型 long,8 线程各 100000 次:结果不对 0 次,抛异常 0 次;例:null
共用同一个 DecimalFormat,输入类型 double(带负数子模式),8 线程各 100000 次:结果不对 0 次,抛异常 0 次;例:null
共用同一个 DecimalFormat 做 parse(setParseBigDecimal=false),8 线程各 100000 次:结果不对 292186 次,抛异常 12541 次;例:7204.25 (期望 7004.25)
共用同一个 DecimalFormat 做 parse(setParseBigDecimal=true),8 线程各 100000 次:结果不对 274181 次,抛异常 0 次;例:1014.25 (期望 1022.25)
一个线程不断 setMinimum/MaximumFractionDigits(0..3),4 个线程格式化 1234.5678:出现的不同结果 [ , . , .568, 1,234.568, 1,234.57, 1,234.6, 1,235, 1,235.];异常 1318 次(java.lang.NullPointerException: Cannot assign field "fastPathContainer" beca
每线程一个 ThreadLocal<DecimalFormat>:结果不对 0 次
第 4 轮:复用实例 226 ns;每次 new DecimalFormat 573 ns;每次 NumberFormat.getInstance 318 ns
第 5 轮:复用实例 237 ns;每次 new DecimalFormat 637 ns;每次 NumberFormat.getInstance 353 ns
(自写计时循环,不是 JMH;sink=true)