环境
jdk:1.7
前言
之前,我曾写过一篇 java使用BigDecimal 处理商业精度及高精度详解,如何去获取有效位数;当时的我,没有弄清楚有效位和小数位数其实是互斥的!
导致在之后的工作业务中出现了问题;
今天特意记录下;
问题的主要因为是我没有弄清保留有效位和保留小数位是互斥的关系
在此之前,我想写出一个通用的方法,就是既能保留有效位,又能保留小数位;
/**
* 相乘 保留是有效数字,而非有效的小数点
* @param v1
* @param v2
* @param scale
* @author yutao
* @return
* @date 2016年11月14日下午2:52:33
*/
public static BigDecimal validMuli(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(String.valueOf(v1));
BigDecimal b2 = new BigDecimal(String.valueOf(v2));
MathContext mc = new MathContext(scale);
return b1.multiply(b2, mc);
}
/**
* 除法 (保留的是有效数字,而非有效的小数点)
* @param v1
* @param v2
* @param scale
* @return
* @author yutao
* @date 2016年11月14日下午2:01:31
*/
public static BigDecimal validDivi(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(String.valueOf(v1));
BigDecimal b2 = new BigDecimal(String.valueOf(v2));
MathContext mc = new MathContext(scale, RoundingMode.HALF_UP);
return b1.divide(b2, mc);
}
之前我以为上面两个方法已经很完美了,因为其可以实现如下功能:
0.00002 -- 得到的是0.000020
//在金融领域 如果只是单纯的保留两位小数就是0.00,这可是非常严重的错误
最近我在使用时,发现了个问题:
System.out.println(validDivi(12341231.124125124D, 10000, 2).doubleValue());
结果为:
1.2E+3 ---> 1200
这显然不是我想要的;我希望的结果应该是:1234.12
才对。
保留有效位和保留小数位是互斥的
查询资料后,忽然意识到;要是满足了有效位,那么保留小数位就不会得到满足;反之也是!
比如:
0.00002
保留2位有效位:
0.000020
保留2位小数位:
0.00
1231.001
保留2位有效位
1200
保留两位小数位
1231.00
在金融领域,我们只需要特别考虑的是整数位为0
的情况;
所以我需要对上面的方法进行改进
改进后的方法
/**
* 相除时,如果商的整数部分是0,则保留precision有效位
* 否则,就保留precision位小数位。<br>
* 例如:<table>
* <tr>
* <td>被除数</td>
* <td>除数</td>
* <td>结果</td>
* </tr>
* <tr><td>12341231.124125124D</td><td>10000</td><td>1234.12(保留小数)</td></tr>
* <tr><td>0.124125124D</td><td>10000</td><td>0.000012(有效位)</td></tr>
* </table>
* @param v1
* @param v2
* @param precision
* @return
* @author yutao
* @date 2018年1月19日下午11:23:30
*/
public static BigDecimal validDivi(double v1, double v2, int precision){
BigDecimal big = new BigDecimal(String.valueOf(v1));
BigDecimal b2 = new BigDecimal(String.valueOf(v2));
Pattern p = Pattern.compile("-?(\\d+)(\\.*)(\\d*)");
BigDecimal divide = big.divide(b2, BigDecimal.ROUND_HALF_UP);
Matcher m = p.matcher(divide.toString());
// System.out.println(divide);
if(m.matches()){
Long ll = Long.valueOf(m.group(1));
//正数位为0,保留指定的有效位
if(ll == 0){
MathContext mc = new MathContext(precision, RoundingMode.HALF_UP);
//保留指定的有效位
return big.divide(b2, mc);
// return divide.divide(BigDecimal.ONE, mc);
}
}
//保留指定小数位
return big.divide(b2, precision, BigDecimal.ROUND_HALF_UP);
// return divide.setScale(precision, BigDecimal.ROUND_HALF_UP);
}
说明:
两数相除,商的整数部分为0时,我们就保留有效位,否则就是保留小数位;
这里关键是使用正则表达式去匹配商Pattern p = Pattern.compile("-?(\\d+)(\\.*)(\\d*)");
这样我们就能获取到整数部分啦!
网上有说法,直接使用(int)强转不就行了,但是Double
表示的范围比int
表示的范围要大;所以强转不是最佳的选择;正则匹配才是;
正则表达式-?(\\d+)(\\.*)(\\d*)
,这里使用了分组功能。每个括号为一组;
在匹配到的情况下,可以使用group(1)
来获取第一个分组—-也就是整数部分的数字;
再判断是否为0
,是 就保留有效位,否 就保留小数位;
总结
都是数学问题,数学很重要!
=========2018年01月23日====修改==========
上面改进后的方法中:
BigDecimal divide = big.divide(b2, BigDecimal.ROUND_HALF_UP);
这段代码有问题:
比如:
Double big= 12312312.000;
Double b2 = 10000D
//执行上面代码后就变成了1231,正确应该是1231.23
//即:divide=1231,此时再去执行后面的保留小数或者有效位代码时,就错了
再次改进后代码
//保留小数位10位
BigDecimal divide = big.divide(b2, 10, BigDecimal.ROUND_HALF_UP);
//就是多保留些,然后再在后面的代码中去精准保留位数
完整:
/**
* 相除时保留2位
* 相除时,如果商的整数部分是0,则保留precision有效位
* 否则,就保留precision位小数位。<br>
* 例如:<table>
* <tr>
* <td>被除数</td>
* <td>除数</td>
* <td>结果</td>
* </tr>
* <tr><td>12341231.124125124D</td><td>10000</td><td>1234.12(保留小数)</td></tr>
* <tr><td>0.124125124D</td><td>10000</td><td>0.000012(有效位)</td></tr>
* </table>
* @param v1
* @param v2
* @param precision
* @return
* @author yutao
* @date 2018年1月19日下午11:23:30
*/
public static BigDecimal diviValidOrFraction(double v1, double v2, int precision){
if (precision < 0) {
throw new IllegalArgumentException("The precision must be a positive integer or zero");
}
BigDecimal b1 = new BigDecimal(String.valueOf(v1));
BigDecimal b2 = new BigDecimal(String.valueOf(v2));
//如果这里不写保留的小数位(eg:10)其默认就会保留一位小数
//这样之后的判断就会出错
BigDecimal divide = b1.divide(b2, 10, RoundingMode.HALF_UP);
return validOrFraction(divide, precision);
}
/**
* 正数部分为0,保留有效位,否则保留小数位
* @param precision
* @param b1
* @param b2
* @return
* @author yutao
* @date 2018年1月19日下午1:28:29
*/
private static BigDecimal validOrFraction(BigDecimal divide, int precision) {
Pattern p = Pattern.compile("-?(\\d+)(\\.*)(\\d*)");
Matcher m = p.matcher(divide.toString());
if(m.matches()){
Long ll = Long.valueOf(m.group(1));
//正数位为0,保留指定的有效位
if(ll == 0){
MathContext mc = new MathContext(precision, RoundingMode.HALF_UP);
//保留指定的有效位
return divide.divide(BigDecimal.ONE, mc);
}
}
//保留指定小数位
return divide.setScale(precision, BigDecimal.ROUND_HALF_UP);
}
这个方法的主要功能就是 整数为0就保留有效位,整数部分不为0就保留小数位