请在 2024 年 Clojure 状态调查! 中分享您的想法。

欢迎!请参阅 关于 页面以了解更多关于如何使用本网站的信息。

0
Java 互通

{{quot}} 和 {{rem}} 在双精度浮点数情况(任一参数为浮点数)中,对非有限参数给出奇怪的结果

(quot Double/POSITIVE_INFINITY 2) ; Java: Infinity NumberFormatException Infinite or NaN java.math.BigDecimal.<init> (BigDecimal.java:808) (quot 0 Double/NaN) ; Java: NaN NumberFormatException Infinite or NaN java.math.BigDecimal.<init> (BigDecimal.java:808) (quot Double/POSITIVE_INFINITY Double/POSITIVE_INFINITY) ; Java: NaN NumberFormatException Infinite or NaN java.math.BigDecimal.<init> (BigDecimal.java:808) (rem Double/POSITIVE_INFINITY 2) ; Java: NaN NumberFormatException Infinite or NaN java.math.BigDecimal.<init> (BigDecimal.java:808) (rem 0 Double/NaN) ; Java: NaN NumberFormatException Infinite or NaN java.math.BigDecimal.<init> (BigDecimal.java:808) (rem 1 Double/POSITIVE_INFINITY) ; 最奇怪的一个。Java: 1.0 => NaN

quot 和 rem 也对双精度浮点数进行除零检查,这与双精度浮点数除法和的行为不一致

(/ 1.0 0) => NaN (quot 1.0 0) ; Java: NaN ArithmeticException Divide by zero clojure.lang.Numbers.quotient (Numbers.java:176) (rem 1.0 0); Java: NaN ArithmeticException Divide by zero clojure.lang.Numbers.remainder (Numbers.java:191)

附上的补丁 并未 解决此问题,因为我不确定这是否是预期的行为。没有任何测试断言上述提到的任何行为。

这种行为的根本原因是 quot 和 rem 的实现有时(如果除法结果大于 long 的最大值)会取一个 double,将其强制转换为 BigDecimal,然后转换为 BigInteger,然后再转回到 double。这种转换意味着它不能处理非有限中间值。所有这些都完全是多余的,我认为这只是这些方法曾经返回装箱整数类型(long 或 BigInteger)时的残余物。这在(链接:https://github.com/clojure/clojure/commit/e4a76e058ceed9b152ffd00b3f83e2800207bc25 文本:此提交)改为返回原始 double 后没有进行充分的重构。

方法体应该简单地是

`
static public double quotient(double n, double d){

if(d == 0)
    throw new ArithmeticException("Divide by zero");
double q = n / d;
return (q >= 0) ? Math.floor(q) : Math.ceil(q);

}

static public double remainder(double n, double d){

if(d == 0)
    throw new ArithmeticException("Divide by zero");
return n % d;

}
`

这就是附上的补丁所做的事情。(我甚至不确定 {{d == 0}} 检查是否适当。)

即使爆炸在非有限结果上是 quot 和 rem 的一个期望属性,也没有必要进行 BigDecimal+BigInteger 转换。我可以准备一个补丁,以保留现有行为但更高效。

更多讨论在(链接:https://groups.google.com/d/msg/clojure-dev/nSqIfpqSpRM/kp3f5h-zONYJ 文本:Clojure 开发)。

7 个回答

0
_评论由:favila_ 提供

更多测试表明,{{n % d}} 不保持关系{{(= n (+ (* d (quot n d)) (rem n d)))}}以及{{(n - d * (quot n d))}},这对于我来说没有意义,因为这个关系就是[规范|http://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.17.3]所说 % 保持的。显然,% 并不是 Math.IEEEremainder() 与不同舍入的简单替代。

测试用例:(rem 40.0 0.1) == 0.0; 40.0 % 0.1 == 0.0999...(较小的分子仍然不能精确落在0上,但比%更接近。)

更新补丁,该补丁撤销了一些简化的部分,以余数为基础,并添加了此测试用例。
0

评论由:jafingerhut 提供

Francis,您的补丁 clj-1680_no_div0.patch 日期为 2015年3月24日 使用了 isFinite() 方法,该方法似乎是在 Java 1.8 中添加的,在早期版本中不存在。我猜,虽然 Clojure 的下一个版本可能会放弃对 Java 1.6 的支持,但不太可能同时放弃对 Java 1.7 的支持。如果您的补丁可以使用类似!(isInfinite() | isNaN())的东西,那将是件很好的事,我相信这是等价的,并且这两个方法都存在于早期的 Java 版本中。

0

评论由:favila 提供

更新补丁,包括与 Java 1.7 兼容的版本,并重新基于 master。

除了这个测试用例没有失败外,其他所有测试都通过,我认为这个测试用例与补丁无关。

`

 [java] FAIL in (gen-interface-source-file) (genclass.clj:151)
 [java] expected: (= "examples.clj" (str sourceFile))
 [java]   actual: (not (= "examples.clj" ""))

`

0

评论由:michaelblume 提供

Francis,我尝试下载您的补丁,但我看到了任何测试失败都没有。如果您从Clojure仓库中检出master分支,您看到同样的问题吗?您用mvn clean先执行过吗?如果是这样,那么为它打开一个工单并看看是否有其他人可以复现这个问题可能值得。

0

评论由:jafingerhut 提供

是的,如果您使用未经修改的Clojure执行'mvn clean test'或'./antsetup.sh ; ant clean; ant',看到失败,请告诉我们您正在使用的操作系统和JVM。我没有在尝试过的操作系统/JVM组合中看到这种情况。

0

评论由:favila 提供

没关系,清理后失败的测试消失了。所有测试都通过了。

0
参考:[https://clojure.atlassian.net/browse/CLJ-1680](https://clojure.atlassian.net/browse/CLJ-1680)(由favila报告)
...