truncatingRemainder (dividingBy:), возвращающий ненулевой остаток, даже если число полностью делится - PullRequest
1 голос
/ 22 октября 2019

Я пытаюсь получить остаток, используя метод truncatingRemainder(dividingBy:) Свифта. Но я получаю ненулевой остаток, даже если значение, которое я использую, полностью делится на deviser. Я испробовал несколько доступных здесь решений, но ни одно из них не сработало.

Значения PS, которые я использую: Double (также Tried 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 как остаток, который явно не является ожидаемым результатом.

1 Ответ

2 голосов
/ 22 октября 2019

Как обычно (см. 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, все зависит от внутренней системы хранения. Например, если price равно 0.4, а tick равно 0.04, тогда r исчезающе близко к нулю (как вы ожидаете), и последнее утверждение верно.

Но когда price - это 0.5, а tick - это 0.05, есть небольшое несоответствие из-за способа хранения чисел, и мы в конечном итоге получаем такую ​​странную ситуацию, когда r вместо того, чтобы исчезающе приблизиться к нулю,исчезает близко к tick! И, конечно, последнее утверждение ложно.

Вам просто придется компенсировать это в своем коде. Очевидно, что остаток не может быть делителем, поэтому, если остаток исчезающе близко к делителю (в пределах некоторого эпсилона), вам просто нужно игнорировать его и назвать его нулем.

Вы можете отправить сообщение об ошибке, но я сомневаюсь, что с этим можно многое сделать.


Хорошо, я поставил запрос об этом и получил обратно, что он ведет себя как задумано,как я и подозревал. Ответ (от Стивен Кэнон ) был:

Это правильное поведение. 0,05 является двойным со значением 0,05000000000000000277555756156289135105907917022705078125. Деление 0.5 на это значение в точной арифметике дает 9 с остатком 0.04999999999999997501998194593397784046828746795654296875, который является именно тем результатом, который вы видите.

Единственная ошибка округления, которая возникает в вашем примере, заключается в цене / тике деления, котораяокругляется до 10 до того, как ваш .rounded(.towardZero) получит шанс вступить в силу. Мы добавим API, чтобы в какой-то момент вы могли сделать что-то вроде price.divided(by: tick, rounding: .towardZero), что исключит это округление, но поведение truncatingRemainder точно соответствует назначению.

Вы действительно хотите иметь либодесятичный тип (также в списке дел) или масштабирование задачи до десяти, чтобы ваш делитель стал точным:

    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
...