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