gpt4 book ai didi

ios - 正 NSDecimalNumber 返回意外的 64 位整数值

转载 作者:IT王子 更新时间:2023-10-29 05:38:23 27 4
gpt4 key购买 nike

我偶然发现了一个奇怪的 NSDecimalNumber 行为:对于某些值,调用 integerValuelongValuelongLongValue 等,返回意想不到的值(value)。示例:

let v = NSDecimalNumber(string: "9.821426272392280061")
v // evaluates to 9.821426272392278
v.intValue // evaluates to 9
v.integerValue // evaluates to -8
v.longValue // evaluates to -8
v.longLongValue // evaluates to -8

let v2 = NSDecimalNumber(string: "9.821426272392280060")
v2 // evaluates to 9.821426272392278
v2.intValue // evaluates to 9
v2.integerValue // evaluates to 9
v2.longValue // evaluates to 9
v2.longLongValue // evaluates to 9

这是使用 XCode 7.3;我没有使用早期版本的框架进行测试。

我看过很多关于 NSDecimalNumber 的意外舍入行为的讨论,以及不要使用继承的 NSNumber 初始化器初始化它的警告,但我没有没有看到有关此特定行为的任何信息。不过, 有一些关于内部表示和舍入的相当详细的讨论,其中可能包含我要寻找的金 block ,所以如果我错过了,请提前致歉。

编辑:它隐藏在评论中,但我已将其作为问题 #25465729 提交给 Apple。打开雷达:http://www.openradar.me/radar?id=5007005597040640 .

编辑 2:Apple 已将其标记为 #19812966 的复制品。

最佳答案

由于您已经知道问题是由于“精度太高”造成的,因此您可以先将小数四舍五入来解决此问题:

let b = NSDecimalNumber(string: "9.999999999999999999")
print(b, "->", b.int64Value)
// 9.999999999999999999 -> -8

let truncateBehavior = NSDecimalNumberHandler(roundingMode: .down,
scale: 0,
raiseOnExactness: true,
raiseOnOverflow: true,
raiseOnUnderflow: true,
raiseOnDivideByZero: true)
let c = b.rounding(accordingToBehavior: truncateBehavior)
print(c, "->", c.int64Value)
// 9 -> 9

如果要使用 int64Value(即 -longLongValue),请避免使用精度超过 62 位的数字,即避免总共超过 18 位数字。原因解释如下。


NSDecimalNumber 在内部表示为 Decimal structure :

typedef struct {
signed int _exponent:8;
unsigned int _length:4;
unsigned int _isNegative:1;
unsigned int _isCompact:1;
unsigned int _reserved:18;
unsigned short _mantissa[NSDecimalMaxSize]; // NSDecimalMaxSize = 8
} NSDecimal;

这可以使用.decimalValue获得,例如

let v2 = NSDecimalNumber(string: "9.821426272392280061")
let d = v2.decimalValue
print(d._exponent, d._mantissa, d._length)
// -18 (30717, 39329, 46888, 34892, 0, 0, 0, 0) 4

这意味着 9.821426272392280061 在内部存储为 9821426272392280061 × 10-18 — 请注意 9821426272392280061 = 34892 × 655363 + 46888 × 655362 + 39329 × 65536 + 30717.

现在与 9.821426272392280060 比较:

let v2 = NSDecimalNumber(string: "9.821426272392280060")
let d = v2.decimalValue
print(d._exponent, d._mantissa, d._length)
// -17 (62054, 3932, 17796, 3489, 0, 0, 0, 0) 4

请注意,指数减少到 -17,这意味着 Foundation 省略了尾随零。


知道了内部结构,我现在声明:这个bug是因为34892 ≥ 32768。观察:

let a = NSDecimalNumber(decimal: Decimal(
_exponent: -18, _length: 4, _isNegative: 0, _isCompact: 1, _reserved: 0,
_mantissa: (65535, 65535, 65535, 32767, 0, 0, 0, 0)))
let b = NSDecimalNumber(decimal: Decimal(
_exponent: -18, _length: 4, _isNegative: 0, _isCompact: 1, _reserved: 0,
_mantissa: (0, 0, 0, 32768, 0, 0, 0, 0)))
print(a, "->", a.int64Value)
print(b, "->", b.int64Value)
// 9.223372036854775807 -> 9
// 9.223372036854775808 -> -9

请注意,32768 × 655363 = 263 的值刚好足以溢出一个带符号的 64 位数字。因此,我怀疑该错误是由于 Foundation 将 int64Value 实现为 (1) 将尾数直接转换为 Int64,然后 (2) 除以 10 |指数|

事实上,如果你反汇编Foundation.framework,你会发现它基本上就是int64Value的实现方式(这与平台的指针宽度无关)。

但为什么 int32Value 不受影响?因为它在内部只是实现为 Int32(self.doubleValue),所以不会发生溢出问题。不幸的是,double 只有 53 位精度,因此 Apple 别无选择,只能在没有浮点运算的情况下实现 int64Value(需要 64 位精度)。

关于ios - 正 NSDecimalNumber 返回意外的 64 位整数值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36322336/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com