格式化商业高精度数据 即包含逗号的格式
环境
jdk:1.7
前言
因为某种原因,导致,原本是前端去格式化的工作,现在需要后台API
这边进行格式化;
例如:
21024.00 -> 21,024.00
虽然这个实现起来很简单,网上有很多教程。
但是,今天我,特意研究了;
现在做下笔记,因为我发现,网上存在很多片面的知识!
非常容易误导人!
DecimalFormat
要实现格式化的话,就需要用到DecimalFormat
类。
其是NumberFormat
的一个子类,NumberFormat
也可以格式化;
但是网上说不能自定义;因为我业务需要自定义,所以我这里只说DecimalFormat
。
以我的业务为例:
就是每三位数字加个逗号,并且要保留2位小数,没有就补00
;
因为比较简单,我就直接贴代码:
/**
* 将数字格式化为中间有逗号的形式
* @param style 你要格式化的格式 比如 ,###.00#;21024.00 -> 21,024.00
* @param value 数字
* @return
* @author yutao
* @date 2018年1月4日上午9:43:49
*/
public static String numberOfCommaFormat(String style, Double value){
if(value == null){
return null;
}
DecimalFormat df = new DecimalFormat();
df.applyPattern(style);
return df.format(value);
}
调用的话就是:
String style = "#,##0.00";
numberOfCommaFormat(style, 21024.00);
结果就是:21,024.00
;
Pattern
我们发现,其实这里关键就是自定义的Pattern
;
而模式(Pattern
),又是有正负之分的,上面我的代码中,我没有写负数的模式,这种情况下,将使用正数的模式,只不过,前缀变成了-
。(目前看不懂没关系,后面讲解)
名称 | 描述 |
---|---|
Pattern | 模式;分为正数模式和负数模式 |
PositivePattern | 正数模式;格式:前缀(可选)数字 后缀(可选) |
NegativePattern | 负数模式;格式:前缀(可选)数字 后缀(可选) |
Prefix | 前缀;任意的Unicode字符,\uFFFE, \uFFFF和特殊字符除外 |
Suffix | 后缀;任意的Unicode字符,\uFFFE, \uFFFF和特殊字符除外 |
Number | 数字; 格式1:整数 科学计数法(可选) 格式2:整数部分.小数部分 科学记数法(可选) |
Integer | 整数部分;可以为以下格式: MinimumInteger # # Integer # , Integer |
MinimumInteger | 最小整数个数;可以为以下格式: 0 0 MinimumInteger 0 , MinimumInteger |
Fraction | 小数部分;MinimumFraction(可选) OptionalFraction(可选) |
MinimumFraction | 小数部分最小个数;格式:0 MinimumFraction(可选) |
OptionalFraction | OptionalFraction(可选) |
Exponent | 科学计数法; 格式:E MinimumExponent |
MinimumExponent | 最小 格式:0 MinimumExponent(可选) |
通过上面的图表我们可以知道,pattern
的基本构成是:
前缀 数字 后缀
比如:
#,##0.00
这个数字没有定义前缀和后缀,只定义了数字部分;
我是前缀#,##0.00我是后缀
↑ 这个定义了前缀和后缀:
System.out.println(numberOfCommaFormat("我是前缀00.###E0", 0.00123 ));
执行后的效果:
我是前缀12.3E-4我是后缀
模式中的正负模式
一个模式可以通过分号;
来区分正负模式;分号前面为正数模式,后面负数模式
String pattern="###,###.##正数后缀;-###,###.##负数后缀";
System.out.println(numberOfCommaFormat(pattern, -1223233.456));
这里我们可以看到 正数的模式###,###.##正数后缀
;
负数模式-###,###.##负数后缀
;
默认不写负数模式的话,就是使用正数的模式,只不过系统会把前缀改为
-
但是,我们自己定义了负数的模式的时候,就需要注意以下几个问题:
① 负数模式只能更改前缀和后缀,数字部分是无法修改的,即使改了,也是正式模式的效果;
② 修改负数模式的数字部分要是和正数数字部分不一样时,会以正数数字模式的个数为基准来跳过负数模式的数字部分相应的字符个数;
这里我们现在看一个官方的例子:
官方文档说:
# 我只截取了一部分
That means that "#,##0.0#;(#)" produces precisely the same behavior as "#,##0.0#;(#,##0.0#)".
执行如下代码:
System.out.println(numberOfCommaFormat("#,##0.0#;(#)", -11D));
System.out.println(numberOfCommaFormat("#,##0.0#;(#,##0.0#)", -11D));
System.out.println(numberOfCommaFormat("#,##0.0#;(#)", 11D));
System.out.println(numberOfCommaFormat("#,##0.0#;(#,##0.0#)", 11D));
结果为:
(11.0
(11.0)
11.0
11.0
前面两个是传入 负数的效果,后面两个是传入正数的效果;
你会发现,传入负数时,后缀不一样啦!但是数字部分是一样的!
为什么后缀不一样呢?
我们先来看正式模式的数字部分#,##0.0#
,这里共有8个字符。
而上例中,我们把负数的数字格式修改为#
,又因为我们的修改并不会失效,所以系统会在我们自己定义的负数模式中从数字部分开始,跳过相应个数的字符,在这个例子中,它会跳过8个字符,要是后面还有字符的话,就当后缀处理啦!
比如:
我们把格式改下:
System.out.println(numberOfCommaFormat("#,##0.0#;(#))))))))", -11D));
//结果为:
(11.0)
//其从#开始,跳过8个字符发现还有个字符`)`,就把它当做后缀,所以结果为(11.0)
同时你也会发现,按照上面的格式,怎么把-11
,格式化成正数11
呢?
这说明自定义负数格式时,当我们改变前缀时,其原本的负号-
,不会自动加上去;
Special Pattern Characters
符合 | 位置 | 本地化 | 描述 |
---|---|---|---|
0 | 数字部分 | yes | 数字,严格匹配,还是没有就补0 |
# | 数字部分 | yes | 数字,要是没有就不显示 |
. | 数字部分 | yes | 小数分隔符或货币小数点分隔符 |
- | 数字部分 | yes | 负号 |
, | 数字部分 | yes | 分组分隔符 |
E | 数字部分 | yes | 用科学记数法分隔尾数和指数。在前缀或后缀中不需要引号 |
; | 子模式分割边界 | yes | 分割正数和负数的子模式 |
% | 前缀或者后缀 | yes | 乘以100,并以百分比的形式显示 |
\u2030 | 前缀或者后缀 | yes | 乘以1000,并以千分比的形式显示 |
¤(\u00A4) | 前缀或者后缀 | no | 货币符号,由货币符号取代。 如果加倍,用国际货币符号代替。如果出现在模式中,则使用货币小数点分隔符代替小数点分隔符 |
‘ | 前缀或者后缀 | no | 在前缀或者后缀中对特殊字符使用单引号; 例如:”’#’#” 格式化123,结果”#123”. 要使用单引号本身,可以同时使用两个单引号”# o”clock”. |
指定多个分组 只有一个生效
"#,##,###,####"
"######,####"
"##,####,####"
这里有三个格式,但是他们最终的结果都是一样的;
System.out.println(numberOfCommaFormat("#,##,###,####", 24112311233.668D));
System.out.println(numberOfCommaFormat("######,####", 24112311233.668D));
System.out.println(numberOfCommaFormat("##,####,###", 24112311233.668D));
结果都是:
241,1231,1234
241,1231,1234
241,1231,1234
虽然指定了多个分组格式,但是只是最后一个生效;
科学计数法
根据上面的pattern
的语法规则:
要使用E0
;
System.out.println(numberOfCommaFormat("我是前缀00.###E0我是后缀", 123));
结果为:
我是前缀12.3E1我是后缀
这里一定要注意E后面的那个0是必须的,这是语法规则要求的;
仔细想想,也对,0表示的是必须要有值,否则就是补0;
既然要是有科学计数法,那指数部分肯定是要有值的。
再看几个例子:
System.out.println(numberOfCommaFormat("00.###E0", 123));
System.out.println(numberOfCommaFormat("##0.#####E0", 223456 ));
执行结果为:
12.3E1
223.456E3
注意:
1、如果整数部分的最大位数 大于其整数部分的最小位数并且大于1,系统就会将幂强制为整数部分最大位数的整数倍,并且整数部分的最小位数被强制为1位。
例如:格式##0.#####E0
,数字12345
,格式化后为12.345E3
,数字123456
,格式化后,123.456E3
;
为什么呢?
先看整数部分##0
,最大位数为3
位,最小位数为1
(#
表示可有可无,0
表示没有就补0
,也就是这个位置上一定会有数字);
3-1=2>1, 满足条件,所以幂为3
的倍数。
2、如果不满足条件1的话,那么将通过幂来得出整数部分的最小位数。
例如:数字0.00123
,格式00.###E0
,格式化后为12.3E-4
这句话理解起来有点别扭,其实要使条件1
不满足,只有两种情况:
①整数部分全部都用0表示
②整数部分只有一位
这两种情况其实已经确定了整数的最小位数的值;
3、在尾数中的有效位的个数是整数部分的最小位数加上小数部分的最大位数,整数部分的最大位数并不会影响到它。例如:
数字12345
,格式##0.##E0
,格式化为12.3E3
;
4、幂模式不能包含分组符逗号,
。
其中1 和 2、3是互斥的,也就是满足1的情况下,2、3可以不满足。
总结
一般我们的业务要求就是把每3三位数字用逗号分割。
我扩展讲了下,正负格式和科学计数法;其中科学计数法和负数格式都有需要注意的地方;
负数格式:一旦自定义了格式,只能改前缀和后缀;所以建议数字部分和正数格式保持一致。
科学计数法:其实一般都不会用到,不但用不到,我们一般还要使用BigDecimal
把它转成数字来进行计算;
纯属因为网上讲的太烂啦!自己对着官方文档测试一遍后,做个笔记。
参考地址:
上一篇: #后端#项目#自定义接口异常处理
推荐阅读