java BigDecimal类型
我们在使用金额计算或者展示金额的时候经常会使用BigDecimal,也是涉及金额时非常推荐的一个类型,BigDecimal自身也提供了很多构造器方法,这些构造器方法使用不当可能会造成不必要的麻烦甚至是金额损失,从而引起事故资损。
public static void main(String[] args) {
BigDecimal bigDecimal=new BigDecimal(88);
System.out.println(bigDecimal);
bigDecimal=new BigDecimal("8.8");
System.out.println(bigDecimal);
bigDecimal=new BigDecimal(8.8);
System.out.println(bigDecimal);
}
执行结果如
通过测试发现,当使用double或者float这些浮点数据类型时,会丢失精度,String、int则不会。
我们点开构造器方法看下源码:
public static long doubleToLongBits(double value) {
long result = doubleToRawLongBits(value);
// Check for NaN based on values of bit fields, maximum
// exponent and nonzero significand.
if ( ((result & DoubleConsts.EXP_BIT_MASK) ==
DoubleConsts.EXP_BIT_MASK) &&
(result & DoubleConsts.SIGNIF_BIT_MASK) != 0L)
result = 0x7ff8000000000000L;
return result;
}
问题就处在 doubleToRawLongBits 这个方法上,在jdk中double类(float与int对应)中提供了double与long转换,doubleToRawLongBits就是将double转换为long,这个方法是原始方法(底层不是java实现,是c++实现的)。double之所以会出问题,是因为小数点转二进制丢失精度。BigDecimal在处理的时候把十进制小数扩大N倍让它在整数上进行计算,并保留相应的精度信息
- float和double类型,主要是为了科学计算和工程计算而设计的,之所以执行二进制浮点运算,是为了在广泛的数值范围上提供较为精确的快速近和计算。
- 并没有提供完全精确的结果,所以不应该被用于精确的结果的场合。
- 当浮点数达到一定大的数,就会自动使用科学计数法,这样的表示只是近似真实数而不等于真实数。
- 当十进制小数位转换二进制的时候也会出现无限循环或者超过浮点数尾数的长度。
总结:
所以,在涉及到精度计算的过程中,我们尽量使用String类型来进行转换,正确用法如下:
BigDecimal bigDecimal2=new BigDecimal("8.8");
BigDecimal bigDecimal3=new BigDecimal("8.812");
System.out.println( bigDecimal2.compareTo(bigDecimal3));
System.out.println( bigDecimal2.add(bigDecimal3));
BigDecimal创建出来的是对象,我们不能用传统的加减乘除对其进行运算,必须使用他的方法,在我们数据库存储里,如果我们使用的是double或者float类型,需要进行来回的转换后进行计算,非常不方便。
BigDecimalUtils工具:
import java.math.BigDecimal;
public class BigDecimalUtils {
public static BigDecimal doubleAdd(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.add(b2);
}
public static BigDecimal floatAdd(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.add(b2);
}
public static BigDecimal doubleSub(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.subtract(b2);
}
public static BigDecimal floatSub(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.subtract(b2);
}
public static BigDecimal doubleMul(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.multiply(b2);
}
public static BigDecimal floatMul(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.multiply(b2);
}
public static BigDecimal doubleDiv(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
// 保留小数点后两位 ROUND_HALF_UP = 四舍五入
return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
}
public static BigDecimal floatDiv(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
// 保留小数点后两位 ROUND_HALF_UP = 四舍五入
return b1.divide(b2, 2, BigDecimal.ROUND_HALF_UP);
}
/**
* 比较v1 v2大小
* @param v1
* @param v2
* @return v1>v2 return 1 v1=v2 return 0 v1<v2 return -1
*/
public static int doubleCompareTo(double v1, double v2) {
BigDecimal b1 = new BigDecimal(Double.toString(v1));
BigDecimal b2 = new BigDecimal(Double.toString(v2));
return b1.compareTo(b2);
}
public static int floatCompareTo(float v1, float v2) {
BigDecimal b1 = new BigDecimal(Float.toString(v1));
BigDecimal b2 = new BigDecimal(Float.toString(v2));
return b1.compareTo(b2);
}
}
hive decimal 函数
hive基于java开发,涉及浮点型数据进行计算,同样存在精度丢失的问题。
hive中数字类型使用说明:
- hive中整数默认给int类型
- 带有小数位的给double类型
- 可以通过cast(num as decimal(n,m)) 指定数字类型
- int类型是最弱的类型,它和其他两个操作后会被转换
- double是最强的类型,和其他两个类型操作时会全部转换成dluble类型
- 使用double运算时可能会导致精度丢失,如对精度要求较高,建议全都换成decimal类型之后再做操作。
- hive将字符串默认转换成double类型进行计算
select cast('0.00407' as decimal(18,6))*2500 --10.175
select cast('0.00407' as decimal(18,6))*cast(2500 as decimal(18,2)) --10.175
select cast('0.00407' as decimal(18,6))*cast(2500 as double) -- 10.174999999999999
explain select 200; --返回值类型:int
explain select 200.00; --返回值类型:double
explain select 200*200.00 --返回值类型:double
explain select 200*cast(200.00 as decimal(18,2)) --返回值类型:decimal
explain select 200.00*cast(200.00 as decimal(18,2)) --返回值类型:double
结果分析:
- 两个declimal计算不会造成精度丢失
- double和decimal计算可能造成精度缺失
hive将字符串默认转换成double类型进行计算
--'0.00407' 是double,2500 是int类,结果是double类
explain select '0.00407'*2500 -->10.1749999999
总结:
- Hive的decimal类型借鉴于Oracle,decimal(m,n)表示数字总长度为m位,小数位为n位,那么整数位就只有m-n位了。这与MySql是不一样的,MySql就直接表示整数位为m位了。
- 如果你在使用Hive的时候发现字段长度不够,Hive在处理数值字段的时候会直接置该字段值为NULL,而不会将它截去。如:select cast(123.1234 as decimal(4,2)) 。
- hive中double和decimal两个类型计算会返回double,有可能造成精度缺失。应该把两个计算值都转换成decimal类型。
- 高精度计算时,尽量多保留两位小数,并把数据转换成decimal类型后再做计算,以免精度丢失。
- 对于decimal类型来说,计算时应尽量让乘法在除法前计算,减少中间值无法精确表示的情况。