Vì bạn đã biết vấn đề là do "độ chính xác quá cao", bạn có thể workaround nó bằng cách làm tròn số thập phân thứ nhất:
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
Nếu bạn muốn sử dụng int64Value
(tức -longLongValue
), tránh sử dụng các số có độ chính xác lớn hơn 62 bit, nghĩa là tránh có quá 18 chữ số hoàn toàn. Lý do giải thích dưới đây.
NSDecimalNumber được nội thể hiện dưới dạng một 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;
này có thể thu được bằng .decimalValue
, ví dụ
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
Điều này có nghĩa 9,821426272392280061 được lưu trữ như nội 9821426272392280061 × 10 -18 - lưu ý rằng 9821426272392280061 = 34.892 × 65536 + × 65536 + × 65536 + .
Bây giờ, hãy so sánh với 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
Lưu ý rằng số mũ được giảm xuống -17, có nghĩa là dấu 0 được bỏ qua bởi Foundation.
Biết được cấu trúc bên trong, bây giờ tôi làm cho một khẳng định: lỗi này là vì 34.892 ≥ 32.768. Quan sát:
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
Lưu ý rằng 32768 × 65536 = 2 là giá trị chỉ đủ để tràn một số 64-bit đã ký kết. Do đó, tôi nghi ngờ rằng lỗi này là do Tổ chức thực hiện int64Value
khi (1) chuyển đổi phần trực tiếp vào một số Int64
, và sau đó (2) chia cho 10 | số mũ |.
Thực tế, nếu bạn tháo rời Foundation.framework, bạn sẽ thấy rằng về cơ bản cách int64Value
được triển khai (điều này độc lập với chiều rộng con trỏ của nền tảng).
Nhưng tại sao int32Value
không bị ảnh hưởng? Bởi vì nội bộ nó chỉ được thực hiện như Int32(self.doubleValue)
, do đó, không có vấn đề tràn sẽ xảy ra. Thật không may là một đôi chỉ có 53 bit chính xác, do đó Apple không có lựa chọn nào khác ngoài việc thực hiện int64Value
(yêu cầu 64 bit chính xác) mà không có điểm số nổi.
Trong trường hợp nó có liên quan, các biểu diễn hex của các giá trị này là '0xFFFFFFF8' và' 0x9'. Hoặc dưới dạng nhị phân, '1111 1111 1111 1111 1111 1111 1111 1000' và '1001'. – nhgrif
Cảm ơn @nhgrif; Tôi đã cập nhật tiêu đề/nội dung để phản ánh điều này. –
[Tham chiếu] (https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/) đối với 'NSNumber' không bao gồm cảnh báo:" Vì các loại số có khả năng lưu trữ khác nhau , cố gắng khởi tạo với một giá trị của một loại và truy cập vào giá trị của một loại khác có thể tạo ra một kết quả sai lầm " –