У меня есть реализация функции:

func number(for cof: UInt8, limit: UInt64) -> UInt64 {
    let decimalResult = (1 + Decimal(cof)/255) * Decimal(limit) / 1000
    return NSDecimalNumber(decimal: decimalResult).uint64Value
}

После использования этой функции с 0 cof:

number(for: 0, gasLimit: 21000) // Result is 21

Но когда я вызываю функцию с другим значением cof.

number(for: 128, gasLimit: 21000) // Result is 0 

После отладки для значения 128 cof.

Я узнал что

 let decimalResult = (1 + Decimal(gasPriceCoef)/255) * Decimal(gasLimit) / 1000 // Result is 31.5411764705882352941176470588235294116

И проблема в том, что когда я конвертирую Decimal значение в UInt64, я получаю 0

0
Oleg Gordiichuk 20 Сен 2018 в 19:56

2 ответа

Лучший ответ

uint64Value является свойством NSNumber, и его применение к NSDecimalNumber плохо документировано.

Но насколько я тестировал, когда часть мантиссы превышает диапазон UInt64, она возвращает 0. Например, 31.5411764705882352941176470588235294116 представлен как 315411764705882352941176470588235294116 × 10 ^ (- 38) в NSDecimalNumber, а 315411764705882352941176470588235294116 больше, чем UInt64.max. ( * 1 )

Чтобы избежать такого поведения (* 2), вы можете округлить десятичное значение перед преобразованием его в UInt64.

func number(for cof: UInt8, limit: UInt64) -> UInt64 {
    var decimalResult = (1 + Decimal(cof)/255) * Decimal(limit) / 1000
    var roundedResult = Decimal()
    NSDecimalRound(&roundedResult, &decimalResult, 0, NSDecimalNumber.RoundingMode.plain)
    return NSDecimalNumber(decimal:  roundedResult).uint64Value
}
print(number(for: 128, limit: 21000)) //->32

(* 1) Фактическое поведение кажется немного более сложным, см. короткие примеры в комментарии Мартина Р. выше.

(* 2) Такое поведение определенно является ошибкой, когда-то помеченной как РЕШЕННАЯ. См. Также другой комментарий Мартина Р. выше.

3
OOPer 20 Сен 2018 в 18:12

Это потому, что значение Decimal не является целым числом, и unint64Value с этим работать не будет. Вы всегда можете просто использовать примитивы -

let result = (1 + Double(cof) / 255) * Double(limit) / 1000
return UInt64(result)

Это возвращает правильный результат (31) на детской площадке

1
SomaMan 20 Сен 2018 в 17:14