Строковое кодирование возвращает неправильные значения. 33.48 становится 33.47999999999999488

Я пытаюсь создать хэш объекта give после его быстрого преобразования в строку, но значения, закодированные в строке, отличаются.

        print(myObjectValues.v) // 33.48
        let mydata = try JSONEncoder().encode(myObjectValues)
        let string = String(data: mydata, encoding: .utf8)!
        print(string) // 33.47999999999999488

Здесь в myObjectValues ​​содержится десятичное значение 33,48. Если я попытаюсь закодировать эти mydata в строку, возвращаемое значение будет 33.47999999999999488. Я пытался округлить десятичное значение до двух знаков, но последняя строка продолжает размещать это число. Я попытался сохранить его в String, а затем обратно в Decimal, но все же значение, возвращаемое в закодированной строке, составляет 33,479999999ish.

Я не могу использовать эту строку для вычисления и сравнения хэшей, так как хеш-значение, возвращаемое сервером, соответствует 33,48, что никогда не будет равно тому, что я получу с этим длинным значением.


person Joe    schedule 24.06.2021    source источник
comment
Это не десятичное значение. Это значение типа Double, потому что с ним работает JSONEncoder. См. stackoverflow.com/questions/588004/ Если вы ищете кодировщик JSON, который может работать напрямую с десятичными значениями (а у вас действительно есть десятичные значения), вам понадобится что-то особенное. См. github.com/rnapier/RNJSON, где находится находящийся в стадии разработки пример того, как вы подойдете к этому.   -  person Rob Napier    schedule 25.06.2021
comment
спасибо и да, я знаю, что это двойное значение, и я конвертирую это двойное в десятичное значение. и даже до кодирования я попытался преобразовать его в строковое значение. но все возвращается так же. При работе на android с такими проблемами не сталкивался. Это какая-то ошибка с JSONEncoder в быстром?   -  person Joe    schedule 25.06.2021
comment
Можем ли мы увидеть код, который должен был округлить его до двух цифр? Он не округлится. (Обратите внимание, что вы не можете округлить его на месте. Вы должны округлить его и преобразовать в строку на том же шаге.)   -  person David Schwartz    schedule 25.06.2021
comment
Использование десятичного значения в качестве хеша - плохая идея, в общем случае не следует полагаться на равенство десятичных значений. Использование Int или String имело бы больше смысла.   -  person EmilioPelaez    schedule 25.06.2021
comment
в конце мы используем сторонний сервис, который принимает только значения Double. поэтому о преобразовании его в строку не может быть и речи, а значения имеют десятичные точки, поэтому int здесь не будет работать, так как мы потеряем десятичные точки.   -  person Joe    schedule 25.06.2021
comment
Python (в предыдущем выпуске) изменил способ преобразования числа с плавающей запятой в строку, чтобы предпочесть наиболее компактную нотацию (с точно таким же двоичным представлением). Вы можете проверить примечания к выпуску Python, чтобы найти алгоритм, и просто реализовать его самостоятельно. (в конце вам нужно стабильное представление, поэтому может быть достаточно обхода, например, строка для плавания в строку)   -  person Giacomo Catenazzi    schedule 25.06.2021
comment
Я конвертирую это двойное в десятичное значение. Я предполагаю, что вы делаете что-то вроде Decimal(value). Это просто закодирует двоичное округленное значение (чего вы пытаетесь избежать). Decimal(33.48) - 33,47999999999999488, потому что это значение Double, которое вы передали в Decimal.init. Если вы хотите создать Decimal с десятичным округлением, а не с двоичным округлением, вам нужно создать его из строки: Decimal("33.48")!. Если вы это закодируете, все будет работать так, как вы ожидаете.   -  person Rob Napier    schedule 25.06.2021


Ответы (2)


Decimal значения, созданные с базовыми Double значениями, всегда будут создавать эти проблемы.

Decimal значения, созданные с помощью базовых String значений, не вызовут этих проблем.

Что вы можете попробовать сделать, так это -

  1. Имейте частное значение String в качестве резервного хранилища, которое существует только для безопасного кодирования и декодирования этого десятичного значения.
  2. Предоставьте другое вычисленное значение Decimal, которое использует это базовое значение String.
import Foundation

class Test: Codable {
    // Not exposed : Only for encoding & decoding
    private var decimalString: String = "33.48"
    
    // Work with this in your app
    var decimal: Decimal { 
        get { 
            Decimal(string: decimalString) ?? .zero
        }
        set { 
            decimal = newValue 
            decimalString = "\(newValue)"
        }
    }
}

do {
    let encoded = try JSONEncoder().encode(Test())
    print(String(data: encoded, encoding: .utf8))
    // Optional("{\"decimalString\":\"33.48\"}")
    
    let decoded = try JSONDecoder().decode(Test.self, from: encoded)
    
    print(decoded.decimal) // 33.48
    print(decoded.decimal.nextUp) // 33.49
    print(decoded.decimal.nextDown) // 33.47
} catch {
    print(error)
}
person Tarun Tyagi    schedule 24.06.2021
comment
Я попробую это сейчас и обновлю - person Joe; 25.06.2021
comment
Ооо, это похоже на прекрасную возможность для обертки собственности. Тот, который кодируется с использованием строк, но хранит и представляет десятичные значения - person Alexander; 25.06.2021

Я пытаюсь создать хэш объекта give после его быстрого преобразования в строку, но значения, закодированные в строке, отличаются.

Не делай этого. Только не надо. Это неразумно.

Я объясню это по аналогии: представьте, что вы представляете числа с шестью десятичными знаками точности. Вы должны использовать некоторую точность, верно?

Теперь 1/3 будет представлена ​​как 0.333333. Но 2/3 будут представлены 0.666667. Итак, теперь, если вы умножите 1/3 на два, вы не получите 2/3. И если вы прибавите от 1/3 к 1/3 до 1/3, вы не получите 1.

Таким образом, хэш 1 будет отличаться в зависимости от того, как вы его получили! Если вы добавите от 1/3 к 1/3 до 1/3, вы получите другой хеш, чем если бы вы добавили 2/3 к 1/3.

Это просто неподходящий тип данных для хеширования. Не используйте для этой цели двойники. Округление будет работать, пока этого не произойдет.

И вы используете 33 + 48/100 - значение, которое не может быть представлено точно по основанию два, так же как 1/3 не может быть представлено точно по основанию десять.

person David Schwartz    schedule 24.06.2021
comment
да, это блестящий момент, и я действительно не решаюсь использовать хеширование с такими значениями, так как шансов, что значения будут представлены в разных форматах, невозможно избежать. Я готов изменить процесс проверки моих объектов. Если вы можете предложить другие способы включения этого типа значений. - person Joe; 25.06.2021
comment
@Joe Ну, а что это за ценности? Это целые числа сотых? Если значения не являются точными и дискретными, не используйте эти типы значений в приложениях, требующих хеширования. Данные, которые не имеют одного и только одного правильного представления, не должны хешироваться, точка. Есть ли способ изменить дизайн так, чтобы было одно правильное представление? - person David Schwartz; 25.06.2021
comment
Это своего рода объект корзины покупок с разными типами значений, включая строки и двойные / десятичные значения. и перед тем, как перейти к оформлению заказа, нам нужно проверить, закален он или нет. и обычно темперирование выполняется с помощью цен, поэтому эти двойные / десятичные значения важны для хеширования. Мы конвертируем все двойные и десятичные точки в два разряда, чтобы они были как xx.xx, но после кодирования значения меняются. - person Joe; 25.06.2021
comment
@Joe Store и представьте их как целые числа пенни. - person David Schwartz; 25.06.2021