NSDecimalNumber 到 UInt64 的转换问题

Posted

技术标签:

【中文标题】NSDecimalNumber 到 UInt64 的转换问题【英文标题】:NSDecimalNumber to UInt64 conversion issue 【发布时间】:2018-09-20 16:56:50 【问题描述】:

我已经实现了以下功能:

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

【问题讨论】:

我认为有一些关于 Swift 中的 Decimal 和/或 NSDecimalNumber 的错误报告,我会看看是否可以找到参考。 证明存在问题的简短示例:NSDecimalNumber(string: "31.54117647058823529").uint64Value 返回 31,但 NSDecimalNumber(string: "31.541176470588235294").uint64Value 返回 13 (!),NSDecimalNumber(string: "31.5411764705882352941").uint64Value 返回 0。 见bugs.swift.org/browse/SR-2980 - 它被标记为“已解决”,但我仍然可以用 Xcode 10 / Swift 4.2 重现该问题 【参考方案1】:

uint64ValueNSNumber 的一个属性,应用到NSDecimalNumber 时没有很好的记录。

但据我测试,当尾数部分超出UInt64 的范围时,它返回0。例如31.5411764705882352941176470588235294116NSDecimalNumber中表示为315411764705882352941176470588235294116×10^(-38),而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) 实际行为似乎有点复杂,请参阅上面 Martin R 评论中的短示例

(*2) 这种行为绝对是一个错误,一旦标记为已解决。另见上述 Martin R 的另一条评论。

【讨论】:

这可能会“解释”为什么 NSDecimalNumber(string: "31.541176470588235294").uint64Value 返回 13 ... (31541176470588235294 % 2^64 = 13094432396878683678)。 – 我想知道为什么这个错误(见我上面的评论)被标记为已解决。 @MartinR,错误报告中描述的行为肯定是在 Xcode 10 中找到的。这可能是某种回归,但我不确定为什么会发生这样的事情。无论如何,我们可能需要再次编写相同的错误报告。【参考方案2】:

这是因为 Decimal 值不是整数,& unint64Value 无法处理。你总是可以只使用原语 -

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

这会在操场上返回正确的结果 (31)

【讨论】:

以上是关于NSDecimalNumber 到 UInt64 的转换问题的主要内容,如果未能解决你的问题,请参考以下文章

NSDecimalNumber 返回错误的 uint64_t 值

NSDecimalNumber 和大的无符号长长(64 位)整数

Cardinal 到 OleVariant 的错误转换。 UInt64 没问题

从 uint8_t* 到 uint32_t 的无效转换 - 从 32 位架构迁移到 64 位架构时?

从uint32_t [16]数组到uint32_t变量序列的64位副本

_mm256_movemask_epi8 到 uint64_t