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

欢迎!请参阅关于页面,以了解此服务的更多信息。

0
Java Interop

{{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 文本:此提交)改回返回原始双精度浮点数,但这些方法体没有经过足够的重构。

方法体应该简单地是

`
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 dev)。

7 个答案

0
by
评论者: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
by

评论者:jafingerhut

Francis,您于2015年3月24日提供的补丁clj-1680_no_div0.patch使用了isFinite()方法,该方法似乎是在Java 1.8中添加的,在早期版本中不存在。我猜测,在Clojure的下一个版本可能会放弃对Java 1.6的支持,但是同时放弃对Java 1.7的支持的可能性较小。如果您的补丁可以使用类似<(isInfinite() | isNaN())>这样的方法,我认为它们等效,并且两种方法都存在于早期Java版本中,可能会很方便。

0
by

评论者: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
by

评论者:michaelblume

Francis,我尝试下载您的补丁,但我根本没看到任何测试失败。您在从Clojure仓库检查出的master分支上还会看到这样的失败吗?在您运行mvn clean之后,您还会看到它吗?如果是这样,为它打开一个问题并看看是否有人可以重现可能会有帮助。

0
by

评论者:jafingerhut

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

0
by

评论者:favila

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

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