一.BigInteger(大整数操作类)
为什么要使用BigInteger类
在Java的整数类型
里面,byte为8位,short为16位,int为32位,long为64位。正因为这些数值的二进制位数已经固定
,所以它们能表示的数值大小就有一定的范围限制
。因此,Java中提供BigInteger类来处理更大的数字。
java.math.BigInteger
就是用来表示任意大小的整数。BigInteger内部用一个int[]数组来模拟一个非常大的整数
:
构造方法
方法 | 描述 |
---|---|
BigInteger(int bitLength, int certainty, Random rnd) | 构造一个随机生成的正 BigInteger,它可能是一个具有指定 bitLength 的素数。 |
BigInteger(int numBits, Random rnd) | 构造一个随机生成的 BigInteger,它是在 0 到 (2numBits - 1)(包括)范围内均匀分布的值。 |
BigInteger(byte[] val) | 将包含 BigInteger 的二进制补码表示形式的 byte 数组转换为 BigInteger |
BigInteger(int signum, byte[] magnitude) | 将 BigInteger 的符号-数量表示形式转换为 BigInteger |
BigInteger(String val) |
将BigInteger 的十进制字符串表示形式转换为 BigInteger |
BigInteger(String val, int radix) | 将指定基数的 BigInteger 的字符串表示形式转换为 BigInteger |
常用的构造就是BigInteger(String val)
- 将十进制字符串转为BigInteger类型,因为数字类型都可以通过String.valueOf(val)容易转换为String 类型,
进制转换:BigInteger(String val,int radix);
- 这个方法可以结合toString();(总是返回十进制类型的字符串)很方便对进制进行转换,不过就是要多几步把数字转成String,然后在从String转回来,或者结合toString(int radix)指定返回的字符串的进制位数
常用方法
方法 | 描述 |
---|---|
add(BigInteger val) | 加法:返回一个BigInteger值与另一个BigInteger值相加后的BIgInteger值, |
subtract(BigInteger val) | 减法:返回其值为 (this - val) 的 BigInteger |
multiply(BigInteger val) | 乘法: 返回其值为 (this * val) 的 BigInteger |
divide(BigInteger val) | 除法: 求除数,不带小数值,返回其值为 (this / val) 的 BigInteger 可能会产生除零异常 |
remainder(BigInteger val) | 求余数,返回其值为 (this % val) 的BigInteger |
and(BigInteger val) | 返回其值为 (this & val) 的 BigInteger |
abs() | 返回BigInteger绝对值表示形式 |
compareTo(BigInteger val) | BigIntege与val比较大小,当此 BigInteger 在数值上小于、等于或大于 val 时,返回 -1,0,或1。 |
pow(BigInteger val) | 此 BigInteger 的指数,返回 this ^ val的BigInteger |
shiftLeft(BigInteger val) | 左移 :返回其值为 (this << n) 的 BigInteger。 |
shiftRight(BigInteger val) | 右移: 返回其值为 (this >> n) 的 BigInteger。 |
toString() | 返回此 BigInteger 的十进制字符串表示形式 |
toString(int radix) | 返回此 BigInteger 的给定基数的字符串表示形式。 |
intValue() | 将此 BigInteger 转换为int |
longValue() | 将此 BigInteger 转换为 long |
BigInteger方法较多,但用的较多的也就是这些方法
使用示例:
public static void main(String[] args) {
BigInteger a = new BigInteger("10");
BigInteger b = new BigInteger("3");
System.out.println("add >>> " + a.add(b));
System.out.println("subtract >>> " + a.subtract(b));
System.out.println("multiply >>> " + a.multiply(b));
System.out.println("divide >>> " + a.divide(b));
System.out.println("divideAndRemainder[0] >>> " + a.divideAndRemainder(b)[0]);
System.out.println("divideAndRemainder[1] >>> " + a.divideAndRemainder(b)[1]);
}
add >>> 13
subtract >>> 7
multiply >>> 30
divide >>> 3
divideAndRemainder[0] >>> 3
divideAndRemainder[1] >>> 1
注意事项
-
对BigInteger做运算的时候,只能使用实例方法,例如,加法运算:
public static void main(String[] args) { BigInteger i1 = new BigInteger("1234567890"); BigInteger i2 = new BigInteger("12345678901234567890"); BigInteger sum = i1.add(i2); // 12345678902469135780 }
和
long型
整数运算比,BigInteger不会有范围限制,但缺点是速度比较慢
。 -
可以把BigInteger转换成long型,如果超出了long型的范围,会抛出ArithmeticException
public static void main(String[] args) { BigInteger i = new BigInteger("123456789000"); System.out.println(i.longValue()); // 123456789000 System.out.println(i.multiply(i).longValueExact()); // java.lang.ArithmeticException: BigInteger out of long range }
-
BigInteger和Integer、Long一样,也是不可变类,并且也继承自
Number类
。因为Number定义了转换为基本类型的几个方法:- 转换为byte:byteValue()
- 转换为short:shortValue()
- 转换为int:intValue()
- 转换为long:longValue()
- 转换为float:floatValue()
- 转换为double:doubleValue()
如果BigInteger的值甚至超过了float的最大范围
(3.4 * 1038),那么不会报错,h会返回Infinity:
表示无限大的意思
public static void main(String[] args) {
BigInteger n = new BigInteger("999999").pow(99);
float f = n.floatValue();
System.out.println(f);// Infinity:无限大的意思
}
通过上述方法,可以把BigInteger转换成基本类型。如果BigInteger表示的范围超过了基本类型的范围
,转换时将丢失高位信息
,即结果不一定是准确的。如果需要准确地转换成基本类型,可以使用intValueExact()、longValueExact()
等方法,在转换时如果超出范围,将直接抛出ArithmeticException异常
。
二.BigDecimal(大小数操作类)
BigDecimal可以表示一个任意大小且精度完全准确
的浮点数
, 常用于财务计算;
为什么要使用BigDecimal类
-
虽然Java的基本类型提供了float和double类型,但他们在执行浮点运算的时候,只是提供了一个较为精确的结果,不能用于要求精确度很高的环境中。因此,Java提供了BigDecimal类来保证结果的精确度。
-
BigDecimal都是不可变的(immutable)的,在进行每一步运算时,都会产生一个新的对象,所以在做运算时千万要保存操作后的值。
-
使用BigDecimal的坏处是性能比double和float差,在处理庞大,复杂的运算时尤为明显,因根据实际需求决定使用哪种类型
使用float和double导致精度缺失
public static void main(String[] args) {
System.out.println(0.2 + 0.1);
System.out.println(0.3 - 0.1);
System.out.println(0.2 * 0.1);
System.out.println(0.3 / 0.1);
}
0.30000000000000004
0.19999999999999998
0.020000000000000004
2.9999999999999996
使用BigDecimal类来解决使用浮点类型导致精度缺失的问题
构造方法
方法 | 描述 |
---|---|
BigDecimal(BigInteger val) |
将 BigInteger 转换为 BigDecimal |
BigDecimal(BigInteger unscaledVal, int scale) | 将 BigInteger 非标度值和 int 标度转换为 BigDecimal |
BigDecimal(BigInteger unscaledVal, int scale, MathContext mc) | 将 BigInteger 非标度值和 int 标度转换为 BigDecimal(根据上下文设置进行舍入) |
BigDecimal(BigInteger val, MathContext mc) | 将 BigInteger 转换为 BigDecimal(根据上下文设置进行舍入) |
BigDecimal(char[] in) | 将 BigDecimal 的字符数组表示形式转换为 BigDecimal,接受与 BigDecimal(String) 构造方法相同的字符序列。 |
BigDecimal(char[] in, int offset, int len) | 将 BigDecimal 的字符数组表示形式转换为 BigDecimal,接受与 BigDecimal(String) 构造方法相同的字符序列,同时允许指定子数组 |
BigDecimal(char[] in, int offset, int len, MathContext mc) | 将 BigDecimal 的字符数组表示形式转换为 BigDecimal,接受与 BigDecimal(String) 构造方法相同的字符序列,同时允许指定子数组,并根据上下文设置进行舍入 |
BigDecimal(char[] in, MathContext mc) | 将 BigDecimal 的字符数组表示形式转换为 BigDecimal,接受与 BigDecimal(String) 构造方法相同的字符序列(根据上下文设置进行舍入) |
BigDecimal(double val) |
将 double 转换为 BigDecimal,后者是 double 的二进制浮点值准确的十进制表示形式 |
BigDecimal(double val, MathContext mc) | 将 double 转换为 BigDecimal(根据上下文设置进行舍入) |
BigDecimal(int val) | 将 int 转换为 BigDecimal |
BigDecimal(int val, MathContext mc) | 将 int 转换为 BigDecimal(根据上下文设置进行舍入) |
BigDecimal(long val) | 将 long 转换为 BigDecimal |
BigDecimal(long val, MathContext mc) | 将 long 转换为 BigDecimal(根据上下文设置进行舍入) |
BigDecimal(String val) |
将 BigDecimal 的字符串表示形式转换为 BigDecimal |
BigDecimal(String val, MathContext mc) | 将 BigDecimal 的字符串表示形式转换为 BigDecimal,接受与 BigDecimal(String) 构造方法相同的字符串(按照上下文设置进行舍入) |
常用构造方法:
- BigDecimal(double val);将double类型转换为BigDecimal类型
- BigDecimal(BigInteger val);将BigInteger类型转换为BigDecimal类型
- BigDecimal(String val);将十进制String类型的浮点数转为BigDecimal类型
不使用传入double/float的构造方法
- 因为float和double会导致精度缺失的问题,所以不建议使用将double/float作为参数的构造方法
如下代码:
public static void main(String[] args) {
BigDecimal a = new BigDecimal(1.01);
BigDecimal b = new BigDecimal(1.02);
BigDecimal c = new BigDecimal("1.01");
BigDecimal d = new BigDecimal("1.02");
System.out.println(a.add(b));//a + b
System.out.println(c.add(d));// c + d
}
2.0300000000000000266453525910037569701671600341796875
2.03
一般使用BigDecimal来解决商业运算上丢失精度的问题的时候, 声明BigDecimal对象的时候一定要使用参数为String的类型的构造方法
常用方法
方法 | 描述 |
---|---|
add(BigDecimal augend) | 加法运算 |
subtract(BigDecimal subtrahend) | 减法运算 |
multiply(BigDecimal multiplicand) | 乘法运算 |
divide(BigDecimal divisor) | 除法运算,可能会产生除零异常和不能整除异常。 |
divide(BigDecimal divisor, int scale, int roundingMode) | 除法运算,可传入精确小数位数scale,和舍入模式roundingMode |
divideAndRemainder(BigDecimal divisor) | 获取商值和余数组成的数组,初始元素是商值,最终元素是余数 |
setScale(int newScale, int roundingMode) | 进行舍入操作 |
toString() | 将BigDecimal对象的数值转换成字符串 |
doubleValue() | 将BigDecimal对象中的值以双精度数返回 |
floatValue() | 将BigDecimal对象中的值以单精度数返回 |
intValue() | 将BigDecimal对象中的值以整数返回 |
加(add(val)),减(subtract(val)),乘法(multiply(val)),除法(divide(val)),求余(remainder(val)),比较(compareTo(val)),等等一大堆数值操作的方法和BigInteger都是差不多用法
8种舍入模式
方法 | 描述 |
---|---|
ROUND_UP | 向远离零的方向舍入。舍弃非零部分,并将非零舍弃部分相邻的一位数字加一 |
ROUND_DOWN | 向接近零的方向舍入。舍弃非零部分,同时不会非零舍弃部分相邻的一位数字加一,采取截取行为 |
ROUND_CEILING | 向正无穷的方向舍入。如果为正数,舍入结果同ROUND_UP一致;如果为负数,舍入结果同ROUND_DOWN一致 |
ROUND_FLOOR | |
ROUND_HALF_UP | 向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。这种模式也就是我们常说的我们的“四舍五入” |
ROUND_HALF_DOWN | 向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则为向下舍入的舍入模式。这种模式也就是我们常说的我们的“五舍六入” |
ROUND_HALF_EVEN | 向“最接近”的数字舍入,如果与两个相邻数字的距离相等,则相邻的偶数舍入。如果舍弃部分左边的数字奇数,则舍入行为与ROUND_HALF_UP相同;如果为偶数,则舍入行为与ROUND_HALF_DOWN相同。四舍六入,五分两种情况,如果前一位为奇数,则入位,否则舍去 |
ROUND_UNNECESSARY | 断言请求的操作具有精确的结果,因此不需要舍入。如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException |
例如: public BigDecimal divide(BigDecimal divisor,int scale, int roundingMode)
第一个参数是除数,第二个参数代表保留几位小数,第三个代表的是使用的模式。
BigDecimal.ROUND_DOWN:直接省略多余的小数,比如1.28如果保留1位小数,得到的就是1.2
BigDecimal.ROUND_UP:直接进位,比如1.21如果保留1位小数,得到的就是1.3
BigDecimal.ROUND_HALF_UP:四舍五入,2.35保留1位,变成2.4
BigDecimal.ROUND_HALF_DOWN:四舍五入,2.35保留1位,变成2.3
使用示例:
@Test
public void testBigDecimal3 () {
BigDecimal a = new BigDecimal(10);
BigDecimal b = new BigDecimal(3);
System.out.println("add >>> " + a.add(b));
System.out.println("subtract >>> " + a.subtract(b));
System.out.println("multiply >>> " + a.multiply(b));
// System.out.println("divide >>> " + a.divide(b));// Non-terminating decimal expansion; no exact representable decimal result.
System.out.println("divide >>> " + a.divide(b, 2, BigDecimal.ROUND_HALF_UP));
System.out.println("divideAndRemainder[0] >>> " + a.divideAndRemainder(b)[0]);
System.out.println("divideAndRemainder[1] >>> " + a.divideAndRemainder(b)[1]);
}
add >>> 13
subtract >>> 7
multiply >>> 30
divide >>> 3.33
5 divideAndRemainder[0] >>> 3
divideAndRemainder[1] >>> 1
注意事项
- BigDecimal用
scale()
表示小数位数
public static void main(String[] args) {
BigDecimal d1 = new BigDecimal("123.45");
BigDecimal d2 = new BigDecimal("123.4500");
BigDecimal d3 = new BigDecimal("1234500");
System.out.println(d1.scale()); // 2,两位小数
System.out.println(d2.scale()); // 4
System.out.println(d3.scale()); // 0
}
- 通过BigDecimal的
stripTrailingZeros()
方法,可以将一个BigDecimal格式化为一个相等的,但去掉了末尾0的BigDecimal
public static void main(String[] args) {
BigDecimal d1 = new BigDecimal("123.4500");
BigDecimal d2 = d1.stripTrailingZeros();
System.out.println(d1.scale()); // 4
System.out.println(d2.scale()); // 2,因为去掉了00
BigDecimal d3 = new BigDecimal("1234500");
BigDecimal d4 = d3.stripTrailingZeros();
System.out.println(d3.scale()); // 0
System.out.println(d4.scale()); // -2
}
- 如果一个BigDecimal的scale()返回负数,例如,-2,表示这个数是个整数,并且末尾有2个0。可以对一个BigDecimal设置它的
scale
,如果精度比原始值低,那么按照指定的方法进行四舍五入或者直接截断:
public static void main(String[] args) {
BigDecimal d1 = new BigDecimal("123.456789");
BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四舍五入,123.4568
BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 直接截断,123.4567
System.out.println(d2);
System.out.println(d3);
}
- 对BigDecimal做加、减、乘时,精度不会丢失,但是做除法时,
存在无法除尽
的情况,这时,就必须指定精度以及如何进行截断
:
public static void main(String[] args) {
BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("23.456789");
BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); // 保留10位小数并四舍五入
BigDecimal d4 = d1.divide(d2); // 报错:ArithmeticException,因为除不尽
}
还可以对BigDecimal做除法的同时求余数
:
public static void main(String[] args) {
BigDecimal n = new BigDecimal("12.345");
BigDecimal m = new BigDecimal("0.12");
BigDecimal[] dr = n.divideAndRemainder(m);
System.out.println(dr[0]); // 102
System.out.println(dr[1]); // 0.105
}
- 调用
divideAndRemainder()
方法时,返回的数组包含两个BigDecimal,分别是商和余数
,其中商总是整数,余数不会大于除
数。我们可以利用这个方法判断两个BigDecimal是否是整数倍数
:
public static void main(String[] args) {
BigDecimal n = new BigDecimal("12.75");
BigDecimal m = new BigDecimal("0.15");
BigDecimal[] dr = n.divideAndRemainder(m);
if (dr[1].signum() == 0) {
// n是m的整数倍
}
}
- 比较BigDecimal
在比较两个BigDecimal的值是否相等时,要特别注意,使用equals()方法不但要求两个BigDecimal的值相等,还要求它们的scale()相等
:
public static void main(String[] args) {
BigDecimal d1 = new BigDecimal("123.456");
BigDecimal d2 = new BigDecimal("123.45600");
System.out.println(d1.equals(d2)); // false,因为scale不同
System.out.println(d1.equals(d2.stripTrailingZeros())); // true,因为d2去除尾部0后scale变为2
System.out.println(d1.compareTo(d2)); // 0
}
比较两个BigDecimal的值是否相等必须使用compareTo(方法来比较)
,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于
- 如果查看BigDecimal的源码,可以发现,实际上一个BigDecimal是通过
一个BigInteger
和一个scale
来表示的,即BigInteger表示一个完整的整数,而scale表示小数位数:
public class BigDecimal extends Number implements Comparable<BigDecimal> {
private final BigInteger intVal;
private final int scale;
}
BigDecimal也是从Number继承的,也是不可变对象
。
Java面试问题:你会用什么数据类型来存储金额?
如果这个时候你回答float,double那么恭喜你,又可以省出时间来准备别的公司的面试了,当面试官说float,和double不行的时候你可能还一脸懵逼,为啥不行?那么请看下面的例子
public static void main(String[] args) {
double a = 0.03;
double b = 0.02;
double c = a - b;
System.out.println(c);// 0.009999999999999998
BigDecimal a2 = new BigDecimal("0.03");
BigDecimal b2 = new BigDecimal("0.02");
BigDecimal c3 = a2.subtract(b2);
System.out.println(c3.doubleValue());//0.1
}
结果会打印多少,大部分人肯定都在想结果肯定是0.01啊!如果觉得结果是0.01的同学可以动手在编辑器上实现并运行一下你会惊奇的发现结果居然是0.009999999999999998
,因为float与double
都是浮点数,浮点数参与的运算
通常伴随着因为无法精确表示
而进行的近似或舍入
,所以导致结果会有丝毫的偏差
,而涉及金额的计算是绝对不予许存在偏差的。
java-float和double类型的表示范围和精度
- 使用BigDecimal来表示金额
- 通过将金额的单位变小用整数来表示金额
mysql数据库设计
- BigDecimal在进行入库时, 数据库选择
decimal类型
, 长度可以自定义, 如18,小数点我们项目中用的是2, 保留2位小数. 此外还要注意的就是默认值, 一定写成0.00,
不要用默认的NULL
, 否则在进行加减排序等操作时, 会带来转换的麻烦!
balance
decimal(18,2) DEFAULT ‘0.00’ COMMENT ‘账户余额’,
要定义数据类型为DECIMAL的列,请使用以下语法:
- column_name DECIMAL(P,D);
- P是表示有效数字数的精度。 P范围为1〜65。
- D是表示小数点后的位数。 D的范围是0~30。MySQL要求D小于或等于(<=)P。
如: amount DECIMAL(6,2);
- amount列最多可以存储6位数字,小数位数为2位; 因此,amount列的范围是从
-9999.99到9999.99
。