09 | 数值计算:注意精度、舍入和溢出问题
09 | 数值计算:注意精度、舍入和溢出问题
讲述:王少泽
时长14:29大小13.26M
“危险”的 Double
考虑浮点数舍入和格式化的方式
用 equals 做判等,就一定是对的吗?
小心数值溢出问题
重点回顾
思考与讨论
赞 27
提建议
精选留言(23)
- Darren2020-03-28精度问题遇到的比较少,可能与从事非金融行业有关系,试着回答下问题 第一种问题 1、 ROUND_UP 舍入远离零的舍入模式。 在丢弃非零部分之前始终增加数字(始终对非零舍弃部分前面的数字加1)。 注意,此舍入模式始终不会减少计算值的大小。 2、ROUND_DOWN 接近零的舍入模式。 在丢弃某部分之前始终不增加数字(从不对舍弃部分前面的数字加1,即截短)。 注意,此舍入模式始终不会增加计算值的大小。 3、ROUND_CEILING 接近正无穷大的舍入模式。 如果 BigDecimal 为正,则舍入行为与 ROUND_UP 相同; 如果为负,则舍入行为与 ROUND_DOWN 相同。 注意,此舍入模式始终不会减少计算值。 4、ROUND_FLOOR 接近负无穷大的舍入模式。 如果 BigDecimal 为正,则舍入行为与 ROUND_DOWN 相同; 如果为负,则舍入行为与 ROUND_UP 相同。 注意,此舍入模式始终不会增加计算值。 5、ROUND_HALF_UP 向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为向上舍入的舍入模式。 如果舍弃部分 >= 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同。 注意,这是我们大多数人在小学时就学过的舍入模式(四舍五入)。 6、ROUND_HALF_DOWN 向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则为上舍入的舍入模式。 如果舍弃部分 > 0.5,则舍入行为与 ROUND_UP 相同;否则舍入行为与 ROUND_DOWN 相同(五舍六入)。 7、ROUND_HALF_EVEN 向“最接近的”数字舍入,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。 如果舍弃部分左边的数字为奇数,则舍入行为与 ROUND_HALF_UP 相同; 如果为偶数,则舍入行为与 ROUND_HALF_DOWN 相同。 注意,在重复进行一系列计算时,此舍入模式可以将累加错误减到最小。 此舍入模式也称为“银行家舍入法”,主要在美国使用。四舍六入,五分两种情况。 如果前一位为奇数,则入位,否则舍去。 以下例子为保留小数点1位,那么这种舍入方式下的结果。 1.15>1.2 1.25>1.2 8、ROUND_UNNECESSARY 断言请求的操作具有精确的结果,因此不需要舍入。 如果对获得精确结果的操作指定此舍入模式,则抛出ArithmeticException。 第二个问题 在MySQL中,整数和浮点数的定义都是有多种类型,整数根据实际范围定义,浮点数语言指定整体长度和小数长度。浮点数类型包括单精度浮点数(float型)和双精度浮点数(double型)。定点数类型就是decimal型。定点数以字符串形式存储,因此,其精度比浮点数要高,而且浮点数会出现误差,这是浮点数一直存在的缺陷。如果要对数据的精度要求比较高,还是选择定点数decimal比较安全。展开
作者回复: 👍🏻
共 9 条评论62 - 👽2020-03-28想请教一下。关于金额。 还存在 使用Long类型的分存储,以及封装的money对象存储的方式。这两种方式适合解决金额类的精度丢失问题嘛?
作者回复: 用分存储是可以(解决精度问题),但是容易出错,万一读的时候忘记/100或者是存的时候忘记*100,可能会引起重大问题,还是使用DECIMAL(13, 2) /DECIMAL(13, 4) 存比较好。
共 3 条评论16 - Monday2020-03-29手机计算器把 10%+10% 算成了 0.11 而不是 0.2。 读到这里,吓得我赶快掏出安卓机算了下11
- Jerry Wu2020-04-01感谢老师,看完这篇文章,改了BigDecimal工具类,避免了一个事故。
作者回复: 赞
共 3 条评论10 - Geek_3b10962020-03-29用equals对两BigDecimal判等...之前就被坑了共 2 条评论8
- 每天晒白牙2020-03-28我们现在对金额的计算都是用分做单位处理共 5 条评论7
- geek_time2020-03-28double\float精度问题,会导致一些结果不是我们想要的。比如3.35 其实如果用double表示,则是3.34900000,如果用float表示,则是3.500000所以控制精度不能用他们。 浮点数的字符串格式化也要通过 BigDecimal 进行。 BigDecimal num1 = new BigDecimal("3.35"); BigDecimal num2 = num1.setScale(1, BigDecimal.ROUND_DOWN); System.out.println(num2); BigDecimal num3 = num1.setScale(1, BigDecimal.ROUND_HALF_UP); System.out.println(num3); 使用 BigDecimal 表示和计算浮点数,且务必使用字符串的构造方法来初始化 BigDecimal 如果一定要用 Double 来初始化 BigDecimal 的话,可以使用 BigDecimal.valueOf 方法,以确保其表现和字符串形式的构造方法一致。 如果我们希望只比较 BigDecimal 的 value,可以使用 compareTo 方法 BigDecimal的equals方法是比较scale的所以1.0跟1就是不同的。我们如果要比较两个BigDecimal要用compareTo. BigDecimal 的 equals 和 hashCode 方法会同时考虑 value 和 scale,如果结合 HashSet 或 HashMap 使用的话就可能会出现麻烦。比如,我们把值为 1.0 的 BigDecimal 加入 HashSet,然后判断其是否存在值为 1 的 BigDecimal,得到的结果是 false: 第一个方法是,使用 TreeSet 替换 HashSet。TreeSet 不使用 hashCode 方法,也不使用 equals 比较元素,而是使用 compareTo 方法,所以不会有问题。 Set<BigDecimal> treeSet = new TreeSet<>(); treeSet.add(new BigDecimal("1.0")); System.out.println(treeSet.contains(new BigDecimal("1")));//返回true 第二个方法是,把 BigDecimal 存入 HashSet 或 HashMap 前,先使用 stripTrailingZeros 方法去掉尾部的零,比较的时候也去掉尾部的 0,确保 value 相同的 BigDecimal,scale 也是一致的: Set<BigDecimal> hashSet2 = new HashSet<>(); hashSet2.add(new BigDecimal("1.0").stripTrailingZeros()); System.out.println(hashSet2.contains(new BigDecimal("1.000").stripTrailingZeros()));//返回true 我们平时会忽略掉溢出的问题。 方法一是,考虑使用 Math 类的 addExact、subtractExact 等 xxExact 方法进行数值运算,这些方法可以在数值溢出时主动抛出异常. \方法二是,使用大数类 BigInteger。BigDecimal 是处理浮点数的专家,而 BigInteger 则是对大数进行科学计算的专家。展开共 1 条评论4
- Tesla2020-04-24推荐money类2
- 👻2020-04-11最好的办法就是写好测试用例2
- hellojd2020-03-28还有 bigdecimal的值对比,也是问题2
- hellojd2020-03-28对账时,涉及double求和 ,遇到了2
- pedro2020-03-28第一个问题,BigDecimal 的 8 中 Round模式,分别是 1.ROUND_UP:向上取整,如 5.1 被格式化后为 6,如果是负数则与直观上不一致,如 -1.1 会变成 -2。2.ROUND_DOWN:向下取整,与 ROUND_UP 相反。 3.ROUND_CEILING:正负数分开版的取整,如果是正数,则与 ROUND_UP 一样,如果是负数则与 ROUND_DOWN 一样。 4.ROUND_FLOOR:正负数分开版的取整,与 ROUND_CEILING 相反。 5.ROUND_HALF_UP:四舍五入版取整,我们直观上最为理解的一种模式,如 5.4 小数部分小于 0.5,则舍位为 5,如果是 5.6 则进位变成 6,如果是负数,如 -5.4 => -5,-5.6 => -6。 6.ROUND_HALF_DOWN:五舍六入版取整,必须大于 0.5 才可进位,其它与 ROUND_HALF_UP 一致。 7.ROUND_HALF_EVEN:奇偶版四舍五入取整,如果舍弃部分左边的数字为奇数,则作 ROUND_HALF_UP;如果它为偶数,则作ROUND_HALF_DOWN,会根据舍弃部分的奇偶性来选择进位的是四舍五入还是五舍六入。 8. ROUND_UNNECESSARY:要求传入的数必须是精确的,如 1 和 1.0 都是精确的,如果为 1.2 或者 1.6 之类的均会报 ArithmeticException 异常。 第二个问题,MySQL 是支持 bigint 和 bigdecimal 数据类型存储的,当然还有 numberic,numberic 的作用与 bigdecimal 一致,当然如果这些数据类型在数据库中计算我觉得是不妥的,应该查询后在代码层面中计算,当然如果有人补充一下如何在数据库中科学计算,也可让大家涨涨见识😄。展开
作者回复: 👍🏻
3 - 岳宜波2020-05-25一般用的比较多的就是,向上取整,向下取整,四舍五入和舍位四种,在我们项目里因为有国际化,会有币种档案,在币种中定义金额精度和价格精度以及舍入方式,在商品的计量单位上定义数量精度以及舍入方式。
作者回复: 👍🏻
1 - 美美2020-04-08请教老师string.valueof替代bigdecimal.valueof可否呢
作者回复: 可以比较一下,主要是要小心scale: BigDecimal bigDecimal1 = new BigDecimal("100"); BigDecimal bigDecimal2 = new BigDecimal(String.valueOf(100d)); BigDecimal bigDecimal3 = BigDecimal.valueOf(100d); BigDecimal bigDecimal4 = new BigDecimal(Double.toString(100d)); System.out.println(bigDecimal1.multiply(new BigDecimal("4.015"))); System.out.println(bigDecimal2.multiply(new BigDecimal("4.015"))); System.out.println(bigDecimal3.multiply(new BigDecimal("4.015"))); System.out.println(bigDecimal4.multiply(new BigDecimal("4.015")));
1 - 吴国帅2020-03-29真棒 get到知识了!
作者回复: 觉得好可以多转发分享
2 - 许童童2020-03-28浮点数我印象很深刻的一个问题就是大数吃小数的问题1
- 梦倚栏杆2020-03-280.45和0.55也有这个问题。double a=0.45,可以输出0.45,double b =1-0.55=0.4444444491
- chachae2021-08-19equal 这个确实很坑hhh
- JoJi2021-04-01如果数据库里设置了BigDecimal小数点为2位,在存储方面BigDecimal格式化的问题(Double.toString那个例子)是不是就不影响了。1
- 鲁鸣2021-01-15对于大数BigInteger,一定要使用longValueExact方法,如果是使用longValue的话,同样存在溢出问题1