2016-03-31 18 views
10

Tôi đã tình cờ gặp một hành vi NSDecimalNumber lẻ: đối với một số giá trị, lời gọi integerValue, longValue, longLongValue, v.v. Ví dụ:Tích cực NSDecimalNumber trả về giá trị số nguyên 64 bit không mong muốn

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 

Điều này đang sử dụng XCode 7.3; Tôi chưa thử nghiệm sử dụng các phiên bản trước đó của các khung công tác.

Tôi đã nhìn thấy một loạt các cuộc thảo luận về hành vi làm tròn không mong muốn với NSDecimalNumber, cũng như admonishments không khởi tạo nó với kế thừa NSNumber initializers, nhưng tôi đã không nhìn thấy bất cứ điều gì về hành vi cụ thể này. Tuy nhiên, có một số cuộc thảo luận khá chi tiết về đại diện nội bộ và làm tròn có thể chứa nugget tôi tìm kiếm, vì vậy xin lỗi trước nếu tôi bỏ lỡ nó.

CHỈNH SỬA: Nó được đưa vào nhận xét nhưng tôi đã gửi vấn đề này là vấn đề # 25465729 với Apple. OpenRadar: http://www.openradar.me/radar?id=5007005597040640.

CHỈNH SỬA 2: Apple đã đánh dấu phần này là dấu gạch ngang # 19812966.

+0

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

+0

Cảm ơn @nhgrif; Tôi đã cập nhật tiêu đề/nội dung để phản ánh điều này. –

+0

[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 " –

Trả lời

0

Tôi sẽ gửi lỗi cho Apple nếu tôi là bạn. Các docs nói rằng NSDecimalNumber có thể đại diện cho bất kỳ giá trị lên đến 38 chữ số dài. NSDecimalNumber thừa kế các thuộc tính đó từ NSNumber và các tài liệu không nói rõ ràng chuyển đổi nào có liên quan tại điểm đó, nhưng cách giải thích hợp lý duy nhất là nếu số có thể thay đổi và biểu diễn dưới dạng Int, thì bạn sẽ có câu trả lời đúng.

Dường như với tôi như một lỗi trong việc xử lý tiện ích mở rộng dấu hiệu trong khi chuyển đổi ở đâu đó, vì intValue là 32-bit và integerValue là 64-bit (trong Swift).

+0

Yup, đó là điều tôi nghi ngờ. Lưu thành vấn đề # 25465729. –

0

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.

Các vấn đề liên quan