查看原文
其他

同事因在涉及金钱交易中使用double造成无法挽回的损失,已跑路

作者:Java实用技术

来源:blog.csdn.net/vbirdbest/article/details/80280627


在《Effective Java》这本书(48条)中也提到这个原则:float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal。所以,也才有了这个标题,不是标题党,是之前听一个同事说,他的一个朋友因为用了double来用作金钱运算,导致被黑客利用,造成不少的损失跑路了,下面咱们就来聊聊这个问题吧。


简介


Java中的简单浮点数类型float和double不能够进行运算,或者运算会丢失精度,不光是Java,在其它很多编程语言中也有这样的问题。在大多数情况下,计算的结果是准确的,float和double只能用来做科学计算或者是工程计算,在商业计算中我们要用 java.math.BigDecimal。

// 浮点型计算不准确示例@Testpublic void testDouble(){ // 0.060000000000000005 System.out.println(0.05+0.01); // 0.5800000000000001 System.out.println(1.0-0.42); // 401.49999999999994 System.out.println(4.015*100); // 1.2329999999999999 System.out.println(123.3/100); // 4.01 四舍五入保留两位 System.out.println(Math.round(4.015*100)/100.0);}

BigDecimal


BigDecimal有多种构造函数,常用的有2种,其中有一种不建议使用,就是double构造方式,建议使用String构造方式。

// 强制使用String的构造函数,double也有可能计算不太准确// 原则是使用BigDecimal并且一定要用String来够造。public BigDecimal(String val);public BigDecimal(double val);
BigDecimal也定义了几个常用的值,0、1、10,静态的,可以通过类名直接引用BigDecimal.ZEROpublicstaticfinal BigDecimal ZERO = zeroThroughTen[0]; /** * The value 1, with a scale of 0. * * @since 1.5 */ publicstaticfinal BigDecimal ONE = zeroThroughTen[1]; /** * The value 10, with a scale of 0. * * @since 1.5 */ publicstaticfinal BigDecimal TEN = zeroThroughTen[10];

工具类

由于构造方法要用String对应的构造方法,如果我们要做一个加法运算,需要先将两个浮点数转为String,然后够造成BigDecimal,在其中一个上调用add方法,传入另一个作为参数,然后把运算的结果(BigDecimal)再转换为浮点数。你能够忍受这么烦琐的过程吗?下面我们提供一个工具类来简化操作。

import java.math.BigDecimal;/** * 由于Java的简单类型不能够精确的对浮点数进行运算,这个工具类提供精 * 确的浮点数运算,包括加减乘除和四舍五入。 */publicclass ArithUtil { //默认除法运算精度 privatestaticfinalint 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 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 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 除数 * @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){ thrownew 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 小数点后保留几位 * @return 四舍五入后的结果 */ public static double round(double v,int scale){ if(scale<0){ thrownew 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(); }}

测试

@Testpublic void testBigDecimal(){ BigDecimal price = BigDecimal.ZERO; // 通常建议优先使用(String)构造函数 BigDecimal amount = new BigDecimal("6.66"); // 0.06 System.out.println(ArithUtil.add(0.05, 0.01)); // 0.58 System.out.println(ArithUtil.sub(1.0, 0.42)); // 401.5 System.out.println(ArithUtil.mul(4.015, 100)); // 1.233 System.out.println(ArithUtil.div(123.3, 100)); // 4.02 System.out.println(ArithUtil.round(4.015, 2)); // BigDecimal 比较大小使用compareTo方法 // public int compareTo(BigDecimal val);}


 往期推荐 

🔗



 

点击阅读原文,获得更多精彩内容

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存