以下是该程序的输出。
value is : 2.7755575615628914E-17
Double.compare with zero : 1
isEqual with zero : true
我的问题是,什么应该是epsilon值?是否有任何可靠的方法来获取价值,而不是从天空中挑选一个数字。
package sandbox;
/**
*
* @author yccheok
*/
public class Main {
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
double zero = 1.0/5.0 + 1.0/5.0 - 1.0/10.0 - 1.0/10.0 - 1.0/10.0 - 1.0/10.0;
System.out.println("value is : " + zero);
System.out.println("Double.compare with zero : " + Double.compare(zero, 0.0));
System.out.println("isEqual with zero : " + isEqual(zero, 0.0));
}
public static boolean isEqual(double d0, double d1) {
final double epsilon = 0.0000001;
return d0 == d1 ? true : Math.abs(d0 - d1) < epsilon;
}
}
答案 0 :(得分:11)
我喜欢(伪代码,我不做java)
bool fuzzyEquals(double a, double b)
{
return abs(a - b) < eps * max(abs(a), abs(b));
}
与epsilon是机器epsilon的几倍。如果您不知道该使用什么,请取10 ^ -12。
然而,这是完全依赖于问题的。如果给出a和b的计算容易出现舍入误差,或涉及许多操作,或者它们本身处于某种(已知的)准确度之内,那么你想要采用更大的epsilon。
重点是使用 relative 精度,而不是绝对精度。
答案 1 :(得分:8)
没有一个正确的价值。您需要相对于所涉及的数字的大小来计算它。你基本上处理的是一些有效数字,而不是具体的数字。例如,如果您的数字都在1e-100范围内,并且您的计算应保持大致8位有效数字,那么您的epsilon应该在1e-108左右。如果你对1e + 200范围内的数字做了相同的计算,那么你的epsilon将在1e + 192左右(即epsilon~ = magnitude - 有效数字)。
我还注意到isEqual
名字很差 - 你需要像isNearlyEQual
这样的东西。出于一个原因,人们相当合理地期望“平等”是可传递的。至少,您需要表达结果不再具有传递性的想法 - 即,如果您对isEqual
的定义,isEqual(a, c)
可能是错误的,即使isEqual(a, b)
和{ {1}}都是真的。
编辑:(回应评论):我说“如果你的计算应保持大致8位有效数字,那么你的epsilon应该......”。基本上,它涉及到你正在做什么计算,以及你在这个过程中可能失去多少精确度,以提供一个合理的猜测,在重要之前差异有多大。在不知道你正在做的计算的情况下,我无法猜测。
就epsilon的大小而言:不, 总是小于或等于1是有意义的。浮点数只能保持有限的精度。在IEEE双精度浮点的情况下,可以表示的最大精度约为20个十进制数字。这意味着如果你从1e + 200开始,机器可以代表所有的那个数字的绝对最小差异是大约1e + 180(并且双倍可以表示高达~1e + 308的数字,在这一点上,可以表示的最小差异是~1e + 288)。
答案 2 :(得分:8)
你的第二个问题的答案是否定的。有限机器精度误差的大小可以任意大:
public static void main(String[] args) {
double z = 0.0;
double x = 0.23;
double y = 1.0 / x;
int N = 50000;
for (int i = 0; i < N; i++) {
z += x * y - 1.0;
}
System.out.println("z should be zero, is " + z);
}
这会提供~5.55E-12
,但如果您增加N
,则可以获得您想要的任何级别的错误。
关于如何编写数值稳定的算法,有大量过去和现在的研究。这是一个难题。
答案 3 :(得分:7)
在isEqual
中,有类似的内容:
epsilon = Math.max(Math.ulp(d0), Math.ulp(d1))
双值的ulp是它之间的正距离 浮点值和下一个更大的双值。 [1]
[1] http://docs.oracle.com/javase/6/docs/api/java/lang/Math.html#ulp%28double%29
答案 4 :(得分:1)
您首先应该阅读https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/。
讨论了比较浮点数的各种方法:绝对容差,相对容差,ulp距离。它提供了一个相当不错的论据,即ulp检查是要走的路。这个案例取决于如果你想检查两个浮点数是否相同,你必须考虑可表示的浮点数之间的距离。换句话说,你应该检查这两个数字是否在彼此的e浮点数内。
算法在C中给出,但可以使用java.lang.Double#doubleToLongBits
和java.lang.Float#floatToIntBits
转换为java,以实现从浮动到整数类型的转换。另外,用java&gt; 1.5有方法ulp(double)
ulp(float)
和java&gt; 1.6 nextUp(double)
nextUp(float)
nextAfter(double, double)
nextAfter(float, float)
,可用于量化两个浮点数之间的差异。
答案 5 :(得分:1)
这里涉及两个概念:
Double.ulp()
double d
的机器精度:Double.ulp(d)
如果你致电Double.ulp()
,你将获得机器精度单位,这是你可以从某个硬件平台获得的精度......无论这个定义是什么!
如果您致电Double.ulp(d)
,您将获得double d
的机器精度。换句话说,每个double d
都有其特定的精度。这比前一段更有用。
当您执行涉及级联计算的迭代时,您必须特别注意细节,即:当前计算中使用先前计算的结果时。这是因为错误在这些情况下累积,并且在某些情况下可能最终导致结果偏离它们应该提供的真实价值。在某些情况下,累积误差的大小甚至可能大于真实值。查看一些disastrous examples here。
在某些业务领域,数值计算错误根本不可接受。根据业务领域,法规,要求和特性,您必须采用其他方法来简化选择使用浮点运算(即:doubles
或floats
)。
例如,在财务案例中,永远不会使用浮点运算。在处理金钱时,永远不要使用doubles
或floats
。决不。期。您可以根据具体情况使用BigDecimal或fixed point arithmetic。
在处理股票价格的特定情况下,您知道价格总是精确到5位数,在这种情况下,fixed point arithmetic足够多,并且还提供了您可能获得的最大性能,这是一个这个业务领域非常强大和普遍的要求。
如果业务领域确实需要数值计算,那么在这种情况下,您必须确保在严格且非常小心的控制下保持错误传播。这是一个很长的主题,有很多技术,很多时候开发人员忽略了这个问题,只是认为对一个为他们做了所有艰苦工作的方法有一个神奇的调用。不,它没有。你必须做你的研究,做你的功课,并做所有必要的努力工作,以确保你控制错误。您需要准确了解您实施的数值算法的确切情况。