在 Java 中從 float 轉換為 BigDecimal
1. 概述
在 Java 中處理數字最自然的選擇是使用各種可用的基本類型。我們可以使用float或double來表示小數。
在某些用例中這可能就足夠了,但在其他用例中,特別是當需要準確性和精確度時, BigDecimal是更好的選擇。有時,我們可能需要從float值轉換為BigDecimal 。我們將在本文中探討實現此目的的各種方法。
2. float和BigDecimal類型的簡要說明
在討論從float轉換為BigDecimal,我們先簡單描述一下它們。
2.1. float
原始float是一種 32 位元類型,用於表示浮點數,即小數。 float實作遵循IEEE 754 規範,這是一個廣泛使用的標準。它有一個對應的 Java 類,名為Float 。我們還有一個名為double.我們可以將float稱為單精度類型, double雙精度類型。
計算機中浮點數的內部實現本質上是不準確的。以精確的精度表示實數需要無限位。透過執行一些簡單的測試,我們可以很容易地看出會出現什麼問題:
@Test
public void whenFloatComparedWithDifferentValues_thenCouldMatch() {
assertNotEquals(1.1f, 1.09f);
assertEquals(1.1f, 1.09999999f);
}
因此,儘管「1.1」和「1.09999999」是不同的數字,但在使用float類型的 Java 程式中相互比較時,它們看起來是相同的。
我們可以透過線上工具FloatConverter並用上例中使用的分數「1.1」進行測試來了解各種float的內部表示形式。根據該工具,計算機儲存的內部值為「1.10000002384185791015625」。
當我們將小數從十進制轉換為二進制時,我們需要特定的位數,對於某些分數來說可能是無限的。在計算機中,可用的位數是有限的。如果我們再次將這個有限表示形式轉換回十進制,我們會得到與原始結果不同的結果。
我們將在以下部分中看到從float到BigDecimal的各種轉換方法,使用1.10000002384185791015625f值作為一致的float範例進行轉換以簡化討論,因為我們知道它以 IEEE 754 二進位形式準確表示。
2.2. BigDecimal
當我們需要更高的準確性和精確度時,推薦使用BigDecimal類別來處理小數。例如,金融應用程式經常出現這種情況。
它有一個所謂的「未縮放」部分,即任意精確度的整數(僅受可用記憶體限制),以及一個名為「scale」的 32 位元整數部分,表示小數點右側的位數觀點。
BigDecimal提供了對數字和格式轉換執行各種操作的方法。 BigDecimal實例表示單一小數。我們可以使用toString方法來取得實例的String表示形式。
3. 如何將float轉換為BigDecimal
BigDecimal有許多建構函式和方法來執行型別轉換,包括float到BigDecimal 。但我們需要小心,因為 Java float類型實作的浮點數表示有限制。我們將在以下部分中看到執行轉換的不同方法。
3.1.使用帶有float參數的BigDecimal double建構函數
BigDecimal的構造函數之一採用double精度作為參數。我們可以編寫一個簡單的測試來評估向其傳遞float參數時的轉換結果:
@Test
public void whenCreatedFromFloat_thenMatchesInternallyStoredValue() {
float floatToConvert = 1.10000002384185791015625f;
BigDecimal bdFromFloat = new BigDecimal(floatToConvert);
assertEquals("1.10000002384185791015625", bdFromFloat.toString());
}
我們使用了1.10000002384185791015625f ,這與我們使用線上工具遇到的值相同,我們知道它是準確表示的。我們可以在這裡看到BigDecimal執行了與 IEEE 754 表示一致的轉換。
使用BigDecimal float建構函數,在具有現有float值的實際場景中,我們至少可以確信轉換與內部浮點表示形式一致。程式設計師需要謹慎處理BigDecimal結果值。
3.2.使用帶有String參數的BigDecimal建構函數
使用小數初始化BigDecimal的最可靠方法是使用帶有String參數的建構函數,並傳遞最初表示為String物件的輸入值。典型的場景是輸入數字來自使用者介面並儲存為String物件。這些數字可以透過使用上面提到的構造函數來保留其原始表示形式:
@Test
public void whenCreatedFromString_thenPreservesTheOriginal() {
BigDecimal bdFromString = new BigDecimal("1.1");
assertEquals("1.1", bdFromString.toString());
}
不過,在本文中,我們將介紹float到BigDecimal轉換。我們取得一個float值,使用Float類別的toString方法產生String表示形式,然後將其傳遞給BigDecimal建構函數:
@Test
public void whenCreatedFromFloatConvertedToString_thenFloatInternalValueGetsTruncated() {
String floatValue = Float.toString(1.10000002384185791015625f);
BigDecimal bdFromString = new BigDecimal(floatValue);
assertEquals("1.1", floatValue);
assertEquals("1.1", bdFromString.toString());
}
在上面的測試中,我們可以看到Float toString方法在應用於1.10000002384185791015625f時給出“1.1” ,而BigDecimal String建構子只保留這個中間String值。
透過其String建構函式將現有float值轉換為BigDecimal在保留其內部表示的精確度方面並不可靠。
3.3.使用BigDecimal valueOf方法
除了其建構函式之外, BigDecimal還具有valueOf靜態方法來執行轉換,並以double為唯一參數。從BigDecimal原始碼我們可以看到,它的實作只是將Double toString方法的結果傳遞給String建構子:
public static BigDecimal valueOf(double val) {
return new BigDecimal(Double.toString(val));
}
Double toString執行的效果是將1.10000002384185791015625f截斷為“1.100000023841858” :
@Test
public void whenDoubleConvertsFloatToString_thenFloatValueGetsTruncated() {
assertEquals("1.100000023841858", Double.toString(1.10000002384185791015625f));
}
由於valueOf實作只是Double.toString(value) ,我們期望透過執行它得到相同的結果:
@Test
public void whenCreatedByValueOf_thenFloatValueGetsTruncated() {
assertEquals("1.100000023841858", BigDecimal.valueOf(1.10000002384185791015625f).toString());
}
我們可以得出結論,使用valueOf方法保留的精度低於帶有float參數的建構子。
4。結論
在本文中,我們描述了將float值轉換為BigDecimal的幾種方法。帶有float參數的BigDecimal構造函數在保持現有float值的精確度方面具有最佳結果。
我們也可以得出結論,如果我們想要完全控制帶有小數的BigDecimal的初始化,我們應該向String建構函式提供最初表示為String物件的值。
範例程式碼可在 GitHub 上取得。