Я наткнулся на нечетное поведение NSDecimalNumber: для некоторых значений вызовы integerValue
, longValue
, longLongValue
и т.д. возвращают неожиданное значение. Пример:
Это использование XCode 7.3; Я не тестировал более ранние версии фреймворков.
Ответ 2
Поскольку вы уже знаете, что проблема связана с "слишком высокой точностью", вы можете обходить ее путем округления десятичного числа:
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 внутренне представлен как Десятичная структура:
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 × 65536 3 + 46888 × 65536 2 + 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.
Зная внутреннюю структуру, я теперь заявляю: ошибка происходит потому, что 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 × 65536 3= 2 63 - это значение, достаточное для переполнения подписанного 64-разрядного номера. Поэтому я подозреваю, что ошибка связана с тем, что Foundation реализует int64Value
как (1) преобразовать мантисс непосредственно в Int64
, а затем (2) делить на 10 | экспонент |.
На самом деле, если вы разобьете Foundation.framework, вы обнаружите, что в основном реализовано int64Value
(это не зависит от ширины указателя платформы).
Но почему int32Value
не влияет? Потому что внутренне он реализован как Int32(self.doubleValue)
, поэтому проблема с переполнением не возникнет. К сожалению, двойной имеет только 53 бит точности, поэтому у Apple нет выбора, кроме как реализовать int64Value
(требующий 64 бита точности) без арифметики с плавающей запятой.