2016-03-31 8 views
10

मैं एक अजीब NSDecimalNumber व्यवहार पर ठोकर खाई गए रिटर्न: कुछ मूल्यों के लिए, integerValue, longValue, longLongValue का आमंत्रण, आदि, एक अप्रत्याशित मान। उदाहरण:सकारात्मक NSDecimalNumber अप्रत्याशित 64-बिट पूर्णांक मान

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 

यह एक्सकोड 7.3 का उपयोग कर रहा है; मैंने ढांचे के पुराने संस्करणों का उपयोग करके परीक्षण नहीं किया है।

मैं NSDecimalNumber साथ अप्रत्याशित राउंडिंग व्यवहार के बारे में चर्चा का एक समूह देखा है, साथ ही admonishments विरासत में मिला NSNumber initializers के साथ प्रारंभ करने में नहीं के रूप में है, लेकिन मैं इस विशिष्ट व्यवहार के बारे में कुछ भी नहीं देखा है। फिर भी आंतरिक प्रतिनिधित्व और गोल करने के बारे में कुछ विस्तृत चर्चाएं हैं जिनमें मुझे पता चलने वाली गड़गड़ाहट हो सकती है, इसलिए यदि मैं इसे याद करता हूं तो अग्रिम माफ़ी।

संपादित करें: इसे टिप्पणियों में दफनाया गया है, लेकिन मैंने इसे ऐप्पल के साथ # 25465729 के रूप में दायर किया है। ओपनराडर: http://www.openradar.me/radar?id=5007005597040640

संपादित करें 2: ऐप्पल ने इसे # 19812 9 66 की डुप्ली के रूप में चिह्नित किया है।

+0

यदि यह प्रासंगिक है, तो इन मानों का हेक्स प्रतिनिधित्व '0xFFFFFFF8' और' 0x9' है। या बाइनरी में, '1111 1111 1111 1111 1111 1111 1111 1000' और '1001'। – nhgrif

+0

धन्यवाद @nhgrif; मैंने इसे प्रतिबिंबित करने के लिए शीर्षक/शरीर को अद्यतन किया है। –

+0

[संदर्भ] (एनएसएनंबर' के लिए https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/) में एक चेतावनी शामिल है: "क्योंकि संख्यात्मक प्रकारों में अलग-अलग स्टोरेज क्षमताएं होती हैं , एक प्रकार के मान के साथ प्रारंभ करने और किसी अन्य प्रकार के मान तक पहुंचने का प्रयास करने से एक गलत परिणाम उत्पन्न हो सकता है " –

उत्तर

0

यदि मैं आप थे तो मैं ऐप्पल के साथ एक बग फाइल करूंगा। docs कहता है कि NSDecimalNumber 38 अंकों तक किसी भी मान का प्रतिनिधित्व कर सकता है। NSDecimalNumber NSNumber से उन गुणों को विरासत में लेता है, और दस्तावेज़ स्पष्ट रूप से नहीं कहते हैं कि उस बिंदु पर कौन सा रूपांतरण शामिल है, लेकिन केवल उचित व्याख्या यह है कि यदि संख्या एक इंट के रूप में गोल करने योग्य और प्रतिनिधित्व योग्य है, तो आपको सही उत्तर मिल जाएगा।

यह मुझे रूपांतरण के दौरान साइन-एक्सटेंशन को संभालने में एक बग की तरह दिखता है, क्योंकि intValue 32-बिट है और पूर्णांक वैल्यू 64-बिट (स्विफ्ट में) है।

+0

यूप, यही मुझे संदेह है। समस्या # 25465729 के रूप में दायर की। –

0

के बाद से आप पहले से ही समस्या पता है "बहुत उच्च परिशुद्धता" की वजह से है, तो आपको पहले दशमलव संख्या गोलाई से यह वैकल्पिक हल कर सकते हैं:

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 = 34,892 × 65536 + × 65536 + × 65536 + ।

अब 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 के लिए कम हो जाता है, जिसका अर्थ है अनुगामी शून्य फाउंडेशन द्वारा छोड़ा गया है।


आंतरिक संरचना को जानने का, मैं अब एक दावा कर: बग क्योंकि 34,892 ≥ 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 = 2 सिर्फ एक पर हस्ताक्षर किए 64-बिट संख्या से ऊपर जाने का पर्याप्त मूल्य है। इसलिए, मुझे संदेह है कि बग फाउंडेशन के कारण int64Value को लागू करने के कारण है (1) मंटिसा को सीधे Int64 में परिवर्तित करें, और फिर (2) 10 द्वारा विभाजित करें | एक्सपोनेंट |

वास्तव में, यदि आप फाउंडेशन.फ्रेमवर्क को अलग करते हैं, तो आप पाएंगे कि यह मूल रूप से int64Value लागू किया गया है (यह प्लेटफ़ॉर्म की सूचक चौड़ाई से स्वतंत्र है)।

लेकिन int32Value क्यों प्रभावित नहीं है? क्योंकि आंतरिक रूप से इसे अभी Int32(self.doubleValue) के रूप में कार्यान्वित किया गया है, इसलिए कोई ओवरफ़्लो समस्या नहीं होगी। दुर्भाग्य से एक डबल में केवल 53 बिट सटीक हैं, इसलिए ऐप्पल के पास int64Value (परिशुद्धता के 64 बिट्स की आवश्यकता होती है) को फ्लोटिंग-पॉइंट अंकगणित के बिना लागू करने के अलावा कोई विकल्प नहीं है।

संबंधित मुद्दे