我们都知道,计算机内部是通过二进制来进行计算,他无法识别二进制数据以外的任何数据
首先列举五个简单的Demo
一:
int a = 3;
int b = 4;
a / b;
//0
二:
double (a / b);
//0.0
三:
int a = 3;
float b = (float) 4.0;
double v = a / b;//0.75
四:
double a = (1.2 - 0.4) / 0.1;//得到的是7.9999999
int a = (int) ((1.2 - 0.4) / 0.1);//得到的是7
double a = 0.8 / 0.1;//得到的是8.0,这里只是似乎不会出问题,注意只是似乎不会出现问题;
五:
float a = (float) (1.0 / 3.0);//0.33333334
double b = (double) a;//0.3333333432674408
很多朋友可能看到Demo4和Demo5的时候就会产生疑惑,那么为什么会造成这种问题呢?
1 | 其实计算机在做计算的时候,是把程序都转换成二进制来计算。 |
首先我们需要对基础知识进行一波回顾
进制转换问题
二进制的转换要分两种
1 十进制转换成二进制
1.1 十进制转二进制(整数型)
例如 十进制的10 转换成二进制就是1010(不断除2,取余,逆序排列)
1.2 十进制转二进制(小数型)
这里就需要把十进制的整数部分和小数部分进行拆分
例如 10.1转换成二进制,得到的结果就是
10依然是转换成1010
而小数部分的计算,则是不断乘2,取整(只需要整数部分),顺序排列
很明显,这里如果拿值直接进行换算的话,就会造成精度丢失。
2 二进制转十进制
2.1 二进制转换十进制(整数)
例如 二进制的1010转换成十进制就是10(根据位数*2的次数相加)
2.2二进制转换十进制(小数)
例如 二进制的1101.01转换成十进制
整数部分的计算不变
小数部分,不断除以2
3 二进制的运算方法
3.1 加法
0+0=0
0+1=1+0=1
1+1=0 (遇2进位)
1+1+1=1 (遇2进位,再加1为1)
3.2 减法
0-0=0
1-1=0
1-0=1
0-1=1 (二进制借位为2,再减1,得1)
3.3 乘法(参考十进制的乘法)
0×0=0
0×1=1×0=0
1×1=1
3.4 除法(参考十进制的除法)
知道了这里的原理的话,那么我们上面的demo就很好解释
题目讲解
Demo1
1 | int a=3;==>转换二进制就是011 |
Demo2
1 | 因为a/b还是为int 7,转换成double 就是7.0 |
Demo3
1 | int a=3;==>转换二进制就是011 |
Demo4
1 | double a = (1.2 - 0.4) / 0.1;//得到的是7.9999999 |
所以 float和double只是用于科学计算。
float是精确到小数点8位
如果尾数是0的话,则忽略不计。
double多少个字节。内存里面保存了多少位的字节,最后一位一定是一个0,但是显示的话,只是16位。
Demo5
1 | int a=1; |
所以 在当计算时,如果是float类型,那么就会得到六位数小数,而double类型,则会得到16位小数
最后总结:1
2
3
4浮点类型的数,只适合做科学计算。
有的时候,可能我们会踩狗屎运,没有遇到这些问题,但是要注意的,那只是碰巧而已。
如果是在实际项目中遇到一些业务需求,例如,金额的四舍五入,加减乘除,分期付款,优惠券等
那么我们就需要用到BigDecimal类(专门用于商业计算)来进行计算
注意事项:
1 | 举例:Math.round(4.015*100)/100.0; |
当然在使用的时候,注意封装。没必要每次都去new。
BigDecimal简易封装代码
import java.math.BigDecimal;
/**
* 由于Java的简单类型不能够精确的对浮点数进行运算,这个工 具类提供精
* 确的浮点数运算,包括加减乘除和四舍五入。
*/
public class Arith{
//默认除法运算精度
private static final int DEF_DIV_SCALE = 10;
//这个类不能实例化
private Arith(){
}
/**
* 提供精确的加法运算。
* @param v1 被加数
* @param v2 加数
* 两个参数的和
*/
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 减数
* 两个参数的差
*/
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 乘数
* 两个参数的积
*/
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();
}
/**
* 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到
* 小数点以后10位,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* 两个参数的商
*/
public static double div(double v1,double v2){
return div(v1,v2,DEF_DIV_SCALE);
}
/**
* 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指
* 定精度,以后的数字四舍五入。
* @param v1 被除数
* @param v2 除数
* @param scale 表示表示需要精确到小数点以后几位。
* 两个参数的商
*/
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();
}
/**
* 提供精确的小数位四舍五入处理。
* @param v 需要四舍五入的数字
* @param scale 小数点后保留几位
* 四舍五入后的结果
*/
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));
BigDecimal one = new BigDecimal("1");
return b.divide(one,scale,BigDecimal.ROUND_HALF_UP).doubleValue();
}
};
举一个我们项目遇到的真实问题
一件商品的价格是10.5449万元RMB
业务上如果要保留两位小数,四舍五入,显示就是10.54万元。
就会造成一个问题,用户在线下进行实际购买时,会多出49元,那用户肯定是不乐意的。
如果UI上显示"约10.54",那也不太好,用户心理不平衡。
所以这里的最佳业务就是 小数点第三位大于0,则进一位。
利用BigDecimal也可以完成。