Как разобрать десятичную из строки без потери точности? - PullRequest
2 голосов
/ 25 апреля 2019

Я имею дело с денежными суммами в моем приложении, поэтому точность, очевидно, очень важна, а десятичное число / NSDecimal должно быть способом быстрой обработки денег. Я хочу конвертировать между десятичным и строковым, сохраняя точность. (парсинг ответов API, сохранение в БД ...)

Это мое текущее решение:

private let dotLocaleDictionary = [NSLocale.Key.decimalSeparator: "."]

func apiStringToDecimal(_ string: String) -> Decimal? {
    let number = NSDecimalNumber(string: string, locale: dotLocaleDictionary) as Decimal
    return number.isNaN ? nil : number
}

func decimalToApiString(_ decimal: Decimal) -> String {
    return (decimal as NSDecimalNumber).description(withLocale: dotLocaleDictionary)
}

Словарь локали заботится о десятичном разделителе, и я ожидал, что инициализатор строки будет точным. Но во время игры на детской площадке я заметил, что после определенной длины входных строк результирующие числа обрезаются.

Я также пытался проанализировать строки с помощью средства форматирования чисел, но результаты были еще хуже.

Код моей детской площадки:

let small = "1.135160000500009000100020003000400050061111"
let big = "1234567890123456789000100020003000400061111"
let medium = "123456789123456789.0001000200030004000511111"
let negative = "-123456789123456789.0001000200030004000511111"
let numericStrings = [small, big, medium, negative]

numericStrings.forEach {
    print($0)
    guard let decimal = apiStringToDecimal($0) else {
        print("Failed to parse decimal from: \($0)")
        print("--------------------")
        return
    }

    print(decimal)
    let string = decimalToApiString(decimal)
    print($0 == string ? "match" : "mismatch")

    print("--------------------")
}

let max = Decimal.greatestFiniteMagnitude
print("MAX decimal: \(max)")
print((max as NSDecimalNumber).description(withLocale: dotLocaleDictionary))
print(decimalToApiString(max))

И вывод, который он производит:

1.135160000500009000100020003000400050061111
1.13516000050000900010002000300040005006
mismatch
--------------------
1234567890123456789000100020003000400061111
1234567890123456789000100020003000400060000
mismatch
--------------------
123456789123456789.0001000200030004000511111
123456789123456789.000100020003000400051
mismatch
--------------------
-123456789123456789.0001000200030004000511111
-123456789123456789.000100020003000400051
mismatch
--------------------
MAX decimal: 3402823669209384634633746074317682114550000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
3402823669209384634633746074317682114550000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
3402823669209384634633746074317682114550000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

Преобразование из десятичной в строку, кажется, работает нормально, проблема в строке в десятичную. Так как мои числа меньше максимального десятичного числа, я не думаю, что мои тестовые числа просто слишком велики. Я почти уверен, что исключил проблемы с печатью и запустил код на реальном устройстве, так что это не проблема игровой площадки.

Я что-то не так делаю? Есть ли лучший способ конвертировать между десятичным и строковым в Swift?

Или строки просто слишком длинные? (я не нашел никакой документации / обсуждения того, почему это не будет работать)

1 Ответ

3 голосов
/ 25 апреля 2019

С NSDecimalNumber

NSDecimalNumber, неизменяемый подкласс NSNumber, предоставляет объектно-ориентированную оболочку для выполнения арифметики с основанием 10.Экземпляр может представлять любое число, которое может быть выражено как экспонента мантиссы x 10 ^, где мантисса - десятичное целое число длиной до 38 цифр , а экспонента - целое число от –128 до 127.

(выделено мной)

Хотя NSDecimalNumber обеспечивает десятичную арифметику с большой точностью, точность имеет предел.

Возможно, вам нужна пользовательская библиотека, например BigNum .

...