Java小記 —— 浮點數(double、float)的格式化問題及處理

NO IMAGE

    平時常會面臨浮點數的格式處理問題,下面就舉例說一說常見的問題及處理:

    1,科學計數法問題

    一個浮點數123456789.10,在列印的時候變成了1.234567891E8,處理起來很簡單,如:

double d = 123456789.10;
System.out.println(d);//1.234567891E8
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setGroupingUsed(false);
System.out.println(nf.format(d));//列印結果:123456789.10

    使用NumberFormat的時候要setGroupingUsed(false),否則結果就會變成123,456,789.1。

    再有直接轉為BigDecimal更簡便:

System.out.println(new BigDecimal(d));//列印結果:123456789.10

    

    2,指定小數位的位數

    指定浮點數1.010515的小數位的位數:

double d = 1.010515;
NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMaximumFractionDigits(10);
System.out.println(nf.format(d));//列印結果:1.010515
nf.setMinimumFractionDigits(10);
System.out.println(nf.format(d));//列印結果:1.0105150000

    使用NumberFormat格式化的時候可以設定最大和最小的小數位數,如果要求必須有多少位,就要將最大和最小位數保持一致了。

    再有,轉為BigDecimal也很簡單,會自動補0:

System.out.println(new BigDecimal(d).setScale(10,BigDecimal.ROUND_HALF_UP));//列印結果:1.0105150000

    使用BigDecimal的時候要選擇舍入模式,接下來就說說這個問題。

    

    3,四捨五入

    接著看上面的例子,對1.010515進行小數位擷取,進行四捨五入:

nf.setMaximumFractionDigits(3);
System.out.println(nf.format(d));//列印結果:1.011 四捨五入
nf.setMaximumFractionDigits(5);
System.out.println(nf.format(d));//列印結果:1.01052 四捨五入

    不像BigDecimal,使用NumberFormat指定小數位的時候,不需要指定舍入方式,我們看到結果已經舍入了,但是我們將d 1,然後再看一下:

d = 2.010515;
nf.setMaximumFractionDigits(3);
System.out.println(nf.format(d));//列印結果:2.011 四捨五入
nf.setMaximumFractionDigits(5);
System.out.println(nf.format(d));//列印結果:2.01051 沒有四捨五入

    改變d的值後再進行小數位擷取,會發現有的時候會四捨五入,有的時候不四捨五入,這時候就會想到指定舍入方式,NumberFormat 有很多舍入模式:UP、DOWN、CEILING、FLOOR、HALF_UP、HALF_DOWN、HALF_EVEN、UNNECESSARY,這些模式其實和BigDecimal的模式是一樣的,NumberFormat的舍入模式就是對BigDecimal的做了一下封裝,並沒有什麼不同,比如,RoundingMode.HALF_UP就等於BigDecimal.ROUND_HALF_UP,至於這麼多模式我就不再多解釋了,平時使用的時候根據實際的需求選擇一種模式即可,對於我們平時理解的四捨五入對應的模式就是RoundingMode.HALF_UP,我們看程式碼:

nf.setRoundingMode(RoundingMode.HALF_UP);
System.out.println(nf.format(d));//列印結果:2.01051 沒有四捨五入

    是不是覺得奇怪,為什麼沒有四捨五入,換BigDecimal試一下:

System.out.println(new BigDecimal(d).setScale(5,BigDecimal.ROUND_HALF_UP));//列印結果:2.01051 沒有四捨五入

    還是沒有四捨五入,再來試試下面的程式碼:

System.out.println(nf.format(BigDecimal.valueOf(d)));//列印結果:2.01052 成功了
System.out.println(BigDecimal.valueOf(d).setScale(5,BigDecimal.ROUND_HALF_UP));//列印結果:2.01052 成功了

    哎?怎麼又都成功了!呵呵,神不神奇,其實這就是下一個問題了(丟失精度)。

    4,精度丟失

    還是接著上面的例子說,對2.010515進行四捨五入的時候,只有最後一次的程式碼正確的對其進行了四捨五入的格式處理,關鍵在於BigDecimal.valueOf(d),將d轉為了BigDecimal,但是不是隨便轉的:

System.out.println(nf.format(BigDecimal.valueOf(d)));	//列印結果:2.01052 成功了
System.out.println(nf.format(new BigDecimal(d)));		//列印結果:2.01051 失敗
System.out.println(nf.format(new BigDecimal(String.valueOf(d))));	//列印結果:2.01052 又成功了

    通過上邊的程式碼可知,為了避免精度丟失,儘量將浮點數轉為BigDecimal,並且,要使用BigDecimal.valueOf()函式,如果要用new函式,就要現將浮點數轉為字串,否則同樣會丟失精度。

    接下來我們還是繼續上邊的例子,我們對d*100進行四捨五入的操作:

nf.setMaximumFractionDigits(3);//保留三位小數
nf.setRoundingMode(RoundingMode.HALF_UP);//四捨五入
System.out.println(nf.format(BigDecimal.valueOf(d*100)));//列印結果:201.051 怎麼又失敗了
System.out.println(nf.format(new BigDecimal(String.valueOf(d*100)));//列印結果:201.051 失敗
System.out.println(BigDecimal.valueOf(d*100).setScale(3,BigDecimal.ROUND_HALF_UP));//列印結果:201.051  使用BigDecimal同樣失敗

    按照之前說的轉為BigDecimal,但是還是出現錯誤,原因出在2.010515*100上,直接列印一下看看:

System.out.println(d*100);//列印結果:201.05149999999998,不是2.010515

    結果精度丟失了,變成了201.05149999999998,所以上面的問題不是四捨五入的模式有bug,而是精度又丟失了,就相當於對201.05149999999998保留三位,四捨五入後就變成了201.051,而不是201.052,怎麼解決?還是使用BigDecimal:

System.out.println(nf.format(BigDecimal.valueOf(d).multiply(BigDecimal.valueOf(100))));//列印結果:201.052 成功

    通過BigDecimal運算函式來替換運算子,可以保證精度的不丟失,所以成功了,但是並不是所有的情況我們都能轉化,比如d*100是作為double引數傳遞到我們的函式中的,我們無法干預函式之外的行為,怎麼辦?

    對於上面的丟失精度問題,可以先判斷一下小數位數,當超多12位的時候認為丟失精度了,先進行一次保留12位小數的四捨五入,然後再進行實際位數的四捨五入(不確定該方法是否通用):

nf.setMaximumFractionDigits(12);
BigDecimal bd = new BigDecimal(nf.format(BigDecimal.valueOf(d*100)));
nf.setMaximumFractionDigits(3);
System.out.println(nf.format(bd));//列印結果:201.052 達到目的

    或者使用BigDecimal:

System.out.println(BigDecimal.valueOf(d*100).setScale(12,BigDecimal.ROUND_HALF_UP).setScale(3,BigDecimal.ROUND_HALF_UP));//列印結果:201.052

    5,正負號問題

    兩個浮點數相減,比如:0.0003-0.0005,取3位小數位:

NumberFormat nf = NumberFormat.getNumberInstance();
nf.setMaximumFractionDigits(3);
nf.setMinimumFractionDigits(nf.getMaximumFractionDigits());
System.out.println(nf.format(0.0003-0.0005));//列印結果:-0.000
System.out.println(BigDecimal.valueOf(0.0003-0.0005).setScale(3,BigDecimal.ROUND_HALF_UP));//0.000

    NumberFormat的結果是-0.000,BigDecimal方式的結果是0.000

    對於浮點數相關的問題還有很多其他的,等想起來再補充吧