【问题标题】:Math problems in Java; uncanny NaN from a calculationJava中的数学问题;计算得出的不可思议的 NaN
【发布时间】:2011-03-12 09:20:22
【问题描述】:

我正在开展一个项目,该项目基于这些查询的结果进行大量查询和一些数学建模,最后是一些评分(阅读:“执行时间太长,无法彻底测试”)。

最近我在我的代码中发现了一个相当新的问题/错误;一些结果得到NaN 的分数值!以下是分数的计算方式:

请注意,pfoundpsigdoubles,始终为正或 0

Double  score1 = (pfound!=0) ? (Math.log(factorial((int)psig + 1))/pfound) : 0;

score1 = score1 * alpha_coeff[0];
if (score1.isInfinite())
    throw new RuntimeException(p.getName() + " score1 = Inf");
else if(score1.isNaN())
    throw new RuntimeException(p.getName() + " score1 = NaN");

我已经检查了触发 NaN 的可能原因,但我相信大多数情况下它应该是安全的:

  1. 我已经在检查 pfound == 0(所以没有除以零)

  2. Math.log() 的参数不能为负值

我怀疑factorial()(将阶乘返回为long 的自定义函数)是否返回long 大到不能在不损失精度的情况下转换为double或类似的东西。我检查了Long.doubleValue(),如果它的参数导致NaN,显然它会生成NaN

有没有cmets?我在这里错过了一些基本的东西吗?

【问题讨论】:

  • 不要担心将 long 转换为 double - 从某种意义上说,这始终是安全的,因为它不会产生 NaN,尽管您可能会丢失一些低阶精度。

标签: java math logarithm


【解决方案1】:

如果您的阶乘对 x 进行天真的评估! = 1 * 2 * 3 ...,我敢打赌,您要的是一个不适合您使用的参考的数字的阶乘。

两条建议:

  1. 如果是这种情况,请尝试 BigDecimal
  2. 使用gamma function 而不是幼稚的实现。

n > 12 的阶乘(n) 的递归是一个非常糟糕、幼稚的想法。你并没有认真考虑过这样的事情,是吗?

【讨论】:

  • 是的,它是一个非常简单的递归函数,而且是的,它很可能会导致溢出,正如我在上面对@mikera 帖子的评论中提到的那样。您提到 gamma 函数非常有趣,我之前没有想到过 :) 您能否评论一下它的实现而不是递归阶乘方法的性能。哦,最后,我想不管我用什么,我仍然需要BigInteger,对吧?
  • 不,BigDecimal。伽玛函数比朴素阶乘更通用。 Gamma 的不同之处在于它不是递归的:您将参数传递给它,然后返回值。 “数字食谱”中有一个很好的实现。您还可以查看 Abramowitz 和 Stegun。或者这个:luschny.de/math/factorial/FastFactorialFunctions.htm
  • 阶乘应该是一个查找表,而不是在运行时计算。
  • 嗯,谢谢你的链接。原来logGamma() 的实现很容易在我已经实现的 Apache Commons Math 包中实现。然后n! = Math.exp(logGamma(n+1)) 应该能解决我的问题吗?
  • 我刚刚意识到我根本不需要 exp 函数,非常感谢对阶乘的帮助!
【解决方案2】:

NaN 通过各种算术运算传播,因此即使您正确检查此处的条件,它们也可能是从其他地方引入的。

我怀疑是 alpha_coeff[0] 或 pfound - 尝试检查这些是否为 NaN。

NaN 也可能是阶乘函数的结果,具体取决于它的定义方式。编辑:刚刚注意到您指定这会产生很长的值,因此阶乘不能产生 NaN,另一方面,如果溢出会导致 log() 中的 NaN,它可能会产生负面结果。

【讨论】:

  • 附注请注意 0 != NaN,因此如果 pfound 为 NaN,则执行条件的前半部分。这将产生 NaN 作为整体结果。
  • alpha_coeff.s 都是 0-1 之间的正双倍。所以这也很安全。至于阶乘(),我想你可能会做一些事情,我检查了一下,它出现了他们可以持有的最大值是 2^63-1,相当于 E18。必须检查我的阶乘是否高于此.. ps:有人知道如何排版 cmets,以便为代码相关的东西(如 NaN)显示灰色背景
  • 如果阶乘溢出,您可能会得到一个负值,这将导致 log() 函数产生 NaN .....
  • 好的,事实证明值阶乘应该返回在 E32 左右,这显然超过了 Long 的最大值。如果我使用BigInteger 代替我的阶乘函数,我可以将它发送到Math.log() 就像一个long 一样吗?
  • 是的,您应该可以使用 BigInteger,您可能需要在某些时候应用 BigInteger.doubleValue() 以在执行日志之前转换为 double。
【解决方案3】:

您不应该明确地将双精度数与零进行比较 - 它几乎永远不会起作用。最好做这样的事情:

double EPS = 0.0000001;
if (Math.abs (pfound) < EPS) { //pfound is null } 

我看到的唯一可以产生 NaN 的地方是 Math.log。从其文档中:

  1. 如果参数为 NaN 或小于零,则结果为 NaN。
  2. 如果参数为正无穷大,则结果为正 无穷大。
  3. 如果参数为正零或负零,则结果为 负无穷大。

我认为pfound 包含接近零的负值,这就是你得到 NaN 的原因。尝试在调试器中跟踪变量值。

【讨论】:

  • 除此之外,如果它们 (pFound, psig) 总是正数或零,为什么要使用 Double?
  • 与 pfound 和 psig 的伟大通话,我看不出它们为什么应该是双倍的,肯定已经忘记了那里的东西..无论如何它们现在都是 int.s..
  • 在处理 Java 时,通常最好不要认为(或建议)“null”和“0”是同一个东西。双精度型不能赋值为 null,但可以赋值为 0。
猜你喜欢
  • 2011-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 2011-02-22
  • 2017-11-21
  • 2014-04-23
相关资源
最近更新 更多