我希望使用定义的容差(HashSet
)为实数(目前Double
s)创建epsilon
,(cf Assert.assertEquals(double, double, double)
由于使用Double.equals()
仅适用于完全相等,Double
是最终类,我无法使用它。我最初的想法是使用HashSet
方法扩展DoubleHashSet
(例如转到setEpsilon(double)
)并创建一个新的类ComparableDouble
,其中equals()
使用DoubleHashSet
中的此值{1}}。但是,我想检查是否已有现有的解决方案和现有的F / OSS库。
(将来我想把它扩展到实数的元组 - 例如矩形和立方体 - 所以最好采用通用的方法
注意:@NPE表示这是不可能的。不幸的是我怀疑这是正式正确:-)所以我想知道是否有近似的方法......其他人一定有这个问题并且大致解决了。 (我已经经常使用工具Real.isEqual(a, b, epsilon)
并且它非常有用。)我准备接受一些不常见的传递性错误。
注意:我将使用TreeSet,因为它解决了“几乎等于()”的问题。稍后我将比较complexNumbers,矩形(和更复杂的对象),并且能够设置两个相等的限制是非常有用的。 complexNumbers没有简单的自然排序(也许Cantor方法可行),但我们可以判断它们是否几乎相等。
答案 0 :(得分:4)
这种方法存在一些根本性缺陷。
HashSet
使用equals()
检查两个元素是否相等。 The contract on equals()
has the following among its requirements:
transitive :对于任何非空引用值
x
,y
和z
,如果x.equals(y)
返回{{1} }和true
返回y.equals(z)
,然后true
应该返回x.equals(z)
。
现在考虑以下示例:
true
很明显,您提出的比较方案会打破传递性要求(x = 0.0
y = 0.9 * epsilon
z = 1.8 * epsilon
等于x
而y
等于y
,但z
没有&#39 ; t等于x
)。在这些情况下,z
无法正常运行。
此外,HashSet
会产生额外的挑战,原因如下requirement:
如果两个对象根据
hashCode()
方法相等,则在两个对象中的每一个上调用equals(Object)
方法必须产生相同的整数结果。
使用hashCode
代替hashCode()
可以回避TreeSet
要求。
答案 1 :(得分:1)
我会做的是在使用它们之前围绕双打(假设这是合适的)
e.g。
public static double roundByFactor(double d, long factor) {
return (double) Math.round(d * factor) / factor;
}
TDoubleHashSet set = new TDoubleHashSet(); // more efficient than HashSet<Double>
set.add(roundByFactor(1.001, 100));
set.add(roundByFactor(1.005, 100));
set.add(roundByFactor(1.01, 100));
// set has two elements.
您可以在自己的DoubleHashSet中包装此行为。如果要保留原始值,可以使用HashMap或TDoubleDoubleHashMap,其中键是舍入值,值是原始值。
答案 2 :(得分:0)
我实施了@ NPE的方法(我接受了他/她的答案,所以他/她得到了点数:-)并在这里给出了代码
//Create a comparator:
public class RealComparator implements Comparator<Double> {
private double epsilon = 0.0d;
public RealComparator(double eps) {
this.setEpsilon(eps);
}
/**
* if Math.abs(d0-d1) <= epsilon
* return -1 if either arg is null
*/
public int compare(Double d0, Double d1) {
if (d0 == null || d1 == null) {
return -1;
}
double delta = Math.abs(d0 - d1);
if (delta <= epsilon) {
return 0;
}
return (d0 < d1) ? -1 : 1;
}
/** set the tolerance
* negative values are converted to positive
* @param epsilon
*/
public void setEpsilon(double epsilon) {
this.epsilon = Math.abs(epsilon);
}
并测试它
public final static Double ONE = 1.0;
public final static Double THREE = 3.0;
@Test
public void testTreeSet(){
RealComparator comparator = new RealComparator(0.0);
Set<Double> set = new TreeSet<Double>(comparator);
set.add(ONE);
set.add(ONE);
set.add(THREE);
Assert.assertEquals(2, set.size());
}
@Test
public void testTreeSet1(){
RealComparator comparator = new RealComparator(0.0);
Set<Double> set = new TreeSet<Double>(comparator);
set.add(ONE);
set.add(ONE-0.001);
set.add(THREE);
Assert.assertEquals(3, set.size());
}
@Test
public void testTreeSet2(){
RealComparator comparator = new RealComparator(0.01);
Set<Double> set = new TreeSet<Double>(comparator);
set.add(ONE);
set.add(ONE - 0.001);
set.add(THREE);
Assert.assertEquals(2, set.size());
}
@Test
public void testTreeSet3(){
RealComparator comparator = new RealComparator(0.01);
Set<Double> set = new TreeSet<Double>(comparator);
set.add(ONE - 0.001);
set.add(ONE);
set.add(THREE);
Assert.assertEquals(2, set.size());
}