Функция быстрого смещения и округления - PullRequest
0 голосов
/ 14 мая 2019

Я делаю свою собственную специальную графическую структуру и у меня возникают проблемы с некоторыми простыми остатками операторов if.

Я бы хотел, чтобы ось X была логарифмической. Каждый раз, когда пользователь просматривает график, он меняет значения X-Min и X-Max на сумму translation.x.

У меня есть следующий код

for i in Int(xMin)...Int(xMax) {
    let logValue = Double(log10(Float(i)))
    let rounded = logValue.rounded(toPlaces:3)

    if rounded.truncatingRemainder(dividingBy: 0.1) == 0 {
        //Draw division line
        print("Got Here")
    }
}

extension Double {
    /// Rounds the double to decimal places value
    func rounded(toPlaces places:Int) -> Double {
        let divisor = pow(10.0, Double(places))
        return (self * divisor).rounded() / divisor
    }
}

Если я поставлю точки останова в своем коде, я могу видеть, что если округленное имеет значение 1,3, код никогда не достигнет строки печати. Если я вручную введу значение округления до 100, опять же, линия печати никогда не будет достигнута.

До сих пор у меня не было проблем с использованием оператора% remainder для целых чисел, однако я не могу понять, где я ошибся.

Ответы [ 2 ]

0 голосов
/ 14 мая 2019

Это происходит потому, что лежащие в основе вычисления являются двоичными, а не десятичными.

Мы можем использовать Десятичное число (начиная с Swift 3), чтобы добиться большего успеха.Вот быстрая и грязная мод-функция для иллюстрации:

// a mod b = a - b*floor(a/b)
func mod(_ a: Decimal,_ b: Decimal) -> Decimal {
    var d = a/(b)
    var f : Decimal = 0
    NSDecimalRound(&f, &d, 0, .down)
    return a-(b*(f))
}

С обычными вычислениями с плавающей точкой:

var i = 1.3
var j = 0.1
i.truncatingRemainder(dividingBy: j) // 0.09999999999999998

i = 1.3
j = 0.15
i.truncatingRemainder(dividingBy: j) // 0.1000000000000001

С десятичной дробью:

var a : Decimal = 1.3
var b : Decimal = 0.1
mod(a, b) // 0

a = 1.3
b = 0.15
mod(a, b) // 0.1

Нам все еще нужночтобы позаботиться:

var a : Decimal = 0.906 // 0.9060000000000002048 conversion to decimal issue

// Specify the significant digits directly
a = Decimal(sign: FloatingPointSign.plus, exponent: -3, significand: Decimal(906)) // 0.906
mod(a, Decimal(0.1)) // 0.006

Так что все же следует сделать осторожное округление и / или учесть небольшую погрешность "эпсилон".(Сравнение с точными числами, такими как 0, вероятно, не очень хорошая идея).

0 голосов
/ 14 мая 2019

Всегда помните, что числа с плавающей запятой на компьютерах грязные из-за всевозможных ошибок округления в пути.Многие мои знакомые программисты, в том числе и я, склонны думать, что деление на 10 (десятичное число) - это чистая операция.Проблема в том, что константа типа fx 0.1 переводится в округленное число в двоичной переменной с плавающей запятой.Более подробное объяснение см. В https://en.wikipedia.org/wiki/Round-off_error#Real_world_example:_Patriot_Missile_Failure_due_to_magnification_of_roundoff_error.

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

Если вы печатаетеРезультат вашего вызова truncatingRemainder выглядит следующим образом:

let rounded = logValue.rounded(toPlaces:3)

let remain = rounded.truncatingRemainder(dividingBy: 0.1)

print("rounded \(rounded)   remain \(remain)")

вы получаете fx

rounded 0.845   remain 0.04499999999999993
rounded 0.903   remain 0.002999999999999975
rounded 0.954   remain 0.05399999999999991
rounded 1.0   remain 0.09999999999999995
rounded 1.041   remain 0.04099999999999987
rounded 1.079   remain 0.0789999999999999
rounded 1.114   remain 0.01400000000000004

Обратите внимание, что даже чистая константа 1.0 имеет грязный результат для truncatingRemainder (dividingBy: 0.1).

...