2014-05-17 21 views
6

背景:浮點數有四捨五入的問題,所以不應該將它們與「==」進行比較。在Java中,如何測試`Double`列表是否包含特定值

問題:在Java中,如何測試Double列表是否包含特定值。 我知道各種解決方法,但我正在尋找最優雅的解決方案,可能是利用Java或第三方庫功能的解決方案。

import java.util.ArrayList; 
import java.util.List; 

public class Test { 
    public static void main(String[] args) { 
     // should be 1.38, but end up with 1.3800000000000001 
     Double d1 = new Double(1.37 + 0.01); 
     System.out.println("d1=" + d1); 

     // won't be able to test the element for containment 
     List<Double> list = new ArrayList<Double>(); 
     list.add(d1); 
     System.out.println(list.contains(1.38)); 
    } 
} 

輸出是:

d1=1.3800000000000001 
false 

謝謝。

+6

核心Java中沒有優雅的數學庫解決方案(我知道) - 您必須使用epsilon小於某個最小值的體面測試來推出自己的版本,或者使用第三方庫。說到這一點,我相信Apache庫有這方面的一些東西。 –

+0

可能重複[使用==運算符比較float/double值](http://stackoverflow.com/questions/6837007/comparing-float-double-values-using-operator?rq=1) –

+0

從@HovercraftFullOfEels之後,您可以簡單地編寫自己的方法來檢查列表是否包含通過循環收集的值。它將同樣有效,並且實施很簡單。 – Whymarrh

回答

4

一般的解決方案是編寫一個實用程序方法,循環訪問列表並檢查每個元素是否在目標值的某個閾值內。我們可以在Java 8做的稍微好一點,不過,使用Stream#anyMatch()

list.stream().anyMatch(d -> (Math.abs(d/d1 - 1) < threshold)) 

注意,我用的是平等的測試建議here

如果你沒有使用Java 8,我會寫沿此線的簡單實用的方法:

public static boolean contains(Collection<Double> collection, double key) { 
    for (double d : collection) { 
     if (Math.abs(d/key - 1) < threshold) 
      return true; 
    } 
    return false; 
} 

注意,你可能需要一個特殊的情況下添加這兩種方法檢查列表是否包含0(或使用abs(x - y) < eps方法)。這只是在平等條件結束時添加|| (abs(x) < eps && abs(y) < eps)

+0

是不是這樣做不必要的自動裝箱/拆箱? – rds

+0

@rds你會如何避免它? – arshajii

+0

與不健全的解決方案,如自定義函數http://www.java2s.com/Code/Java/Collections-Data-Structure/Twodoublevaluearraysarealmostequal.htm – rds

1

您可以將Double包裝在另一個類中,該類爲equals方法提供了「足夠接近」的方面。

package com.michaelt.so.doub; 

import java.util.HashSet; 
import java.util.Set; 

public class CloseEnough { 
    private Double d; 
    protected long masked; 
    protected Set<Long> similar; 

    public CloseEnough(Double d) { 
     this.d = d; 
     long bits = Double.doubleToLongBits(d); 
     similar = new HashSet<Long>(); 
     masked = bits & 0xFFFFFFFFFFFFFFF8L; // 111...1000 
     similar.add(bits); 
     similar.add(bits + 1); 
     similar.add(bits - 1); 
    } 

    Double getD() { 
     return d; 
    } 

    @Override 
    public boolean equals(Object o) { 
     if (this == o) { 
      return true; 
     } 
     if (!(o instanceof CloseEnough)) { 
      return false; 
     } 

     CloseEnough that = (CloseEnough) o; 

     for(Long bits : this.similar) { 
      if(that.similar.contains(bits)) { return true; } 
     } 
     return false; 
    } 

    @Override 
    public int hashCode() { 
     return (int) (masked^(masked >>> 32)); 
    } 
} 

然後一些代碼來說明吧:

package com.michaelt.so.doub; 

import java.util.ArrayList; 
import java.util.List; 

public class Main { 
    public static void main(String[] args) { 
     List<CloseEnough> foo = new ArrayList<CloseEnough>(); 
     foo.add(new CloseEnough(1.38)); 
     foo.add(new CloseEnough(0.02)); 
     foo.add(new CloseEnough(1.40)); 
     foo.add(new CloseEnough(0.20)); 
     System.out.println(foo.contains(new CloseEnough(0.0))); 
     System.out.println(foo.contains(new CloseEnough(1.37 + 0.01))); 
     System.out.println(foo.contains(new CloseEnough(0.01 + 0.01))); 
     System.out.println(foo.contains(new CloseEnough(1.39 + 0.01))); 
     System.out.println(foo.contains(new CloseEnough(0.19 + 0.01))); 
    } 
} 

這段代碼的輸出是:

 
false 
true 
true 
true 
true 

(即第一個錯誤是與0的比較,只是爲了顯示它沒有找到那些不存在的東西)

CloseEnough只是一個簡單的封裝, ks是散列碼的最低三位(足夠的並且還將一組有效的相似數字存儲在一組中。在進行等值比較時,它使用這些集合。如果兩個數字在其集合中包含共同元素,則兩個數字相等。這就是說,我相當肯定有一些值將會出現問題,a.equals(b)爲真,a.hashCode() == b.hashCode()爲錯誤,在邊緣條件下仍然會出現適當的位模式 - 這會使一些事情(如HashSet和HashMap)'不高興'(並且可能會在某個地方提出一個好問題。


可能是這更好的方法將改爲以延長ArrayList中,這樣的indexOf方法處理的數字之間的相似性:

package com.michaelt.so.doub; 

import java.util.ArrayList; 

public class SimilarList extends ArrayList<Double> { 

    @Override 
    public int indexOf(Object o) { 
     if (o == null) { 
      for (int i = 0; i < this.size(); i++) { 
       if (get(i) == null) { 
        return i; 
       } 
      } 
     } else { 
      for (int i = 0; i < this.size(); i++) { 
       if (almostEquals((Double)o, this.get(i))) { 
        return i; 
       } 
      } 
     } 
     return -1; 
    } 

    private boolean almostEquals(Double a, Double b) { 
     long abits = Double.doubleToLongBits(a); 
     long bbits = Double.doubleToLongBits(b); 

     // Handle +0 == -0 
     if((abits >> 63) != (bbits >> 63)) { 
      return a.equals(b); 
     } 

     long diff = Math.abs(abits - bbits); 

     if(diff <= 1) { 
      return true; 
     } 

     return false; 
    } 
} 

與此代碼的工作變得更容易一點(雙關語不打算):

package com.michaelt.so.doub; 

import java.util.ArrayList; 

public class ListTest { 
    public static void main(String[] args) { 
     ArrayList foo = new SimilarList(); 

     foo.add(1.38); 
     foo.add(1.40); 
     foo.add(0.02); 
     foo.add(0.20); 

     System.out.println(foo.contains(0.0)); 
     System.out.println(foo.contains(1.37 + 0.01)); 
     System.out.println(foo.contains(1.39 + 0.01)); 
     System.out.println(foo.contains(0.19 + 0.01)); 
     System.out.println(foo.contains(0.01 + 0.01)); 
    } 
} 

此代碼的輸出是:

 
false 
true 
true 
true 
true 

在這種情況下,根據代碼HasMinimalDifference在相似列表中完成位擺動。同樣,數字被轉換爲比特,但這次數學是在比較中完成的,而不是試圖處理包裝器對象的相等和散列碼。

+0

這對於'1.37'和'0.01'有效,但對於'1.4'和'0.2'則「失敗」。 – tmyklebu

+0

@tmyklebu是的,並且是所有位掩碼方法的問題 - 它們將錯過與'00'相距'10'相同距離的'01'。如果掩碼更改爲「0xf ... c」或「0xf ... 8」,則有更大的範圍,但仍然是邊緣條件。然而,試圖使用數學將'equals()'變成類似的函數意味着hashCode合約被破壞(http://docs.oracle.com/javase/7/docs/api/java/lang/中的#2點Object.html#hashCode()) - 這也不是一個可接受的解決方案。另一種選擇是擴展集合和查找,但是這會變得非常複雜 –

+0

否;這裏真正的問題是你試圖解決的問題沒有得到適當的框架。如果你在將它們比作「幾乎相等」之前搞砸了一些「雙倍」,那幾乎肯定意味着你的代碼不能用於開始,而你只是試圖把口紅放在豬身上。 – tmyklebu

3

比較比特不是一個好主意。類似於另一篇文章,但涉及NaN和Infinities。

import java.util.ArrayList; 
import java.util.List; 

public class Test { 


    public static void main(String[] args) { 
     // should be 1.38, but end up with 1.3800000000000001 
     Double d1 = 1.37d + 0.01d; 
     System.out.println("d1=" + d1); 

     // won't be able to test the element for containment 
     List<Double> list = new ArrayList<>(); 
     list.add(d1); 

     System.out.println(list.contains(1.38)); 
     System.out.println(contains(list, 1.38d, 0.00000001d)); 
    } 

    public static boolean contains(List<Double> list, double value, double precision) { 
     for (int i = 0, sz = list.size(); i < sz; i++) { 
      double d = list.get(i); 
      if (d == value || Math.abs(d - value) < precision) { 
       return true; 
      } 
     } 
     return false; 
    } 
} 
0

背景:浮點數有四捨五入問題,所以他們不應該用「==」進行比較。

這是錯誤的。在編寫浮點代碼時,您確實需要清醒,但在很多情況下,直接推理程序中可能出現的錯誤。如果你無法做到這一點,那麼至少應該得到一個關於你的計算值是多麼錯誤的經驗估計,並考慮你所看到的錯誤是否可以接受。

這意味着你無法避免弄髒自己的手並思考你的程序在做什麼。如果您打算進行近似比較,您需要了解差異意味着兩個值是否真的不同,以及差異是否意味着兩個數值可能相同。

//應該是1.38,但與1.3800000000000001

這也是假的結束。請注意,最接近的double1.370x1.5eb851eb851ecp+0而最接近的double0.010x1.47ae147ae147bp-7。當你添加這些時,你會得到0x1.6147ae147ae14f6p+0,這會輪到0x1.6147ae147ae15p+0。最接近的double1.380x1.6147ae147ae14p+0

有幾個原因爲什麼兩個稍有不同double s不比較==。這裏有兩個:

  • 如果他們這樣做,那會打破傳遞性。 (會有ab,並c這樣a == bb == c!(a == c)
  • 如果他們這樣做了,精心編寫的數字代碼將停止工作。

真正問題,試圖找到一個列表中的doubleNaN比不上==本身。您可以嘗試使用檢查needle == haystack[i] || needle != needle && haystack[i] != haystack[i]的循環。

+0

你已經提出你的觀點,但沒有回答我的問題。 – Skyline

+0

@Skyline:如果不先搞清楚自己想做什麼,就無法做自己想做的事。在這裏,這意味着找出列表中的「Double」有什麼問題。 – tmyklebu

+0

感謝您的觀衆的澄清。但是除非你提出這個問題沒有意義,否則我支持我的話。那麼你可以附加一些可能的解決方案嗎? – Skyline

相關問題