BigDecimal与Double的区别和使用场景
BigDecimal与Double的区别和使用场景
背景
在项目中发现开发小组成员在写程序时,对于Oracle
数据类型为Number
的字段(经纬度),实体映射类型有的人用Double
有的人用BigDecimal
,没有一个统一规范,为此我在这里总结记录一下。
一般说到BigDecimal
与Double
,绕不开的就是金融或电商行业,毕竟涉及到了钱的问题,数据的敏感程度很高,对数据精度要求也很高。
BigDecimal
与Double
于两种类型在使用上都有一些缺点。
Double的问题
- 在计算时会出现不精确的问题
public static void main(String[] args) {
System.out.println(12.3 + 45.6); // 57.900000000000006
System.out.println(12.3 / 100); // 0.12300000000000001
}
- 小数部分无法使用二进制准确的表示
- 等于判断在使用时需要注意
public static void main(String[] args) {
double a = 2.111111111111111111111111112;
double b = 2.111111111111111111111111113;
// duoble超过15位以后就会不对了
System.out.println(a == b); // true
}
BigDecimal的问题
- 使用除法时除不尽会报
ArithmeticException
异常
public static void main(String[] args) {
// 报异常:Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
System.out.println(BigDecimal.valueOf(121).divide(BigDecimal.valueOf(3)));
}
public static void main(String[] args) {
// 需要指定精度和舍入方式,当除不尽时也不会报异常
// 运行结果为 40.33
System.out.println(BigDecimal.valueOf(121).divide(BigDecimal.valueOf(3), 2, RoundingMode.HALF_UP));
}
new BigDecimal(double)
结果或许预料不到。
public static void main(String[] args) {
BigDecimal a = new BigDecimal(12.3);
BigDecimal b = BigDecimal.valueOf(12.3);
// Double的小数位其实无法被精确表示,所以传入的.3被精度扩展之后精度丢失,展示出来的并不是精确的.3
// 结果为12.300000000000000710542735760100185871124267578125
System.out.println(a);
// 从底层代码可以看出来,BigDecimal.valueOf 会先转换为字符串之后再调用new BigDecimal,不会造成精度丢失
// 结果为12.3
System.out.println(b);
}
所以一般情况下会使用BigDecimal.valueOf()
而不是new BigDecimal()
。
另:
BigDecimal.valueOf()
是静态工厂类,永远优先于构造函数。(摘自《Effective java》)
BigDecimal
是不可变类
不可变类代表着,任何针对BigDecimal
的修改都将产生新对象。所以每次对BigDecimal
的修改都要重新指向一个新的对象。
public static void main(String[] args) {
BigDecimal a = BigDecimal.valueOf(12.3);
a.add(BigDecimal.valueOf(2.1));
System.out.println(a); // 12.3,值未修改
BigDecimal b = BigDecimal.valueOf(12.3);
// 重新赋值给新对象
BigDecimal c = b.add(BigDecimal.valueOf(2.1));
System.out.println(c); // 14.4
}
- 比较大小不方便
BigDecimal
大小的比较都需要使用compareTo
,如果需要返回更大的数或更小的数可以使用max
、min
。还要注意在BigDecimal
中慎用equals
。
public static void main(String[] args) {
BigDecimal a = BigDecimal.valueOf(12.3);
BigDecimal b = BigDecimal.valueOf(12.32);
System.out.println(a.compareTo(b)); // -1
System.out.println(b.compareTo(a)); //1
System.out.println(a.max(b)); // 12.32
System.out.println(a.min(b)); // 12.3
System.out.println(b.max(a)); // 12.32
System.out.println(b.min(a)); // 12.3
System.out.println(BigDecimal.valueOf(1).equals(BigDecimal.valueOf(1.0))); //false
}
BigDecimal
重写了equals
方法,在equals
方法里比较了小数位数,在方法注释上也有说明,所以 BigDecimal.valueOf(1).equals(BigDecimal.valueOf(1.0))
为什么结果为false
就可以理解了。在附录中会贴出 BigDecimal
中的 equals
方法的源码。
通过源码可以知道 if (scale != xDec.scale)
这句代码就是比较了小数位数,不等则直接返回false
。
背景
一直从事金融相关项目,所以对BigDecimal再熟悉不过了,也曾看到很多同学因为不知道、不了解或使用不当导致资损事件发生。
所以,如果你从事金融相关项目,或者你的项目中涉及到金额的计算,那么你一定要花时间看看这篇文章,全面学习一下BigDecimal。
BigDecimal概述
Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。
一般情况下,对于不需要准确计算精度的数字,可以直接使用Float和Double处理,但是Double.valueOf(String) 和Float.valueOf(String)会丢失精度。所以如果需要精确计算的结果,则必须使用BigDecimal类来操作。
BigDecimal对象提供了传统的+、-、*、/等算术运算符对应的方法,通过这些方法进行相应的操作。BigDecimal都是不可变的(immutable)的, 在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。
BigDecimal的4个坑
在使用BigDecimal时,有4种使用场景下的坑,你一定要了解一下,如果使用不当,必定很惨。掌握这些案例,当别人写出有坑的代码,你也能够一眼识别出来,大牛就是这么练成的。
第一:浮点类型的坑
在学习了解BigDecimal的坑之前,先来说一个老生常谈的问题:如果使用Float、Double等浮点类型进行计算时,有可能得到的是一个近似值,而不是精确的值。
比如下面的代码:
@Test
public void test0(){
float a = 1;
float b = 0.9f;
System.out.println(a - b);
}
结果是多少?0.1吗?不是,执行上面代码执行的结果是0.100000024。之所以产生这样的结果,是因为0.1的二进制表示是无限循环的。由于计算机的资源是有限的,所以是没办法用二进制精确的表示 0.1,只能用「近似值」来表示,就是在有限的精度情况下,最大化接近 0.1 的二进制数,于是就会造成精度缺失的情况。
关于上述的现象大家都知道,不再详细展开。同时,还会得出结论在科学计数法时可考虑使用浮点类型,但如果是涉及到金额计算要使用BigDecimal来计算。
那么,BigDecimal就一定能避免上述的浮点问题吗?来看下面的示例:
@Test
public void test1(){
BigDecimal a = new BigDecimal(0.01);
BigDecimal b = BigDecimal.valueOf(0.01);
System.out.println("a = " + a);
System.out.println("b = " + b);
}
上述单元测试中的代码,a和b结果分别是什么?
a = 0.01000000000000000020816681711721685132943093776702880859375
b = 0.01
上面的实例说明,即便是使用BigDecimal,结果依旧会出现精度问题。这就涉及到创建BigDecimal对象时,如果有初始值,是采用new BigDecimal的形式,还是通过BigDecimal#valueOf方法了。
之所以会出现上述现象,是因为new BigDecimal时,传入的0.1已经是浮点类型了,鉴于上面说的这个值只是近似值,在使用new BigDecimal时就把这个近似值完整的保留下来了。
而BigDecimal#valueOf则不同,它的源码实现如下:
public static BigDecimal valueOf(double val) {
// Reminder: a zero double returns '0.0', so we cannot fastpath
// to use the constant ZERO. This might be important enough to
// justify a factory approach, a cache, or a few private
// constants, later.
return new BigDecimal(Double.toString(val));
}
在valueOf内部,使用Double#toString方法,将浮点类型的值转换成了字符串,因此就不存在精度丢失问题了。
此时就得出一个基本的结论:第一,在使用BigDecimal构造函数时,尽量传递字符串而非浮点类型;第二,如果无法满足第一条,则可采用BigDecimal#valueOf方法来构造初始化值。
这里延伸一下,BigDecimal常见的构造方法有如下几种:
BigDecimal(int) 创建一个具有参数所指定整数值的对象。
BigDecimal(double) 创建一个具有参数所指定双精度值的对象。
BigDecimal(long) 创建一个具有参数所指定长整数值的对象。
BigDecimal(String) 创建一个具有参数所指定以字符串表示的数值的对象。
其中涉及到参数类型为double的构造方法,会出现上述的问题,使用时需特别留意。
第二:浮点精度的坑
如果比较两个BigDecimal的值是否相等,你会如何比较?使用equals方法还是compareTo方法呢?
先来看一个示例:
@Test
public void test2(){
BigDecimal a = new BigDecimal("0.01");
BigDecimal b = new BigDecimal("0.010");
System.out.println(a.equals(b));
System.out.println(a.compareTo(b));
}
乍一看感觉可能相等,但实际上它们的本质并不相同。
equals方法是基于BigDecimal实现的equals方法来进行比较的,直观印象就是比较两个对象是否相同,那么代码是如何实现的呢?
@Override
public boolean equals(Object x) {
if (!(x instanceof BigDecimal))
return false;
BigDecimal xDec = (BigDecimal) x;
if (x == this)
return true;
if (scale != xDec.scale)
return false;
long s = this.intCompact;
long xs = xDec.intCompact;
if (s != INFLATED) {
if (xs == INFLATED)
xs = compactValFor(xDec.intVal);
return xs == s;
} else if (xs != INFLATED)
return xs == compactValFor(this.intVal);
return this.inflated().equals(xDec.inflated());
}
仔细阅读代码可以看出,equals方法不仅比较了值是否相等,还比较了精度是否相同。上述示例中,由于两者的精度不同,所以equals方法的结果当然是false了。而compareTo方法实现了Comparable接口,真正比较的是值的大小,返回的值为-1(小于),0(等于),1(大于)。
基本结论:通常情况,如果比较两个BigDecimal值的大小,采用其实现的compareTo方法;如果严格限制精度的比较,那么则可考虑使用equals方法。
另外,这种场景在比较0值的时候比较常见,比如比较BigDecimal(“0”)、BigDecimal(“0.0”)、BigDecimal(“0.00”),此时一定要使用compareTo方法进行比较。
第三:设置精度的坑
在项目中看到好多同学通过BigDecimal进行计算时不设置计算结果的精度和舍入模式,真是着急人,虽然大多数情况下不会出现什么问题。但下面的场景就不一定了:
@Test
public void test3(){
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("3.0");
a.divide(b);
}
执行上述代码的结果是什么?ArithmeticException异常!
java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
at java.math.BigDecimal.divide(BigDecimal.java:1690)
...
这个异常的发生在官方文档中也有说明:
If the quotient has a nonterminating decimal expansion and the operation is specified to return an exact result, an ArithmeticException is thrown. Otherwise, the exact result of the division is returned, as done for other operations.
总结一下就是,如果在除法(divide)运算过程中,如果商是一个无限小数(0.333…),而操作的结果预期是一个精确的数字,那么将会抛出ArithmeticException
异常。
此时,只需在使用divide方法时指定结果的精度即可:
@Test
public void test3(){
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("3.0");
BigDecimal c = a.divide(b, 2,RoundingMode.HALF_UP);
System.out.println(c);
}
执行上述代码,输入结果为0.33。
基本结论:在使用BigDecimal进行(所有)运算时,一定要明确指定精度和舍入模式。
拓展一下,舍入模式定义在RoundingMode枚举类中,共有8种:
- RoundingMode.UP:舍入远离零的舍入模式。在丢弃非零部分之前始终增加数字(始终对非零舍弃部分前面的数字加1)。注意,此舍入模式始终不会减少计算值的大小。
- RoundingMode.DOWN:接近零的舍入模式。在丢弃某部分之前始终不增加数字(从不对舍弃部分前面的数字加1,即截短)。注意,此舍入模式始终不会增加计算值的大小。
- RoundingMode.CEILING:接近正无穷大的舍入模式。如果 BigDecimal 为正,则舍入行为与 ROUNDUP 相同;如果为负,则舍入行为与 ROUNDDOWN 相同。注意,此舍入模式始终不会减少计算值。
- RoundingMode.FLOOR:接近负无穷大的舍入模式。如果 BigDecimal 为正,则舍入行为与 ROUNDDOWN 相同;如果为负,则舍入行为与 ROUNDUP 相同。注意,此舍入模式始终不会增加计算值。
- RoundingMode.HALF_UP:向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。如果舍弃部分 >= 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同。注意,这是我们在小学时学过的舍入模式(四舍五入)。
- RoundingMode.HALF_DOWN:向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入模式。如果舍弃部分 > 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同(五舍六入)。
- RoundingMode.HALF_EVEN:向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。如果舍弃部分左边的数字为奇数,则舍入行为与 ROUNDHALFUP 相同;如果为偶数,则舍入行为与 ROUNDHALF_DOWN 相同。注意,在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。此舍入模式也称为“银行家舍入法”,主要在美国使用。四舍六入,五分两种情况。如果前一位为奇数,则入位,否则舍去。以下例子为保留小数点1位,那么这种舍入方式下的结果。
1.15 ==> 1.2 ,1.25 ==> 1.2
- RoundingMode.UNNECESSARY:断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。
通常我们使用的四舍五入即RoundingMode.HALF_UP。
第四:三种字符串输出的坑
当使用BigDecimal之后,需要转换成String类型,你是如何操作的?直接toString?
先来看看下面的代码:
@Test
public void test4(){
BigDecimal a = BigDecimal.valueOf(35634535255456719.22345634534124578902);
System.out.println(a.toString());
}
执行的结果是上述对应的值吗?并不是:
3.563453525545672E+16
也就是说,本来想打印字符串的,结果打印出来的是科学计数法的值。
这里我们需要了解BigDecimal转换字符串的三个方法
- toPlainString():不使用任何科学计数法;
- toString():在必要的时候使用科学计数法;
- toEngineeringString() :在必要的时候使用工程计数法。类似于科学计数法,只不过指数的幂都是3的倍数,这样方便工程上的应用,因为在很多单位转换的时候都是10^3;
三种方法展示结果示例如下:
基本结论:根据数据结果展示格式不同,采用不同的字符串输出方法,通常使用比较多的方法为toPlainString() 。
另外,NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。
使用示例如下:
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
前段时间在开发过程中遇到了数值计算问题,用到了double浮点型来计算,但是因为double有效位数为16位就会出现储存小数位数不够的情况,在这种情况下运算就会出现误差,所有对于高精度计算,像金额、分数等建议使用BigDecimal,接下来分析一下BigDecimal和Double的区别,它俩更适合在什么条件下使用?
1、BigDecimal和Double区别?
1、1 Double分析
double 类型的底层实现是使用 IEEE 754 标准来表示浮点数。在 Java 中,double 类型的变量占用 8 个字节,其中 1 个字节用于表示符号位,11 个字节用于表示指数,剩余的 52 个字节用于表示尾数。由于尾数只有 52 个字节,因此 double 类型能够精确表示的数字是有限的。
在 Java 中,double 类型的值可以用以下公式来计算:
value = (-1)^s * m * 2^e
其中,s 表示符号位,m 表示尾数,e 表示指数。在 double 类型中,符号位占用 1 个字节,尾数占用 52 个字节,指数占用 11 个字节。
在 double 类型中,尾数使用二进制表示,指数使用移位表示。具体来说,指数的值先减去 1023,然后再左移 52 位。这样可以将指数的二进制表示与尾数的二进制表示拼接起来,得到一个 64 位的二进制数,表示一个 double 类型的值。
double 类型的运算是通过对二进制数进行位运算来实现的。例如,两个 double 类型的值相加时,先将它们的二进制表示对齐,然后逐位相加,并将进位的部分保存下来。这样可以保证精度,并避免浮点数精度问题导致的计算错误。
1、2 BigDecimal分析
BigDecimal 类的底层实现是使用一个整数数组来表示一个高精度的十进制数字。在 Java 中,BigDecimal 类的实现方式可以分为两种:基于 int 数组的实现方式和基于 long 数组的实现方式。这两种实现方式的区别在于使用的数组类型不同,但它们的原理都是一样的。
在 BigDecimal 类中,每个数字都是用一个 int 或 long 类型的变量来表示的。例如,对于一个十进制数 123456789,可以使用一个 int 数组来表示它:
int[] digits = {9, 8, 7, 6, 5, 4, 3, 2, 1};
在 BigDecimal 类中,还定义了一些常量,例如 ZERO、ONE、TEN 等。这些常量都是 BigDecimal 类型的对象,用于表示常见的数字。
BigDecimal 类提供了各种精确计算方法,包括加、减、乘、除等操作。在进行这些操作时,BigDecimal 类会根据实际情况选择合适的算法来保证精度。例如,在进行加法操作时,BigDecimal 类会将两个 BigDecimal 对象的小数部分对齐,然后逐位相加,并将进位的部分保存下来。这样可以保证精度,并避免浮点数精度问题导致的计算错误。
使用BigDecimal需要注意的事项:
1、两个BigDecimal值不能使用“ +, -, *, / ” 进行加减乘除,要使用“ add, substract, multiply, divide ”;
2、两个BigDecimal值比较使用compareTo方法, 比较结果有-1, 0, 1, 分别表示小于, 等于, 大于; 对于0, 可以使用BigDecimal.ZERO表示;
3、提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 定精度,以后的数字四舍五入;
4、因double有效位数为 16位这就会出现存储小数位数不够的情况,这种情况下就会出现误差,解决方法就是使用BigDecimal,它的有
效长度足够长可存储 小数位数因此可代替double来进行加减乘除, 金额必须是完全精确的计算, 故不能使用double或者float, 而
应该采用java.math.BigDecimal.
5、mysql数据库设计
BigDecimal在进行入库时, 数据库选择decimal类型, 长度可以自定义, 如18; 小数点我们项目中用的是2, 保留2位小数. 此外还要注意的就是默认值,
一定写成0.00, 不要用默认的NULL, 否则在进行加减排序等操作时, 会带来转换的麻烦!
如 :balance
decimal(18,2) DEFAULT ‘0.00’ COMMENT ‘账户余额’,
2、更适合在什么条件下使用?
上面分析可得在高精度计算情况下建议使用BigDecimal,但是BigDecimal运输会消耗更多的内存并且运算速度更慢一点,所有需要高速运算建议使用double
Double与BigDecimal数值操作效率比较
做了一个测试,从1累加到1000000,Double
与BigDecimal
的效率比:
public static void main(String[] args) {
double a = 0;
long startLong = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
a += i;
}
// 打印结果:double耗时:9
System.out.println("double耗时:" + (System.currentTimeMillis() - startLong));
BigDecimal b = new BigDecimal(0);
long startLong2 = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
b = b.add(BigDecimal.valueOf(i));
}
// 打印结果:BigDecimal耗时:83
System.out.println("BigDecimal耗时:" + (System.currentTimeMillis() - startLong2));
}
可以看到Double
比BigDecimal
的效率高了快10倍。
总结
使用
Double
:在计算过程中会出现精度丢失问题,但使用方便,计算效率高,在对精度要求不高的情况下建议使用。
使用
BigDecimal
:高精度,但做除法的时候要注意除不尽异常,且因为是不可变类和对象类型,做计算时没那么方便,效率比
Double
低。
所以可以知道,在使用场景上,如果涉及到精确的数值计算,比如典型的金额,一定要使用BigDecimal
进行计算,对精准度要求没那么高的可以不使用BigDecimal
。需要进行频繁计算的可以使用Double
。
其实Double
或者Float
这种浮点类型如果不参与计算只是传值的话,其实没有精度问题,如果要计算就得注意一下。
那么回到我们自己的项目上,经纬度其实一般业务代码也不会去进行计算,大多是用于传参定位记录用,涉及到计算的比如距离,如果是保留到6位小数时已经是1米级别了,可以满足绝大多数场景了,所以在经纬度上使用Double
就足够了。
附录
BigDecimal 的 equals方法源码。
/**
* Compares this {@code BigDecimal} with the specified
* {@code Object} for equality. Unlike {@link
* #compareTo(BigDecimal) compareTo}, this method considers two
* {@code BigDecimal} objects equal only if they are equal in
* value and scale (thus 2.0 is not equal to 2.00 when compared by
* this method).
* 该方法认为两个BigDecimal对象只有在值和比例相等时才相等,所以当使用该方法比较2.0与2.00时,二者不相等。
*
* @param x {@code Object} to which this {@code BigDecimal} is
* to be compared.
* @return {@code true} if and only if the specified {@code Object} is a
* {@code BigDecimal} whose value and scale are equal to this
* {@code BigDecimal}'s.
* @see #compareTo(java.math.BigDecimal)
* @see #hashCode
*/
@Override
public boolean equals(Object x) {
// 比较对象是否为 BigDecimal 数据类型,不是直接返回false
if (!(x instanceof BigDecimal))
return false;
BigDecimal xDec = (BigDecimal) x;
if (x == this)
return true;
// 比较 scale 值是否相等。在这里比较了小数位数,不等返回false。
// scale 是BigDecimal 的标度。如果为零或正数,则标度是小数点后的位数。
// 如果为负数,则将该数的非标度值乘以 10 的负 scale 次幂。例如,-3 标度是指非标度值乘以 1000。
if (scale != xDec.scale)
return false;
long s = this.intCompact;
long xs = xDec.intCompact;
if (s != INFLATED) {
if (xs == INFLATED)
xs = compactValFor(xDec.intVal);
return xs == s;
} else if (xs != INFLATED)
return xs == compactValFor(this.intVal);
return this.inflated().equals(xDec.inflated());
}
关于MySql中如何选用这两种类型
在查资料的时候还看到了关于MySql
中如何选用这两种类型的问题,也在此记录一下。
在数据库中除了指定数据类型之外还需要指定精度,所以在MySql
里Double
的计算精度丢失比在Java
里要高很多,Java
的默认精度到了15-16位。
在阿里的编码规范中也强调统一带小数类型的一律要使用Decimal
类型而不是Double
,使用Decimal
可以大大减少计算采坑的概率。
所以在选用类型时,与Java
同样,在精度要求不高的情况下可以使用Double
,比如经纬度,但是有需要计算、金融金额等优先使用Decimal
。
一、常规 double/Double 比较
注意:
- 两个整数相除返回的是取整后的double,建议:
a * 1.0 / b
float
小数点后为 6 位,double
小数点后为 16 位,推荐阅读:java中short、int、long、float、double取值范围
1、测试一
double a = 0.1;
double b = 0.1;
System.out.println(a); //0.1
System.out.println(b); //0.1
System.out.println(a > b); //false
System.out.println(a == b); //true
System.out.println("----------");
Double c = 0.1;
Double d = 0.1;
System.out.println(c); //0.1
System.out.println(d); //0.1
System.out.println(c == d); //false***
System.out.println(c.compareTo(d)); //0***
System.out.println(Double.compare(c, d)); //0***
2、测试二
double a = 2 * 1.0 / 3;
double b = 0.6666666666666666;
System.out.println(a); //0.6666666666666666
System.out.println(b); //0.6666666666666666
System.out.println(a > b); //false
System.out.println(a == b); //true
System.out.println("----------");
Double c = 2 * 1.0 / 3;
Double d = 0.6666666666666666;
System.out.println(c); //0.6666666666666666
System.out.println(d); //0.6666666666666666
System.out.println(c == d); //false***
System.out.println(c.compareTo(d)); //0***
System.out.println(Double.compare(c, d));//0***
3、测试三
int e = 1;
Double f = 1.0;
System.out.println(f.compareTo(Double.valueOf(e))); //0
4、保留小数点位数的方式
(1) 返回 double 型
Math.round
方式:能四舍五入
public static double decimalDigit(double d, int digit) { double pow = Math.pow(10, digit); return Math.round(d * pow) / pow; }
BigDecimal
方式:
double d = 114.145; BigDecimal b = new BigDecimal(d); d = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue(); System.out.println(d);
- BigDecimal.ROUND_HALF_UP 表示四舍五入
- BigDecimal.ROUND_HALF_DOWN 表示五舍六入
- BigDecimal.ROUND_UP表示进位处理(就是直接加1)
- BigDecimal.ROUND_DOWN表示直接去掉尾数
(2) 返回 String 型
#.00
:表示保留后两位,直接截掉不要的尾数,不四舍五入
double d = 114.145; DecimalFormat df = new DecimalFormat("#.00"); String str = df.format(d); System.out.println(str);
%.2f
:表示保留后两位,能四舍五入
double d = 114.145; String.format("%.2f", d);
NumberFormat
方式:
double d = 114.145 NumberFormat nf = NumberFormat.getNumberInstance(); // 保留两位小数 nf.setMaximumFractionDigits(2); // 如果不需要四舍五入,可以使用RoundingMode.DOWN nf.setRoundingMode(RoundingMode.UP); System.out.println(nf.format(d));
- RoundingMode.HALF_DOWN 表示五舍六入,负数先取绝对值再五舍六入再负数
- RoundingMode.HALF_UP 表示四舍五入,负数先取绝对值再五舍六入再负数
二、BigDecimal 构造函数
BigDecimal 用来对超过16位有效位的数进行精确的运算
1、常用构造函数
BigDecimal(int)
:创建一个具有参数所指定整数值的对象BigDecimal(double)
:创建一个具有参数所指定双精度值的对象BigDecimal(long)
:创建一个具有参数所指定长整数值的对象BigDecimal(String)
:创建一个具有参数所指定以字符串表示的数值的对象
2、使用注意
(1) 案例
BigDecimal a = new BigDecimal(0.1);
System.out.println("a values is:" + a);
System.out.println("=====================");
BigDecimal b = new BigDecimal("0.1");
System.out.println("b values is:" + b);
//结果
a values is:0.1000000000000000055511151231257827021181583404541015625
=====================
b values is:0.1
(2) 分析
参数类型为 double 的构造方法的结果有一定的不可预知性,这是因为 0.1 无法准确地表示为 double
参数类型为 String 的构造方法可预知,即 newBigDecimal(“0.1”) 创建的 BigDecimal 结果为 0.1
因此,建议优先使用 String 构造方法
当 double 必须用作 BigDecimal 源时,建议先使用 Double.toString(double) ,然后使用 BigDecimal(String) 构造方法,将double转换为String
要获取该结果,使用 static valueOf(double) 方法
三、BigDecimal 常用方法
add(BigDecimal)
:BigDecimal 对象中的值相加,返回 BigDecimal 对象subtract(BigDecimal)
:BigDecimal 对象中的值相减,返回 BigDecimal 对象multiply(BigDecimal)
:BigDecimal 对象中的值相乘,返回 BigDecimal 对象divide(BigDecimal)
:BigDecimal 对象中的值相除,返回 BigDecimal 对象toString()
:将 BigDecimal 对象中的值转换成字符串doubleValue()
:将 BigDecimal 对象中的值转换成双精度数floatValue()
:将 BigDecimal 对象中的值转换成单精度数longValue()
:将 BigDecimal 对象中的值转换成长整数intValue()
:将 BigDecimal 对象中的值转换成整数
abs()
:绝对值negate()
:相反数pow(int)
:幂次运算max(BigDecimal)
:比较大小并返回大的min(BigDecimal)
:比较大小并返回小的
四、BigDecimal 方法使用注意
1、比较大小
- BigDecimal 比较大小一般用 BigDecimal 的
compareTo
方法
int a = num1.compareTo(num2)
返回结果:
a = -1
:表示 num1 小于 num2a = 0
:表示 num1 等于 num2a = 1
:表示 num1 大于 num2
2、除法异常
divide
方法需设置精确的小数点,如:divide(xxxxx,2)
- 原因:当 divide 方法不能整除,出现无限循环小数时,会抛异常:java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.
五、舍入方式
scale()
:返回小数点后的位数percision()
:返回数的位数
BigDecimal b1 = new BigDecimal("11.111");
//位数
System.out.println(b1.precision());//5
//小数点后有多少位
System.out.println(b1.scale());//3
BigDecimal.ROUND_HALF_UP
:四舍五入模式
BigDecimal b1 = new BigDecimal("1.1449"); System.out.println(b1.setScale(2, BigDecimal.ROUND_HALF_UP));//1.14 System.out.println(b1.setScale(3, BigDecimal.ROUND_HALF_UP));//1.145
BigDecimal.ROUND_HALF_DOWN
:五舍六入模式
BigDecimal b1 = new BigDecimal("1.145"); System.out.println(b1.setScale(1, BigDecimal.ROUND_HALF_DOWN));//1.1 //.5 不大于.5 所以会舍去 System.out.println(b1.setScale(2, BigDecimal.ROUND_HALF_DOWN));//1.14
BigDecimal.ROUND_HALF_EVEN
:银行家舍入法
- 如果舍弃部分左边的数字为奇数,则舍入行为与 ROUND_HALF_UP 相同
- 如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同
BigDecimal b1 = new BigDecimal("1.24"); System.out.println(b1.setScale(1, BigDecimal.ROUND_HALF_EVEN));//1.2 b1 = new BigDecimal("1.25"); System.out.println(b1.setScale(1, BigDecimal.ROUND_HALF_EVEN));//1.2 b1 = new BigDecimal("1.26"); System.out.println(b1.setScale(1, BigDecimal.ROUND_HALF_EVEN));//1.3
BigDecimal.ROUND_UP
:始终在保留的最后一位加 1
BigDecimal b1 = new BigDecimal("1.1203"); System.out.println(b1.setScale(2, BigDecimal.ROUND_UP));//1.13 System.out.println(b1.setScale(3, BigDecimal.ROUND_UP));//1.121
BigDecimal.ROUND_DOWN
:从舍弃位置直接截断
BigDecimal b1 = new BigDecimal("1.1209"); System.out.println(b1.setScale(2, BigDecimal.ROUND_DOWN));//1.12 System.out.println(b1.setScale(3, BigDecimal.ROUND_DOWN));//1.120
BigDecimal.ROUND_CEILING
:接近正无穷大的舍入模式
- 如果 BigDecimal 为正,则舍入行为与 ROUND_UP 相同
- 如果为负,则舍入行为与 ROUND_DOWN 相同
BigDecimal b1 = new BigDecimal("1.1209"); System.out.println(b1.setScale(2, BigDecimal.ROUND_CEILING));//1.13 System.out.println(b1.setScale(3, BigDecimal.ROUND_CEILING));//1.121 BigDecimal b2 = b1.negate();//b1的相反数 System.out.println(b2.setScale(2, BigDecimal.ROUND_CEILING));//-1.12 System.out.println(b2.setScale(3, BigDecimal.ROUND_CEILING));//-1.120
BigDecimal.ROUND_FLOOR
:接近负无穷大的舍入模式
- 如果 BigDecimal 为正,则舍入行为与 ROUND_DOWN 相同
- 如果为负,则舍入行为与 ROUND_UP 相同
BigDecimal b1 = new BigDecimal("1.1209"); System.out.println(b1.setScale(2, BigDecimal.ROUND_FLOOR));//1.12 System.out.println(b1.setScale(3, BigDecimal.ROUND_FLOOR));//1.120 BigDecimal b2 = b1.negate(); System.out.println(b2.setScale(2, BigDecimal.ROUND_FLOOR));//-1.13 System.out.println(b2.setScale(3, BigDecimal.ROUND_FLOOR));//-1.121
BigDecimal.ROUND_UNNECESSARY
:断言请求的操作具有精确的结果,因此不需要舍入
如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException
BigDecimal b1 = new BigDecimal("1.25"); System.out.println(b1.setScale(2, BigDecimal.ROUND_UNNECESSARY));//1.25 System.out.println(b1.setScale(1, BigDecimal.ROUND_UNNECESSARY));//throw ArithmeticException
六、BigDecimal 格式化
1、NumberFormat
NumberFormat 是所有数值格式的抽象基类
NumberFormat 可用于格式化和解析任何语言环境的数值:使代码能够完全独立于小数点、千位分隔符甚至所用特定小数位数的语言环境约定,并与数值格式是否为偶小数无关
(1) 数值格式化
getInstance()、getNumberInstance()
:返回当前默认语言环境的通用数值格式getInstance(Locale)、getNumberInstance(Locale)
:返回指定语言环境的通用数值格式NumberFormat.setMinimumIntegerDigits(int)
:设置数的整数部分所允许的最小位数NumberFormat.setMaximumIntegerDigits(int)
:设置数的整数部分所允许的最大位数NumberFormat.setMinimumFractionDigits(int)
:设置最少小数点位数,不足以0补位,超出按实际位数输出NumberFormat.setMaximumFractionDigits(int)
:设置最多保留小数位数,不足不补0
double doubleNum = 12345.676688000;
NumberFormat numberFormat = NumberFormat.getNumberInstance();
System.out.println(numberFormat.format(doubleNum));//12,345.677 默认只保留到小数点后三位
numberFormat.setMinimumIntegerDigits(2);
System.out.println(numberFormat.format(doubleNum));//12,345.677 整数部分大于2位按默认最大小数位数3位输出
doubleNum = 1234.0;
numberFormat.setMaximumIntegerDigits(3);
System.out.println(numberFormat.format(doubleNum));//234
numberFormat = NumberFormat.getInstance();
doubleNum = 12345.6766;
numberFormat.setMinimumFractionDigits(1);
System.out.println(numberFormat.format(doubleNum));//12,345.677 小数部分大于1位,按默认最大小数位数3位输出
numberFormat.setMinimumFractionDigits(5);
System.out.println(numberFormat.format(doubleNum));//12,345.67660 不够位数补0
numberFormat.setMaximumFractionDigits(1);
System.out.println(numberFormat.format(doubleNum));//12,345.7
numberFormat = NumberFormat.getNumberInstance(Locale.US);
doubleNum = 12345.6789;
System.out.println(numberFormat.format(doubleNum));//12,345.679
numberFormat = NumberFormat.getNumberInstance(Locale.FRANCE);
System.out.println(numberFormat.format(doubleNum));//12 345,679
(2) 货币格式化
getCurrencyInstance()
:建立一个 NumberFormat 类的对象并返回引用,该引用指定货币格式为系统预设的货币格式getCurrencyInstance(Locale)
:建立一个 NumberFormat 类的对象并返回引用,该引用的货币格式由 Locale 指定
//按系统预设的货币格式输出,这里是人民币
NumberFormat numberFormat = NumberFormat.getCurrencyInstance();
System.out.println(numberFormat.format(123.456));//¥123.46
//按指定的货币格式输出,这里是美元
numberFormat = NumberFormat.getCurrencyInstance(Locale.US);
System.out.println(numberFormat.format(123.456));//$123.46
(3) 百分比格式化
getPercentInstance()
:创建一个 NumberFormat 类的对象并返回其引用,百分比格式为系统预设格式getPercentInstance(Locale)
:创建一个 NumberFormat 类的对象并返回引用,百分比格式由 Locale 指定
//按系统预设百分比格式输出
double doubleNum = 123.456;
NumberFormat numberFormat = NumberFormat.getPercentInstance();
System.out.println(numberFormat.format(doubleNum));//12,346%
//按指定百分比格式输出,这里是法国格式
numberFormat = NumberFormat.getPercentInstance(Locale.FRANCE);
System.out.println(numberFormat.format(doubleNum));//12 346 %
(4) 工具类
import java.text.DecimalFormat;
import java.text.NumberFormat;
public class NumberDealUtil {
/**
* 移除数字前面和后面冗余的0,只保留3位小数
*
* @param isFormat 是否需要千位分隔符(,)这种格式输出
* @param num
* @return
*/
public static String trim0(boolean isFormat, double num) {
NumberFormat nf = NumberFormat.getInstance();
if (!isFormat) {
//设置输出格式是否使用“,”分组,默认是使用的
nf.setGroupingUsed(false);
}
String result = nf.format(num);
// return isFormat ? result : result.replace(",", ""); //用上面代码替换去除分隔符操作
return result;
}
/**
* 移除数字前面和后面冗余的0
*
* @param isFormat 是否需要千位分隔符(,)这种格式输出
* @param num
* @param fractionDigit 要保留的小数位数
* @return
*/
public static String trim0(boolean isFormat, double num, int fractionDigit) {
NumberFormat nf = NumberFormat.getInstance();
nf.setMaximumFractionDigits(fractionDigit);
//setMaximumFractionDigits不会保留小数点和后面多余的0,不需下面正则去除
// if (result.contains(".") && result.endsWith("0")) {
// result = result.replaceAll("0+?$", "");//去掉多余的0
// result = result.replaceAll("[.]$", "");//如最后一位是.则去掉
// }
if (!isFormat) {
//设置输出格式是否使用“,”分组,默认是使用的
nf.setGroupingUsed(false);
}
String result = nf.format(num);
// return isFormat ? result : result.replace(",", "");
return result;
}
/**
* 指定位数输出,不足补0
* 整数部分如果位数大于需要的位数按实际位数输出
* 小数部分如果大于需要的位数四舍五入
*
* @param num
* @param integerDigit 整数部分位数
* @param fractionDigit 小数部分位数
* @return
*/
public static String add0Format(double num, int integerDigit, int fractionDigit) {
StringBuilder rule = new StringBuilder();
if (integerDigit <= 0) {
rule.append("#");
} else {
for (int i = 0; i < integerDigit; i++) {
rule.append("0");
}
}
if (fractionDigit > 0) {
rule.append(".");
for (int i = 0; i < fractionDigit; i++) {
rule.append("0");
}
}
DecimalFormat df = new DecimalFormat(rule.toString());
return df.format(num);
}
/**
* 保留几位小数,不足不补0,小数部分冗余的0也不显示
*
* @param num
* @param fractionDigit 要保留小数的位数
* @return
*/
public static String fractionDigitFormat(double num, int fractionDigit) {
/*方法一*/
// StringBuilder rule = new StringBuilder("#");
// if (fractionDigit > 0) {
// rule.append(".");
// for (int i = 0; i < fractionDigit; i++) {
// rule.append("#");
// }
// }
// DecimalFormat df = new DecimalFormat(rule.toString());
// return df.format(num);
/*方法二*/
NumberFormat nf = NumberFormat.getInstance();
nf.setMaximumFractionDigits(fractionDigit);
//设置输出格式是否使用“,”分组,这里不使用
nf.setGroupingUsed(false);
return nf.format(num);
}
}
2、DecimalFormat
(1) 符号含义
符号 | 位置 | 本地化 | 含义 |
---|---|---|---|
0 | 数字 | 是 | 阿拉伯数字 |
# | 数字 | 是 | 阿拉伯数字若不存在就显示为空 |
. | 数字 | 是 | 小数分隔符或货币小数分隔符 |
- | 数字 | 是 | 减号 |
, | 数字 | 是 | 分组分隔符 |
E | 数字 | 是 | 分割科学计数法中的尾数和指数(在前缀和后缀中无需添加引号) |
; | 子模式边界 | 是 | 分隔正数和负数子模式 |
% | 前缀或后缀 | 是 | 乘以 100 并显示为百分数 |
/u2030 | 前缀或后缀 | 是 | 乘以 1000 并显示为千分数 |
¤ (\u00A4) | 前缀或后缀 | 否 | 货币记号,由货币符号替换: 1. 如果两个同时出现,则用国际货币符号替换 2. 如果出现在某个模式中,则使用货币小数分隔符,而不使用小数分隔符 |
' | 前缀或后缀 | 否 | 用于在前缀或后缀中为特殊字符加引号,例如 “’#’#” 将 123 格式化为 “#123" ps:要创建单引号本身,请连续使用两个单引号:”# o’'clock" |
(2) 0和#配合使用
double pi = 3.1415927;
//取一位整数
System.out.println(new DecimalFormat("0").format(pi)); //3
//取一位整数和两位小数
System.out.println(new DecimalFormat("0.00").format(pi)); //3.14
//取两位整数和三位小数,整数不足部分以0填补
System.out.println(new DecimalFormat("00.000").format(pi)); //03.142
//取所有整数部分
System.out.println(new DecimalFormat("#").format(pi)); //3
//以百分比方式计数,并取两位小数
System.out.println(new DecimalFormat("#.##%").format(pi)); //314.16%
pi = 12.34567;
//取一位整数
System.out.println(new DecimalFormat("0").format(pi)); //12
//取一位整数和两位小数
System.out.println(new DecimalFormat("0.00").format(pi)); //12.35
//取两位整数和三位小数,整数不足部分以0填补。
System.out.println(new DecimalFormat("00.000").format(pi)); //12.346
//取所有整数部分
System.out.println(new DecimalFormat("#").format(pi)); //12
//以百分比方式计数,并取两位小数
System.out.println(new DecimalFormat("#.##%").format(pi)); //1234.57%
pi = 12.34;
//整数
System.out.println(new DecimalFormat("6").format(pi)); //612
System.out.println(new DecimalFormat("60").format(pi)); //612
System.out.println(new DecimalFormat("06").format(pi)); //126
System.out.println(new DecimalFormat("00600").format(pi)); //00126
System.out.println(new DecimalFormat("#####60000").format(pi)); //00126
//小数
System.out.println(new DecimalFormat(".6").format(pi)); //12.6
System.out.println(new DecimalFormat(".06").format(pi)); //12.36
System.out.println(new DecimalFormat(".60").format(pi)); //12.36
System.out.println(new DecimalFormat(".0600").format(pi)); //12.3406
System.out.println(new DecimalFormat(".6000").format(pi)); //12.3406
System.out.println(new DecimalFormat(".600000##").format(pi)); //12.340006
注意:
整数
:0 和 # 配合使用,只能是
##00
,即 # 在前,0 在后
- 若是 n 个 0,就从个位开始向高位填充,如果有值就是原来的值,没有就填充0
- 若都是 #,没有实际意义,不管是几个 #,最后的结果都是原来的整数
小数
:0 和 # 配合使用,只能是
.00##
,即 0 在前,# 在后
- 若 n 个 0,就是保留 n 位小数,小数不足的部分用 0 填充
- 若 n 个 #,就是保留 n 位小数,小数不足部分没有就是没有
数字 1-9
:
整数:
若没有 0 或 #,默认在后面拼接整数
若有 0 或 #,找到第一个 0 或 # 的位置,然后找出所有的0或#拼在一起
在第一个 0 或 # 出现的位置插入响应的格式化以后的值
小数:
若没有 0 或 #,格式化是什么就显示什么
若有 0 或 #,找出所有的 0 或 # 拼在一起
在小数点的后面插入响应的格式化以后的值
(3) 科学计数法 E
double pi = 123456789.3456;
System.out.println(new DecimalFormat("0E0").format(pi)); //1E8
System.out.println(new DecimalFormat("0E00").format(pi)); //1E08
System.out.println(new DecimalFormat("#E0").format(pi)); //.1E9
System.out.println(new DecimalFormat("##E0").format(pi)); //1.2E8
System.out.println(new DecimalFormat("###E0").format(pi)); //123E6
System.out.println(new DecimalFormat("####E0").format(pi)); //1.235E8
System.out.println(new DecimalFormat("#####E0").format(pi)); //1234.6E5
System.out.println(new DecimalFormat("######E0").format(pi)); //123.457E6
System.out.println(new DecimalFormat("#######E0").format(pi)); //12.34568E7
System.out.println(new DecimalFormat("########E0").format(pi)); //1.2345679E8
System.out.println(new DecimalFormat("#########E0").format(pi)); //123456789E0
System.out.println(new DecimalFormat("##########E0").format(pi)); //123456789.3E0
pi = 12345678.3456;
System.out.println(new DecimalFormat("0E0").format(pi)); //1E7
System.out.println(new DecimalFormat("0E00").format(pi)); //1E07
System.out.println(new DecimalFormat("#E0").format(pi)); //.1E8
System.out.println(new DecimalFormat("##E0").format(pi)); //12E6
System.out.println(new DecimalFormat("###E0").format(pi)); //12.3E6
System.out.println(new DecimalFormat("####E0").format(pi)); //1235E4
System.out.println(new DecimalFormat("#####E0").format(pi)); //123.46E5
System.out.println(new DecimalFormat("######E0").format(pi)); //12.3457E6
System.out.println(new DecimalFormat("#######E0").format(pi)); //12.34568E7
System.out.println(new DecimalFormat("########E0").format(pi)); //12345678E0
System.out.println(new DecimalFormat("#########E0").format(pi)); //12345678.3E0
System.out.println(new DecimalFormat("##########E0").format(pi)); //12345678.35E0
//0 的个数决定最后输出结果的位数,与 0 的位置无关
pi = 12345;
System.out.println(new DecimalFormat("###.##E0").format(pi)); //12.345E3
System.out.println(new DecimalFormat("##0.##E0").format(pi)); //12.345E3
System.out.println(new DecimalFormat("##0.0##E0").format(pi)); //12.345E3
System.out.println(new DecimalFormat("##0.00000##E0").format(pi)); //12.3450E3
System.out.println(new DecimalFormat("#00.0000##E0").format(pi)); //12.3450E3
System.out.println(new DecimalFormat("#00.00000##E0").format(pi)); //12.34500E3
总结:
使用科学计数法,首先保证 E 前面有 0 或 #,否则就不是科学计数法
E 后面必须是 0,0 的个数对后面的显示有影响,多余就会填充 0
E 前面只有一个 #,得到的结果肯定是
.
开头的结果E 前面 # 与 0 的总个数决定后面的指数
具体:总个数和指数比较:
- 若指数的值大于总个数,则得到的指数的值是个数的倍数
- 若指数的值小于等于总个数,则得到的指数的值等于总个数
整个模式中的 0 的总个数决定最后输出结果的位数,并且与 0 的位置无关
如果整数部分需要保留几位数,就使用几个 0
(4) 分组分隔符
double pi = 1299792458;
//每三位以逗号进行分隔
System.out.println(new DecimalFormat(",###").format(pi)); //1,299,792,458
System.out.println(new DecimalFormat(",##").format(pi)); //12,99,79,24,58
System.out.println(new DecimalFormat("###,##").format(pi)); //12,99,79,24,58
(5) 减号
double pi = 3.14;
System.out.println(new DecimalFormat("-0.00").format(pi)); //-3.14
(6) % 将数字乘以100
double pi = 0.1234;
System.out.println(new DecimalFormat("0.00%").format(pi)); //12.34%
System.out.println(new DecimalFormat("0%.00").format(pi)); //12.34%
System.out.println(new DecimalFormat("%0.00").format(pi)); //%12.34
(7) \u2030 将数字乘以1000
double pi = 0.1234;
System.out.println(new DecimalFormat("0.00\u2030").format(pi)); //123.40‰
System.out.println(new DecimalFormat("0.0\u20300").format(pi)); //123.40‰
System.out.println(new DecimalFormat("\u20300.00").format(pi)); //‰123.40
(8) ¤(\u00A4) 本地化货币符号
double pi = 1234.5678;
System.out.println(new DecimalFormat(",000.00¤").format(pi)); //1,234.57¥
System.out.println(new DecimalFormat(",000.¤00").format(pi)); //1,234.57¥
System.out.println(new DecimalFormat("¤,000.00").format(pi)); //¥1,234.57
System.out.println(new DecimalFormat(",00¤0.¤00").format(pi)); //1,234.57¥¥
System.out.println(new DecimalFormat("¤,000.¤00").format(pi)); //¥1,234.57¥
System.out.println(new DecimalFormat(",000.00¤¤").format(pi)); //1,234.57CNY
1234567
(9) '
用于引用特殊的字符,作为前缀或后缀
double pi = 4.5678;
System.out.println(new DecimalFormat("'#'0.00").format(pi)); //#4.57
System.out.println(new DecimalFormat("'^ _ ^'0.00").format(pi)); //^ _ ^4.57
//使用'本身作为前缀或后缀
System.out.println(new DecimalFormat("''0.00").format(pi)); //'4.57
七、工具类
package org.example;
import java.math.BigDecimal;
/**
* 用于高精确处理常用的数学运算
*/
public class ArithmeticUtils {
//默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static double add(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2).doubleValue();
}
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @return 两个参数的和
*/
public static BigDecimal add(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2);
}
/**
* 提供精确的加法运算
*
* @param v1 被加数
* @param v2 加数
* @param scale 保留scale 位小数
* @return 两个参数的和
*/
public static String add(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.add(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的减法运算
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static double sub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2).doubleValue();
}
/**
* 提供精确的减法运算。
*
* @param v1 被减数
* @param v2 减数
* @return 两个参数的差
*/
public static BigDecimal sub(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2);
}
/**
* 提供精确的减法运算
*
* @param v1 被减数
* @param v2 减数
* @param scale 保留scale 位小数
* @return 两个参数的差
*/
public static String sub(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.subtract(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static double mul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2).doubleValue();
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @return 两个参数的积
*/
public static BigDecimal mul(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2);
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @param scale 保留scale 位小数
* @return 两个参数的积
*/
public static double mul(double v1, double v2, int scale) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return round(b1.multiply(b2).doubleValue(), scale);
}
/**
* 提供精确的乘法运算
*
* @param v1 被乘数
* @param v2 乘数
* @param scale 保留scale 位小数
* @return 两个参数的积
*/
public static String mul(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.multiply(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @return 两个参数的商
*/
public static double div(double v1, double v2) {
return div(v1, v2, DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* @return 两个参数的商
*/
public static double div(double v1, double v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入
*
* @param v1 被除数
* @param v2 除数
* @param scale 表示需要精确到小数点以后几位
* @return 两个参数的商
*/
public static String div(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v1);
return b1.divide(b2, scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 提供精确的小数位四舍五入处理
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static double round(double v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(Double.toString(v));
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).doubleValue();
}
/**
* 提供精确的小数位四舍五入处理
*
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* @return 四舍五入后的结果
*/
public static String round(String v, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b = new BigDecimal(v);
return b.setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取余数
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留几位
* @return 余数
*/
public static String remainder(String v1, String v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
return b1.remainder(b2).setScale(scale, BigDecimal.ROUND_HALF_UP).toString();
}
/**
* 取余数 BigDecimal
*
* @param v1 被除数
* @param v2 除数
* @param scale 小数点后保留几位
* @return 余数
*/
public static BigDecimal remainder(BigDecimal v1, BigDecimal v2, int scale) {
if (scale < 0) {
throw new IllegalArgumentException("The scale must be a positive integer or zero");
}
return v1.remainder(v2).setScale(scale, BigDecimal.ROUND_HALF_UP);
}
/**
* 比较大小
*
* @param v1 被比较数
* @param v2 比较数
* @return 如果v1 大于v2 则 返回true 否则false
*/
public static boolean compare(String v1, String v2) {
BigDecimal b1 = new BigDecimal(v1);
BigDecimal b2 = new BigDecimal(v2);
int bj = b1.compareTo(b2);
boolean res;
if (bj > 0)
res = true;
else
res = false;
return res;
}
}
相关文章:

并发编程下的集合:数组寻址、LinkedList、HashMap、ConcurrentHashMap
如果发现hash取模后的数组索引位下无元素则直接新增,若不是空那就说明存在hash冲突,则判断数组索引位链表结构中的第一个元素的key以及hash值是否与新的key一致则直接覆盖,若不一致则判断当前的数组索引下的链表结构是否为红黑树,若为红黑树则走红黑树的新增方法,若不为红黑树则遍历当前链表结构,遍历中发现某个节点元素的next为null是则直接将新元素指针与next进行关联,若在遍历到next为空前判断到,某个节点的key以及key的hash值与新的key与新的keyhash值一致时则走覆盖。

【日常开发之插件篇】IDEA plugins 神器助我!!
今早因为老代码的一些bug让我突然觉得Idea的一些插件特别好用,我准备将我平时所用到的一些插件做个推荐以及记录。

【日常开发之FTP】Windows开启FTP、Java实现FTP文件上传下载
FTP是一个专门进行文件管理的操作服务,一般来讲可以在任意的操作系统之中进行配置,但是如果考虑到简便性,一般来讲可以直接在Linux系统下进行安装。FTP (File Transfer Protocol、文件传输协议)是TCP/IP协议中的一部分,属于应用层协议。使用FTP最主要的功能是对文件进行管理,所以在FTP内部对于文件支持有两种传输模式:文本模式(ASCII、默认)和二进制模式(Binary),通常文本文件使用ASCIl模式,而对于图片、视频、声音、压缩等文件则会使用二进制的方式进行传输。

【Linux之升华篇】Linux内核锁、用户模式与内核模式、用户进程通讯方式
alloc_pages(gfp_mask, order),_ _get_free_pages(gfp_mask, order)等。字符设备描述符 struct cdev,cdev_alloc()用于动态的分配 cdev 描述符,cdev_add()用于注。外,还支持语义符合 Posix.1 标准的信号函数 sigaction(实际上,该函数是基于 BSD 的,BSD。从最初的原子操作,到后来的信号量,从。(2)命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的。

【Mongdb之数据同步篇】什么是Oplog、Mongodb 开启oplog,java监听oplog并写入关系型数据库、Mongodb动态切换数据源
oplog是local库下的一个固定集合,Secondary就是通过查看Primary 的oplog这个集合来进行复制的。每个节点都有oplog,记录这从主节点复制过来的信息,这样每个成员都可以作为同步源给其他节点。Oplog 可以说是Mongodb Replication的纽带了。

【日常开发之Windows共享文件】Java实现Windows共享文件上传下载
下拉框选择你选择的用户点击添加,然后共享确定。创建一个文件夹然后点击属性界面,点击共享。maven版本存在于SMB协议的兼容问题。首先开启服务,打开控制面板点击程序。点击启用或关闭Windows功能。我这边是专门创建了一个用户。SMB1.0选中红框内的。

CXFServlet类的作用
CXFServlet是Apache CXF框架中的一个核心组件,用于处理HTTP请求并将它们转换为Web服务调用。通过配置CXFServlet,你可以轻松地部署和管理SOAP和RESTful Web服务。

@Scheduled注解的scheduler属性什么作用
注解是 Spring Framework 提供的一种机制,用于定义计划任务,即周期性执行的任务。 注解可以应用于方法上,以指示 Spring 容器在特定的时间间隔或按照某种调度规则来调用该方法。 属性是 注解的一个可选属性,它的作用是允许开发者指定一个自定义的 对象来控制任务的调度方式。默认情况下, 注解使用 Spring 内部的 来执行任务,但如果需要更高级的定制化需求,可以通过 属性指定一个自定义的 实现。自定义调度器:共享调度器资源:高级调度需求:假设你想使用 作为调度器,并且希望所有带有

过滤器、拦截器、aop的先后顺序和作用范围&拦截器preHandle(),postHandle(),afterComplation()方法执行顺序
在Spring框架中,过滤器(Filter)、拦截器(Interceptor)和面向切面编程(AOP)都是用于处理请求和处理流程的组件,但它们的作用范围和触发时机有所不同。下面我会解释这三者的先后顺序和作用范围。执行顺序:请注意,这个顺序可能因具体的配置和使用的技术而有所不同。在实际应用中,建议根据项目的具体需求来合理配置和使用这些组件。拦截器执行流程图:实现拦截器需要实现这个接口,这个 接口中有三个默认方法,这三个方法的执行顺序:我们实现接口然后重写这三个方法,就会在对应的时机被自动执行。这里就是调用处理

Zookeeper概要、协议、应用场景
Zoopkeeper提供了一套很好的分布式集群管理的机制,就是它这种基于层次型的目录树的数据结构并对树中的节点进行有效管理,从而可以设计出多种多样的分布式的数据管理模型,作为分布式系统的沟通调度桥梁。

spring.factories文件的作用
即spring.factories文件是帮助spring-boot项目包以外的bean(即在pom文件中添加依赖中的bean)注册到spring-boot项目的spring容器中。在Spring Boot启动时,它会扫描classpath下所有的spring.factories文件,加载其中的自动配置类,并将它们注入到Spring ApplicationContext中,使得项目能够自动运行。spring.factories文件是Spring Boot自动配置的核心文件之一,它的作用是。

Spring事务七大传播机制与五个隔离级别,嵌套事务
如果当前方法正有一个事务在运行中,则该方法应该运行在一个嵌套事务中,被嵌套的事务可以独立于被封装的事务中进行提交或者回滚。如果封装事务存在,并且外层事务抛出异常回滚,那么内层事务必须回滚,反之,内层事务并不影响外层事务。当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。当前方法必须运行在它自己的事务中。一个新的事务将启动,而且如果有一个现有的事务在运行的话,则这个方法将在运行期被挂起,直到新的事务提交或者回滚才恢复执行。

常见的七种加密算法及实现
**数字签名**、**信息加密** 是前后端开发都经常需要使用到的技术,应用场景包括了用户登入、交易、信息通讯、`oauth` 等等,不同的应用场景也会需要使用到不同的签名加密算法,或者需要搭配不一样的 **签名加密算法** 来达到业务目标。这里简单的给大家介绍几种常见的签名加密算法和一些典型场景下的应用。## 正文### 1. 数字签名**数字签名**,简单来说就是通过提供 **可鉴别** 的 **数字信息** 验证 **自身身份** 的一种方式。一套 **数字签名** 通常定义两种 **互补

7min到40s:SpringBoot 启动优化实践
然后重点排查这些阶段的代码。先看下。

SpringBoot系列教程之Bean之指定初始化顺序的若干姿势
之前介绍了@Order注解的常见错误理解,它并不能指定 bean 的加载顺序,那么问题来了,如果我需要指定 bean 的加载顺序,那应该怎么办呢?本文将介绍几种可行的方式来控制 bean 之间的加载顺序。

在Java中使用WebSocket
WebSocket是一种协议,用于在Web应用程序和服务器之间建立实时、双向的通信连接。它通过一个单一的TCP连接提供了持久化连接,这使得Web应用程序可以更加实时地传递数据。WebSocket协议最初由W3C开发,并于2011年成为标准。

3种方案,模拟两个线程抢票
在多线程编程中,资源竞争是一个常见的问题。资源竞争发生在多个线程试图同时访问或修改共享资源时,可能导致数据不一致或其他并发问题。在模拟两个线程抢票的场景中,我们需要考虑如何公平地分配票,并确保每个线程都有机会成功获取票。本篇文章将通过三种方式来模拟两个线程抢票的过程,以展示不同的并发控制策略。使用 Synchronized 来确保一次只有一个线程可以访问票资源。使用 ReentrantLock 来实现线程间的协调。使用 Semaphore 来限制同时访问票的线程数量。

替代Druid,HakariCP 为什么这么快?
这次源码探究,真的感觉看到了无数个小细节,无数个小优化,积少成多。平时开发过程中,一些小的细节也一定要“扣”。

Java中volatile 的使用场景有哪些?
volatile是一种轻量级的同步机制,它能保证共享变量的可见性,同时禁止重排序保证了操作的有序性,但是它无法保证原子性。所以使用volatilevolatile。

JDK22 正式发布了 !
Java 22 除了推出了新的增强功能和特性,也获得 Java Management Service (JMS) 的支持,这是一项新的 Oracle 云基础设施远程软件服务(Oracle Cloud Infrastructure, OCI) 原生服务,提供统一的控制台和仪表盘,帮助企业管理本地或云端的 Java 运行时和应用。使包含运行时计算值的字符串更容易表达,简化 Java 程序的开发工作,同时提高将用户提供的值编写成字符串,并将字符串传递给其他系统的程序的安全性。支持开发人员自由地表达构造器的行为。

Jackson 用起来!
你可以创建自定义序列化器和反序列化器以自定义特定字段或类的序列化和反序列化行为。为此,请创建一个实现或接口的类,并在需要自定义的字段或类上使用和注解。@Override// ...其他代码...优势性能优异:Jackson在序列化和反序列化过程中表现出优秀的性能,通常比其他Java JSON库更快。灵活性:通过注解、自定义序列化器/反序列化器等功能,Jackson提供了丰富的配置选项,允许你根据需求灵活地处理JSON数据。易于使用:Jackson的API设计简洁明了,易于学习和使用。

拜托!别再滥用 ! = null 判空了!!
另外,也许受此习惯影响,他们总潜意识地认为,所有的返回都是不可信任的,为了保护自己程序,就加了大量的判空。如果你养成习惯,都是这样写代码(返回空collections而不返回null),你调用自己写的方法时,就能大胆地忽略判空)这种情况下,null是个”看上去“合理的值,例如,我查询数据库,某个查询条件下,就是没有对应值,此时null算是表达了“空”的概念。最终,项目中会存在大量判空代码,多么丑陋繁冗!,而不要返回null,这样调用侧就能大胆地处理这个返回,例如调用侧拿到返回后,可以直接。

详解Java Math类的toDegrees()方法:将参数从弧度转换为角度
Java Math 类的 toDegrees() 方法是将一个角度的弧度表示转换为其度表示,返回值为double类型,表示从弧度数转换而来的角度数。这就是Java Math 类的 toDegrees() 方法的攻略。我们已经了解了该方法的基本概念、语法、注意事项以及两个示例。希望这篇攻略对你有所帮助。

SpringBoot接口防抖(防重复提交)的一些实现方案
作为一名老码农,在开发后端Java业务系统,包括各种管理后台和小程序等。在这些项目中,我设计过单/多租户体系系统,对接过许多开放平台,也搞过消息中心这类较为复杂的应用,但幸运的是,我至今还没有遇到过线上系统由于代码崩溃导致资损的情况。这其中的原因有三点:一是业务系统本身并不复杂;二是我一直遵循某大厂代码规约,在开发过程中尽可能按规约编写代码;三是经过多年的开发经验积累,我成为了一名熟练工,掌握了一些实用的技巧。啥是防抖所谓防抖,一是防用户手抖,二是防网络抖动。

公司新来一个同事:为什么 HashMap 不能一边遍历一边删除?一下子把我问懵了!
前段时间,同事在代码中KW扫描的时候出现这样一条:上面出现这样的原因是在使用foreach对HashMap进行遍历时,同时进行put赋值操作会有问题,异常ConcurrentModificationException。于是帮同简单的看了一下,印象中集合类在进行遍历时同时进行删除或者添加操作时需要谨慎,一般使用迭代器进行操作。于是告诉同事,应该使用迭代器Iterator来对集合元素进行操作。同事问我为什么?这一下子把我问蒙了?对啊,只是记得这样用不可以,但是好像自己从来没有细究过为什么?

每天一个摆脱if-else工程师的技巧——优雅的参数校验
在日常的开发工作中,为了程序的健壮性,大部分方法都需要进行入参数据校验。最直接的当然是在相应方法内对数据进行手动校验,但是这样代码里就会有很多冗余繁琐的if-else。throw new IllegalArgumentException("用户姓名不能为空");throw new IllegalArgumentException("性别不能为空");throw new IllegalArgumentException("性别错误");

SpringBoot请求转发与重定向
但是可能由于B网址相对于A网址过于复杂,这样搜索引擎就会觉得网址A对用户更加友好,因而在重定向之后任然显示旧的网址A,但是显示网址B的内容。在平常使用手机的过程当中,有时候会发现网页上会有浮动的窗口,或者访问的页面不是正常的页面,这就可能是运营商通过某种方式篡改了用户正常访问的页面。重定向,是指在Nginx中,重定向是指通过修改URL地址,将客户端的请求重定向到另一个URL地址的过程,Nginx中实现重定向的方式有多种,比如使用rewrite模块、return指令等。使用场景:在返回视图的前面加上。

SSO 单点登录和 OAuth2.0 有何区别?
此方法的缺点是它依赖于浏览器和会话状态,对于分布式或者微服务系统而言,可能需要在服务端做会话共享,但是服务端会话共享效率比较低,这不是一个好的方案。在单点登录的上下文中,OAuth 可以用作一个中介,用户在一个“授权服务器”上登录,并获得一个访问令牌,该令牌可以用于访问其他“资源服务器”上的资源。首先,SSO 主要关注用户在多个应用程序和服务之间的无缝切换和保持登录状态的问题。这种方法通过将登录认证和业务系统分离,使用独立的登录中心,实现了在登录中心登录后,所有相关的业务系统都能免登录访问资源。

TCP协议-TCP连接管理
TCP协议是 TCP/IP 协议族中一个非常重要的协议。它是一种面向连接、提供可靠服务、面向字节流的传输层通信协议。TCP(Transmission Control Protocol,传输控制协议)。

接口响应慢?那是你没用 CompletableFuture 来优化!
大多数程序员在平时工作中,都是增删改查。这里我跟大家讲解如何利用CompletableFuture优化项目代码,使项目性能更佳!