在 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 上取得。