聊聊Java Double相加出現(xiàn)的怪事
Java Double相加出現(xiàn)的怪事
問題的提出
編譯運(yùn)行下面這個(gè)程序會(huì)看到什么
public class test { public static void main(String args[]) { System.out.println(0.05 + 0.01); System.out.println(1.0 - 0.42); System.out.println(4.015 * 100); System.out.println(123.3 / 100); } };
你沒有看錯(cuò)!結(jié)果確實(shí)是
0.060000000000000005
0.5800000000000001
401.49999999999994
1.2329999999999999
Java中的簡單浮點(diǎn)數(shù)類型float和double不能夠進(jìn)行運(yùn)算。不光是Java,在其它很多編程語言中也有這樣的問題。在大多數(shù)情況下,計(jì)算的結(jié)果是準(zhǔn)確的,但是多試幾次(可以做一個(gè)循環(huán))就可以試出類似上面的錯(cuò)誤?,F(xiàn)在終于理解為什么要有BCD碼了。
這個(gè)問題相當(dāng)嚴(yán)重,如果你有9.999999999999元,你的計(jì)算機(jī)是不會(huì)認(rèn)為你可以購買10元的商品的。
在有的編程語言中提供了專門的貨幣類型來處理這種情況,但是Java沒有。現(xiàn)在讓我們看看如何解決這個(gè)問題。
解決方案
現(xiàn)在我們已經(jīng)可以解決這個(gè)問題了,原則是使用BigDecimal并且一定要用String來夠造。
但是想像一下吧,如果我們要做一個(gè)加法運(yùn)算,需要先將兩個(gè)浮點(diǎn)數(shù)轉(zhuǎn)為String,然后夠造成BigDecimal,在其中一個(gè)上調(diào)用add方法,傳入另一個(gè)作為參數(shù),然后把運(yùn)算的結(jié)果(BigDecimal)再轉(zhuǎn)換為浮點(diǎn)數(shù)。你能夠忍受這么煩瑣的過程嗎?下面我們提供一個(gè)工具類Arith來簡化操作。它提供以下靜態(tài)方法,包括加減乘除和四舍五入:
public static double add(double v1, double v2); public static double sub(double v1, double v2); public static double mul(double v1, double v2); public static double div(double v1, double v2); public static double div(double v1, double v2, int scale); public static double round(double v, int scale); package org.nutz.mvc.core; import java.math.BigDecimal; public class Arith { // 源文件Arith.java: /** * 由于Java的簡單類型不能夠精確的對浮點(diǎn)數(shù)進(jìn)行運(yùn)算,這個(gè)工具類提供精 確的浮點(diǎn)數(shù)運(yùn)算,包括加減乘除和四舍五入。 */ // 默認(rèn)除法運(yùn)算精度 private static final int DEF_DIV_SCALE = 10; // 這個(gè)類不能實(shí)例化 private Arith() { } /** * 提供精確的加法運(yùn)算。 * * @param v1 * 被加數(shù) * @param v2 * 加數(shù) * @return 兩個(gè)參數(shù)的和 */ 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(); } /** * 提供精確的減法運(yùn)算。 * * @param v1 * 被減數(shù) * @param v2 * 減數(shù) * @return 兩個(gè)參數(shù)的差 */ 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(); } /** * 提供精確的乘法運(yùn)算。 * * @param v1 * 被乘數(shù) * @param v2 * 乘數(shù) * @return 兩個(gè)參數(shù)的積 */ 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(); } /** * 提供(相對)精確的除法運(yùn)算,當(dāng)發(fā)生除不盡的情況時(shí),精確到 小數(shù)點(diǎn)以后10位,以后的數(shù)字四舍五入。 * * @param v1 * 被除數(shù) * @param v2 * 除數(shù) * @return 兩個(gè)參數(shù)的商 */ public static double div(double v1, double v2) { return div(v1, v2, DEF_DIV_SCALE); } /** * 提供(相對)精確的除法運(yùn)算。當(dāng)發(fā)生除不盡的情況時(shí),由scale參數(shù)指 定精度,以后的數(shù)字四舍五入。 * * @param v1 * 被除數(shù) * @param v2 * 除數(shù) * @param scale * 表示表示需要精確到小數(shù)點(diǎn)以后幾位。 * @return 兩個(gè)參數(shù)的商 */ 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(); } /** * 提供精確的小數(shù)位四舍五入處理。 * * @param v * 需要四舍五入的數(shù)字 * @param scale * 小數(shù)點(diǎn)后保留幾位 * @return 四舍五入后的結(jié)果 */ 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(); } };
Double相加時(shí)出現(xiàn)的多位問題
String test = "40.61 , 18588.73, 29925.07, 7986.06, 18639.19, 25914.32, 32907.74, 34165.89, 9724.7, 52777.92"; String[] arr = test.split(","); double sum = 0; for(int i=0;i<arr.length;i++){ sum += Double.parseDouble(arr[i]); } System.out.println(sum);
結(jié)果: 230670.22999999998
查了查 沒有深入的了解,大概是java 的double機(jī)制,關(guān)于精度的問題,若是不出現(xiàn)這種情況,保留小數(shù)點(diǎn)后兩位就可以了?;蛘呤褂肂igDecimal進(jìn)行計(jì)算,另外《effective java》這本書里也提過,double和float 不建議使用商業(yè)計(jì)算。
補(bǔ)充:原因在于我們的計(jì)算機(jī)是二進(jìn)制的。浮點(diǎn)數(shù)沒有辦法是用二進(jìn)制進(jìn)行精確表示。
我們的CPU表示浮點(diǎn)數(shù)由兩個(gè)部分組成:指數(shù)和尾數(shù),這樣的表示方法一般都會(huì)失去一定的精確度,有些浮點(diǎn)數(shù)運(yùn)算也會(huì)產(chǎn)生一定的誤差。
如:2.4的二進(jìn)制表示并非就是精確的2.4。
反而最為接近的二進(jìn)制表示是 2.3999999999999999。浮點(diǎn)數(shù)的值實(shí)際上是由一個(gè)特定的數(shù)學(xué)公式計(jì)算得到的。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
spring boot整合spring-kafka實(shí)現(xiàn)發(fā)送接收消息實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于spring-boot整合spring-kafka實(shí)現(xiàn)發(fā)送接收消息的相關(guān)資料,文中介紹的非常詳細(xì),對大家具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編一起來看看吧。2017-06-06SpringCloud將Nacos作為配置中心實(shí)現(xiàn)流程詳解
這篇文章主要介紹了Springcloud中的Nacos Config服務(wù)配置,本文以用戶微服務(wù)為例,進(jìn)行統(tǒng)一的配置,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10Mybatis-Plus實(shí)現(xiàn)只更新部分字段的數(shù)據(jù)
這篇文章主要介紹了Mybatis-Plus實(shí)現(xiàn)只更新部分字段的數(shù)據(jù),具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06淺談HTTP使用BASIC認(rèn)證的原理及實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄獪\談HTTP使用BASIC認(rèn)證的原理及實(shí)現(xiàn)方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-11-11學(xué)習(xí)Java之如何對時(shí)間進(jìn)行格式化
當(dāng)我們在默認(rèn)情況下構(gòu)造出來的時(shí)間對象,它的時(shí)間格式并不適合我們閱讀,并且在開發(fā)時(shí),pc端、Android端、iOS端等展示的時(shí)間格式可能也并不完全一樣,本文就從這幾個(gè)問題給大家介紹如何對時(shí)間進(jìn)行格式化,感興趣的同學(xué)可以借鑒一下2023-05-05Java抽象類、繼承及多態(tài)和適配器的實(shí)現(xiàn)代碼
這篇文章主要介紹了Java抽象類、繼承及多態(tài)和適配器的實(shí)現(xiàn),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-06-06