在 Java 中處理「非值」雙精度
1.概述
處理數字時,有時需要為錯誤情況或不存在的值指定一個專用值。如果使用包裝類,這個任務就很簡單了;我們可以將其設定為null,
這或許不是最佳實踐,但至少可以完成任務。對於非數字值以及我們可以擴展的類,任務就更加簡單了,我們可以透過創建一個專用類來表示錯誤情況,從而獲得更清晰的方法。
然而,在本教程中,我們將討論如何對原始值實現相同的效果。在某些情況下,我們可能需要它,但可以使用哪些值並不明確。我們考慮了幾種方案,以便更輕鬆地針對特定情況選擇最合適的方案。我們也探索了一些完全無需設定非值的方法,這些方法與主題沒有直接關係,但可能有用,並為解決問題提供不同的視角。
2. 拋出例外
處理錯誤值最簡單的方法是從一開始就阻止它們被處理。例如,如果我們嘗試讀取員工數據,發現其中一些員工沒有年齡,或者年齡被設定為零,我們可以立即停止處理:
public static double findLargestThrowException(double[] array) {
if (array.length == 0) {
throw new IllegalArgumentException("Array cannot be empty");
}
double max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
}
return max;
}
這種方法將維持我們系統的不變量並簡化它,因為如果我們不允許垃圾進入,我們就不必在應用程式的其他部分處理它。
3.忽略不正確的數值
另一種方法與上一種方法類似,就是跳過那些會導致計算錯誤或系統崩潰的值。這並不總是可行的,但這是合理的方法:
public static double findLargestIgnoreNonValues(double[] array) {
double max = Double.NEGATIVE_INFINITY;
boolean foundValidValue = false;
for (double value : array) {
if (!Double.isNaN(value) && !Double.isInfinite(value)) {
if (!foundValidValue || value > max) {
max = value;
foundValidValue = true;
}
}
}
return foundValidValue ? max : -1.0;
}
例如,如果我們需要計算特定區域的平均降雨量,而某些感測器出現故障或資料缺失,我們仍然可以根據現有資訊獲得盡可能精確的計算結果。然而,與之前的方法一樣,這並不總是可行的。
4. 超出允許範圍的值
這個解決方案很大程度上取決於我們正在處理的網域。在某些情況下,我們可以用允許範圍之外的值來表示「非值」。它在標準庫中被廣泛使用,例如,如果我們使用indexOf(value)
,如果找不到給定的元素,我們得到的結果就是負數:
public static double findLargestReturnNegativeOne(double[] array) {
if (array.length == 0) {
return -1.0;
}
double max = Double.NEGATIVE_INFINITY;
boolean foundValidValue = false;
for (double value : array) {
if (!Double.isNaN(value) && !Double.isInfinite(value)) {
if (!foundValidValue || value > max) {
max = value;
foundValidValue = true;
}
}
}
return foundValidValue ? max : -1.0;
}
程式碼不會拋出異常或傳回結果;它在語法和語義上仍然是正確的。但是,答案將毫無意義,因為我們無法取得具有負索引的元素。
5. 使用包裝器
如同介紹中所提到的,物件(在本例中是包裝類別)有一個很有用的特性:我們可以為它們使用null
值。在這種情況下,我們甚至不需要更改方法的簽名;我們可以更改返回類型:
public static Double findLargestWithWrapper(double[] array) {
Double max = null;
for (double value : array) {
if (!Double.isNaN(value) && !Double.isInfinite(value)) {
if (max == null || value > max) {
max = value;
}
}
}
return max;
}
然而,這種方法會導致前兩種情況。在這種情況下,我們會將問題移到呼叫棧,並建議客戶端自行處理,但這並非最佳實踐。
6. 使用Double.NaN
另一個選項與上一個類似,是回傳Double.NaN
而不是null.
這使得程式碼在語義上更加合理,但我們仍然將問題上移到了呼叫堆疊上:
public static double findLargestReturnNaN(double[] array) {
double max = Double.NEGATIVE_INFINITY;
boolean foundValidValue = false;
for (double value : array) {
if (!Double.isNaN(value) && !Double.isInfinite(value)) {
if (!foundValidValue || value > max) {
max = value;
foundValidValue = true;
}
}
}
return foundValidValue ? max : Double.NaN;
}
關於Double.NaN:
當比較兩個NaN
值時,它總是會傳回false
。因此,如果我們依賴結果之間的比較,則應該始終使用Double.isNaN(value).
7. 結論
在本文中,我們討論瞭如何處理不正確的值或「非值」是一門棘手的藝術,並且很大程度上取決於我們計劃如何處理這些數字。像往常一樣,拋出異常是防止任何垃圾進入程式碼的最佳實踐。但是,如果無法做到這一點,我們還有一些其他選擇。 「最佳」方法取決於程式碼運行的領域和上下文。我們的目標是選擇最適合的方案。
與往常一樣,程式碼範例可在 GitHub 上找到。