In Java, how to check if a `Double` list contains a specific value

Reference Information. Floating-point numbers have rounding problems, so they should never be compared with "==".

Question. In Java, how to check if a Double list contains a specific value. I know of various workarounds, but I'm looking for the most elegant solution , presumably those that use the functions of a Java library or third-party libraries.

 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)); } } 

Output:

 d1=1.3800000000000001 false 

Thanks.

+6
source share
4 answers

A general solution would be to write a utility method that goes through the list and checks to see if each item is at a specific threshold of the target value. We can do a little better in Java 8, though using Stream#anyMatch() :

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

Please note that I am using the equality test suggested here .

If you are not using Java 8, I would write a simple utility method according to this:

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

Note that you may need to add a special case for both of these approaches to check if the list contains 0 (or uses the abs(x - y) < eps approach). This will only consist of adding || (abs(x) < eps && abs(y) < eps) || (abs(x) < eps && abs(y) < eps) at the end of the equality conditions.

+4
source

Comparing bits was not a good idea. Similar to another post, but dealing with NaN and 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; } } 
+3
source

You can wrap Double in another class that provides a "fairly close" aspect to the equals method.

 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)); } } 

And then some code to demonstrate this:

 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))); } } 

The output of this code is:

  false
 true
 true
 true
 true

(the first false is a comparison with 0, just to show that it does not find things that are not there)

CloseEnough is just a simple wrapper around a double that masks the least significant three bits for the hash code (enough that it also stores a valid set of identical numbers in the set). When comparing equally, he uses sets. numbers are equal if they contain a common element in their sets.

However, I am sure there are some values ​​that would be problematic if a.equals(b) was true and a.hashCode() == b.hashCode() was false, which could still occur in extreme conditions for the right bit patterns - this can make some things (like HashSet and HashMap) "dissatisfied" (and probably will make a good question somewhere.


Probably the best approach would be to extend the ArrayList so that the indexOf method handles the similarity between numbers:

 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; } } 

Working with this code is getting a little easier (no pun intended):

 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)); } } 

The output of this code is:

  false
 true
 true
 true
 true

In this case, bit-scripting is performed in SimilarList based on HasMinimalDifference code. Again, numbers are converted to bits, but this time the math is done in comparison, rather than trying to work with the equality symbol and hash of the wrapper object.

+1
source

Reference Information. Floating-point numbers have rounding problems, so they should never be compared with "==".

This is not true. You should be awake when writing floating point code, but the reasonable errors that are possible in your program are in many cases simple. If you cannot do this, you should at least get an empirical estimate of how erroneous your calculated values ​​are and think about whether the errors you see are reasonably small.

What this means is that you cannot avoid dirty hands and think about what your program is doing. If you intend to work with rough comparisons, you need to be aware that the differences mean that the two values ​​are really different, and which differences mean that the two values ​​can be the same.

// should be 1.38, but the result is 1.3800000000000001

This is also not true. Please note that the nearest double to 1.37 is 0x1.5eb851eb851ecp+0 , and the nearest double to 0.01 is 0x1.47ae147ae147bp-7 . When you add them, you get 0x1.6147ae147ae14f6p+0 , which is rounded to 0x1.6147ae147ae15p+0 . The closest double to 1.38 is 0x1.6147ae147ae14p+0 .

There are several reasons why two slightly different double do not compare == . Here are two:

  • If they did, it would violate transitivity. (There would be a , b and c such that a == b and b == c , but !(a == c) .
  • If they do, a carefully written digital code will stop working.

The real problem with finding double in a list is that NaN does not compare == with itself. You can try using a loop that checks needle == haystack[i] || needle != needle && haystack[i] != haystack[i] needle == haystack[i] || needle != needle && haystack[i] != haystack[i] .

0
source

Source: https://habr.com/ru/post/969411/


All Articles