在 Clojure 或 Java 中,如何有效地检查一个数字在舍入到两位小数后是否等于整数?

In Clojure or Java, how can I efficiently check whether a number is equivalent to an integer after rounding to two fractional digits?

我基本上需要 Clojure 中的一个函数,例如 rounds-to-int?,它的作用相当于:

例如

(rounds-to-int? 1) => true
(rounds-to-int? 1.234) => false
(rounds-to-int? 1.01) => false
(rounds-to-int? -1.01) => false
(rounds-to-int? 1.001) => true
(rounds-to-int? -1.001) => true
(rounds-to-int? 1.005) => false
(rounds-to-int? 1.004) => true

感谢任何建议!

理解到不精确性是所提到的数据类型所固有的(例如,大的浮点数甚至不能用整数精度覆盖,所以如果你输入一个大的 Long 这将 return假):

boolean roundsToInt(Number num) {
  var fractional = num.doubleValue() - num.intValue();
  return Math.abs(fractional) < 0.01; // usual warning about exact representations in binary
}

首先,让我们借用 Clojure 实现一个舍入到特定精度的函数。我稍微修改了 this answer from another SO question.

(defn round-to
  "Modified from: 
  [precision d]
  (let [factor (Math/pow 10 (- precision))]
    (/ (Math/round (* d factor)) factor)))

接下来我们可以实现我们的自定义舍入逻辑,该逻辑考虑数字是 -1 和 1 之间的小数部分的情况。诀窍是使用 log10 以获得第一个非零的数字数字,也称为最高有效数字。我们可以利用 round-to 函数根据我们的特殊规则对 x 进行舍入。

(defn my-fancy-round
  [x]
  ;; log10 can "count" the number of digits from the decimal point to the most significant digit. 
  ;; A negative log10 indicates that the most significant digit is to the right of the decimal point (aka x is between 0 and 1).
  (let [precision (int (Math/log10 (Math/abs x)))]
    (round-to (min precision -2) x)))

最后,我们编写 rounds-to-int?,它将使用我们的自定义逻辑对数字进行舍入,并检查结果是否为整数。

(defn rounds-to-int?
  [x]
  (let [rounded (my-fancy-round x)]
    (= rounded (Math/floor rounded))))

这是我想出并最终使用的解决方案。

(defn rounds-to-int?
  [value]
  (let [rounded (.setScale (bigdec value) 2 java.math.RoundingMode/HALF_UP)]
    (== (int rounded) rounded)))

请参阅函数rel= in the Tupelo library。它完全符合您的要求:

(is      (rel=   123450000   123456789 :digits 4 ))       ; .12345 * 10^9
(is (not (rel=   123450000   123456789 :digits 6 )))
(is      (rel= 0.123450000 0.123456789 :digits 4 ))       ; .12345 * 1
(is (not (rel= 0.123450000 0.123456789 :digits 6 )))

(is      (rel= 1 1.001 :tol 0.01 ))                       ; :tol value is absolute error
(is (not (rel= 1 1.001 :tol 0.0001 )))