java百分比用什么类型(java计算百分比代码)
bigdecimal
这篇文章我们会介绍一下java 中的bigdecimal,并且会通过一些例子演示它的用法,例如精度的操作
java在java.math包中提供的api类bigdecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用float和double处理,但是double.valueof(string) 和float.valueof(string)会丢失精度。所以开发中,如果我们需要精确计算的结果,则必须使用bigdecimal类来操作。
为什么需要bigdecimal
前面学习基本类型的时候,我们可以使用float 和 double来表示浮点型数字,但是这里有一个问题那就是基本数据类型float 和 double不应该用于高精度数据的表示,例如货币,因为浮点类型的float 和 double会丢失数据的精度
double d1 = 374.56;
double d2 = 374.26;
system.out.println( "d1 - d2 = " + ( d1 - d2 ));
这里本应输出的是0.30,但是实际上输出如下
d1 - d2 = 0.30000000000001137
这就是为什么在金融相关的程序里数据类型如此重要的原因,所以我们更好的选择是bigdecimal 而不是float 和 double
java bigdecimal 类
bigdecimal 是不可变的,任意精度的有符号十进制类型的数字,可用于货币计算
上面那个例子如果我们是使用bigdecimal来代替double 我们可以获得准确的值
bigdecimal bd1 = new bigdecimal("374.56");
bigdecimal bd2 = new bigdecimal("374.26");
system.out.println("bd1 - bd2 = " + bd1.subtract(bd2));
现在输出就和预期的一样了
bd1 - bd2 = 0.30
java 中的bigdecimal类继承自number并且实现了comparable接口
public class bigdecimal extends number implements comparable<bigdecimal> {
}
bigdecimal 类的构造方法
bigdecimal 提供了很多的构造方法,可以使用int ,char[],bigdecimal,string,doble ,long,int来初始化bigdecimal,bigdecimal 总共提供了18种构造方法,需要注意的实如果使用double 来初始化bigdecimal或许会再次引入精度的问题,下面提供了一个例子
bigdecimal bde = new bigdecimal(23.12);
system.out.println("" + bde.tostring());
输出结果是这样的
23.120000000000000994759830064140260219573974609375
thus it is always safe to go with a constructor that takes string as argument when representing a decimal value.
因此使用string 做为构造函数的参数来表示一个十进制的数字的时候总是安全的
bigdecimal bde = new bigdecimal("23.12");
system.out.println("" + bde.tostring());
output
23.12
bigdecimal 的精度
使用bigdecimal的一个理由是bigdecimal提供了精度控制(小数点后的数字的多少)和舍入模式,为了确定小数点后的保留几位数字你可以使用setscale(int scale) 方法,但是最好的是在使用精度的时候提供舍入模式,也就是setscale的重载方法
- setscale(int newscale, int roundingmode)
- setscale(int newscale, roundingmode roundingmode)
接下来我们通过一个例子演示一下我们为什么要这样做,假设我们在通过一个double值构造一个bigdecimal
bigdecimal bde = new bigdecimal(23.12);
system.out.println("value- " + bde.tostring());
system.out.println("scaled value- " + bde.setscale(1).tostring());
output
value- 23.120000000000000994759830064140260219573974609375
exception in thread "main" java.lang.arithmeticexception: rounding necessary
at java.base/java.math.bigdecimal.commonneedincrement(bigdecimal.java:4495)
at java.base/java.math.bigdecimal.needincrement(bigdecimal.java:4702)
at java.base/java.math.bigdecimal.divideandround(bigdecimal.java:4677)
at java.base/java.math.bigdecimal.setscale(bigdecimal.java:2811)
at java.base/java.math.bigdecimal.setscale(bigdecimal.java:2853)
at org.netjs.programs.app.main(app.java:15)
从上面的输出中我们看到进度已经丢失了,输出的bigdecimal 是
23.120000000000000994759830064140260219573974609375
并且我们看到当我们将精度设置为1 的时候并且没有提供舍入机制的时候导致arithmetic异常被抛出
bigdecimal 的舍入模式
如果你注意到了上面我们在讲精度设置的时候,它其实是有两个设置精度的重载方法,第二个参数代表的就是舍入模式模式的参数,bigdecimal提供了八种舍入模式,它们通过static final int 进行表示
public final static int round_up = 0;
public final static int round_down = 1;
public final static int round_ceiling = 2;
public final static int round_floor = 3;
public final static int round_half_up = 4;
public final static int round_half_down = 5;
public final static int round_half_even = 6;
public final static int round_unnecessary = 7;
需要注意的是在java.math包中也提供舍入模式的枚举值,需要注意的我们是推荐使用枚举值来代替使用int 类型的常量做舍入摸模式的参数
下面我们在设置进度的同时设置一下舍入模式,来避免arithmetic异常
@test
public void scale() {
bigdecimal bde = new bigdecimal(23.12);
system.out.println("scaled value- " + bde.setscale(1,1).tostring());
}
但是我们说了,我们推荐使用枚举值的舍入模式,而不是直接使用int 类型的常量,接下来我们看一下roundingmode 提供的枚举值
- ceiling– rounding mode to round towards positive infinity.
- down– rounding mode to round towards zero.
- floor– rounding mode to round towards negative infinity.
- half_down– rounding mode to round towards “nearest neighbor” unless both neighbors are equidistant, in which case round down.
- half_even– rounding mode to round towards the “nearest neighbor” unless both neighbors are equidistant, in which case, round towards the even neighbor.
- half_up– rounding mode to round towards “nearest neighbor” unless both neighbors are equidistant, in which case round up.
- unnecessary – rounding mode to assert that the requested operation has an exact result, hence no rounding is necessary.
- up– rounding mode to round away from zero.
下面我们通过例子来总结一下这些舍入模式的舍入方式
result of rounding input to one digit with the given rounding mode | ||||||||
input number | up | down | ceiling | floor | half_up | half_down | half_even | unnecessary |
5.5 | 6 | 5 | 6 | 5 | 6 | 5 | 6 | throw arithmeticexception |
2.5 | 3 | 2 | 3 | 2 | 3 | 2 | 2 | throw arithmeticexception |
1.6 | 2 | 1 | 2 | 1 | 2 | 2 | 2 | throw arithmeticexception |
1.1 | 2 | 1 | 2 | 1 | 1 | 1 | 1 | throw arithmeticexception |
1.0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
-1.0 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 |
-1.1 | -2 | -1 | -1 | -2 | -1 | -1 | -1 | throw arithmeticexception |
-1.6 | -2 | -1 | -1 | -2 | -2 | -2 | -2 | throw arithmeticexception |
-2.5 | -3 | -2 | -2 | -3 | -3 | -2 | -2 | throw arithmeticexception |
-5.5 | -6 | -5 | -5 | -6 | -6 | -5 | -6 | throw arithmeticexception |
更多细节参考–
docs.oracle.com/javase/8/do…
bigdecimal格式化
numberformat 格式化
由于numberformat类的format()方法可以使用bigdecimal对象作为其参数,可以利用bigdecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。
以利用bigdecimal对货币和百分比格式化为例。首先,创建bigdecimal对象,进行bigdecimal的算术运算后,分别建立对货币和百分比格式化的引用,最后利用bigdecimal对象作为format()方法的参数,输出其格式化的货币值和百分比。
@test
public void format() {
numberformat currency = numberformat.getcurrencyinstance(); //建立货币格式化引用
numberformat percent = numberformat.getpercentinstance(); //建立百分比格式化引用
percent.setmaximumfractiondigits(3); //百分比小数点最多3位
bigdecimal loanamount = new bigdecimal("15000.48"); //贷款金额
bigdecimal interestrate = new bigdecimal("0.008"); //利率
bigdecimal interest = loanamount.multiply(interestrate); //相乘
system.out.println("贷款金额:t" + currency.format(loanamount));
system.out.println("利率:t" + percent.format(interestrate));
system.out.println("利息:t" + currency.format(interest));
}
输出结果
贷款金额: ¥15,000.48
利率: 0.8%
利息: ¥120.00
decimalformat 格式化
bigdecimal格式化保留2为小数,不足则补0
@test
public void format2(){
decimalformat df = new decimalformat("#.00");
system.out.println(formattonumber(new bigdecimal("3.435")));
system.out.println(formattonumber(new bigdecimal(0)));
system.out.println(formattonumber(new bigdecimal("0.00")));
system.out.println(formattonumber(new bigdecimal("0.001")));
system.out.println(formattonumber(new bigdecimal("0.006")));
system.out.println(formattonumber(new bigdecimal("0.206")));
}
/**
* @desc
* 1. 0~1之间的bigdecimal小数,格式化后失去前面的0,则前面直接加上0。
* 2. 传入的参数等于0,则直接返回字符串"0.00"
* 3. 大于1的小数,直接格式化返回字符串
* @return
*/
public string formattonumber(bigdecimal obj) {
decimalformat df = new decimalformat("#.00");
if(obj.compareto(bigdecimal.zero)==0) {
return "0.00";
}else if(obj.compareto(bigdecimal.zero)>0&&obj.compareto(new bigdecimal(1))<0){
return "0"+df.format(obj).tostring();
}else {
return df.format(obj).tostring();
}
}
bigdecimal 例子
最常见的例子就是精度是2(小数点后保留两位),并且采用四舍五入的舍入模式(如果指定精度的笑一个数字大于等于5则向上取整,否则向下取整)
@test
public void examples() {
bigdecimal bd1 = new bigdecimal("23.126");
system.out.println("bd1 " + bd1.setscale(2, roundingmode.half_up).tostring());
}
// 输出结果
bd1 23.13
因为精度设置为2之后,也就是小数点后两位的后一位数字是6大于等于5,所以向上取整,所以结果是 23.13
bigdecimal bd1 = new bigdecimal("23.1236");
system.out.println("bd1 " + bd1.setscale(2, roundingmode.half_up).tostring());
因为精度设置为2之后,也就是小数点后两位的后一位数字是3小于5,所以向下取整,所以结果是 23.12
bigdecimal bd1 = new bigdecimal("-15.567");
system.out.println("bd1 " + bd1.setscale(2, roundingmode.half_up).tostring());
对于负数,也是同样的道理,所以输出是bd1 -15.57
bigdecimal 的特性
1. 没有重载操作符
在java 中支持的(+, -, *, /)数学运算,bigdecimal并不支持,因为这些操作符是针对基本数据类型的,但是bigdecimal是引用类型,也就是基于对象和类的,因此bigdecimal提供了下面的方法
add,subtract,multiply,and,divide
例如乘法在bigdecimal的实现如下
bigdecimal bd1 = new bigdecimal("15.567");
bigdecimal result = bigdecimal.valueof(68).multiply(bd1);
system.out.println("result " + result.tostring());
output
result 1058.556
2. 使用 compareto() 来比较bigdecimals 而不是使用 equals()
需要注意如果你使用 equals()来比较两个bigdecimal数字,那只有当两个bigdecimal的值和精度都相同的时候equals()猜认为它们是相同的(因此2.0和2.00是不相同的)
bigdecimal bd1 = new bigdecimal("2.00");
bigdecimal bd2 = new bigdecimal("2.0");
system.out.println("bd1 equals bd2 - " + bd1.equals(bd2));
output
bd1 equals bd2 - false
因此你应该使用compareto()方法来比较两个bigdecimal 是否是相等的,bigdecimal实现了comparable接口并且提供了自己的compareto方法,这个方法只会判断两个bigdecimal对象的值是否是相等的忽略了两个数字的精度(ike 2.0 和 2.00 相等的)
对于bd1.compareto(bd2) 的返回值
- -1 bd1 小于 bd2.
- 0 两个相等的
- 1 bd1 大于 bd2.
bigdecimal bd1 = new bigdecimal("2.00");
bigdecimal bd2 = new bigdecimal("2.0");
system.out.println("bd1 compareto bd2 - " + bd1.compareto(bd2));
output
bd1 compareto bd2 - 0
3. bigdecimals 是不可变的
bigdecimal 对象是不可变的,所以是线程安全的,在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。
总结
- 当我们在进行有着高精度的计算要求的时候不要使用double和float 因为它们有着精度丢失的问题
- 如果使用bigdecimal的时候,不要选择double值作为初始化的值,因为它同样会引入精度的问题
- 如果你使用bigdecimal时候设置了精度,那就同时提供舍入模式,告诉bigdecimal如何舍入从而提供你想要的精度
- bigdecimal继承了number类和实现了comparable接口
- bigdecimal 针对加减乘除提供可特定的方法,因为bigdecimal不支持(+, -, *, /)数学运算
- bigdecimal 的对象是不可变的
- bigdecimal因为创建对象开销的原因,所以很多操作都是比原生类型要慢一些的。