truncatingRemainder(dividingBy: ) 返回非零余数,即使数字是完全可整除的

truncatingRemainder(dividingBy: ) returning nonZero remainder even if number is completely divisible

我正在尝试使用 swift 的 truncatingRemainder(dividingBy:) 方法获取余数。 但是即使我使用的值可以被设计者完全整除,我得到的余数也不为零。我在这里尝试了很多可用的解决方案,但 none 有效。

P.S。我使用的值是 Double(也试过 Float)。

这是我的代码。

    let price = 0.5
    let tick = 0.05
    let remainder = price.truncatingRemainder(dividingBy: tick)
    if remainder != 0 {
      return "Price should be in multiple of tick"
    }

我得到 0.049999999999999975 作为余数,这显然不是预期的结果。

像往常一样(参见 https://floating-point-gui.de),这是由数字在计算机中的存储方式引起的。

根据文档,这正是我们所期望的

let price = //
let tick = //
let r = price.truncatingRemainder(dividingBy: tick)
let q = (price/tick).rounded(.towardZero)
tick*q+r == price // should be true

在你看来tick平分price的情况下,一切都取决于内部存储系统。例如,如果 price0.4 并且 tick0.04,那么 r 会逐渐接近于零(如您所料)并且最后一个陈述为真.

但是当 price0.5 并且 tick0.05 时,由于数字的存储方式存在微小差异,我们最终得到这种奇怪的情况 r 不是消失在零附近,而是在 tick 附近消失!当然,最后一个陈述是错误的。

您只需在代码中进行补偿。显然余数不能是除数,所以如果余数 非常接近除数(在某个 epsilon 内),你只需要忽略它并 调用 为零。

您可以就此提交错误,但我怀疑是否可以对此做很多事情。


好的,我对此进行了查询,结果如我所料,它按预期运行。回复(来自 Stephen Canon)是:

That's the correct behavior. 0.05 is a Double with the value 0.05000000000000000277555756156289135105907917022705078125. Dividing 0.5 by that value in exact arithmetic gives 9 with a remainder of 0.04999999999999997501998194593397784046828746795654296875, which is exactly the result you're seeing.

The only rounding error that occurs in your example is in the division price/tick, which rounds up to 10 before your .rounded(.towardZero) has a chance to take effect. We'll add an API to let you do something like price.divided(by: tick, rounding: .towardZero) at some point, which will eliminate this rounding, but the behavior of truncatingRemainder is precisely as intended.

You really want to have either a decimal type (also on the list of things to do) or to scale the problem by a power of ten so that your divisor become exact:

    1> let price = 50.0
      price: Double = 50
    2> let tick = 5.0
      tick: Double = 5
    3> let r = price.truncatingRemainder(dividingBy: tick)
      r: Double = 0