diff --git a/Foundation/Decimal.swift b/Foundation/Decimal.swift index 0a5fe7a11d..46a3e774d8 100644 --- a/Foundation/Decimal.swift +++ b/Foundation/Decimal.swift @@ -1,10 +1,10 @@ // This source file is part of the Swift.org open source project // -// Copyright (c) 2016 Apple Inc. and the Swift project authors +// Copyright (c) 2016 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // -// See http://swift.org/LICENSE.txt for license information -// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // import CoreFoundation @@ -226,6 +226,66 @@ extension Decimal : Hashable, Comparable { return _isNegative != 0 ? -d : d } + // Return the low 64bits of the integer part + private var _unsignedInt64Value: UInt64 { + if _exponent < -20 || _exponent > 20 { + return 0 + } + + if _length == 0 || isZero || magnitude < Decimal(0) { + return 0 + } + + var copy = self.significand + + if _exponent < 0 { + for _ in _exponent..<0 { + _ = divideByShort(©, 10) + } + } else if _exponent > 0 { + for _ in 0..<_exponent { + _ = multiplyByShort(©, 10) + } + } + let uint64 = UInt64(copy._mantissa.3) << 48 | UInt64(copy._mantissa.2) << 32 | UInt64(copy._mantissa.1) << 16 | UInt64(copy._mantissa.0) + return uint64 + } + + // Perform a best effort conversion of the integer value, matching Darwin for + // values outside of UInt64.min .. UInt64.max. Used by NSDecimalNumber. + internal var uint64Value: UInt64 { + let value = _unsignedInt64Value + if !self.isNegative { + return value + } + + if value == UInt64(Int64.max) + 1 { + return UInt64(bitPattern: Int64.min) + } else if value <= UInt64(Int64.max) { + var value = Int64(value) + value.negate() + return UInt64(bitPattern: value) + } else { + return value + } + } + + // Perform a best effort conversion of the integer value, matching Darwin for + // values outside of Int64.min .. Int64.max. Used by NSDecimalNumber. + internal var int64Value: Int64 { + let uint64Value = _unsignedInt64Value + if self.isNegative { + if uint64Value == UInt64(Int64.max) + 1 { + return Int64.min + } else if uint64Value <= UInt64(Int64.max) { + var value = Int64(uint64Value) + value.negate() + return value + } + } + return Int64(bitPattern: uint64Value) + } + public var hashValue: Int { return Int(bitPattern: __CFHashDouble(doubleValue)) } @@ -250,6 +310,7 @@ extension Decimal : Hashable, Comparable { var rhsVal = rhs return NSDecimalCompare(&lhsVal, &rhsVal) == .orderedSame } + public static func <(lhs: Decimal, rhs: Decimal) -> Bool { var lhsVal = lhs var rhsVal = rhs diff --git a/Foundation/NSDecimalNumber.swift b/Foundation/NSDecimalNumber.swift index 68ce7fc81b..29652509ef 100644 --- a/Foundation/NSDecimalNumber.swift +++ b/Foundation/NSDecimalNumber.swift @@ -1,10 +1,10 @@ // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // -// See http://swift.org/LICENSE.txt for license information -// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // /*************** Exceptions ***********/ @@ -354,48 +354,74 @@ open class NSDecimalNumber : NSNumber { // return 'd' for double open override var int8Value: Int8 { - return Int8(exactly: decimal.doubleValue) ?? 0 as Int8 + return Int8(truncatingIfNeeded: decimal.int64Value) } + open override var uint8Value: UInt8 { - return UInt8(exactly: decimal.doubleValue) ?? 0 as UInt8 + return UInt8(truncatingIfNeeded: decimal.uint64Value) } + open override var int16Value: Int16 { - return Int16(exactly: decimal.doubleValue) ?? 0 as Int16 + return Int16(truncatingIfNeeded: decimal.int64Value) } + open override var uint16Value: UInt16 { - return UInt16(exactly: decimal.doubleValue) ?? 0 as UInt16 + return UInt16(truncatingIfNeeded: decimal.uint64Value) } + open override var int32Value: Int32 { - return Int32(exactly: decimal.doubleValue) ?? 0 as Int32 + return Int32(truncatingIfNeeded: decimal.int64Value) } + open override var uint32Value: UInt32 { - return UInt32(exactly: decimal.doubleValue) ?? 0 as UInt32 + return UInt32(truncatingIfNeeded: decimal.uint64Value) } + open override var int64Value: Int64 { - return Int64(exactly: decimal.doubleValue) ?? 0 as Int64 + return decimal.int64Value } + open override var uint64Value: UInt64 { - return UInt64(exactly: decimal.doubleValue) ?? 0 as UInt64 + return decimal.uint64Value } + open override var floatValue: Float { return Float(decimal.doubleValue) } + open override var doubleValue: Double { return decimal.doubleValue } + open override var boolValue: Bool { return !decimal.isZero } + open override var intValue: Int { - return Int(exactly: decimal.doubleValue) ?? 0 as Int + return Int(truncatingIfNeeded: decimal.int64Value) } + open override var uintValue: UInt { - return UInt(exactly: decimal.doubleValue) ?? 0 as UInt + return UInt(truncatingIfNeeded: decimal.uint64Value) } open override func isEqual(_ value: Any?) -> Bool { - guard let other = value as? NSDecimalNumber else { return false } - return self.decimal == other.decimal + if value is NSDecimalNumber { + return decimal.compare(to: (value as! NSDecimalNumber).decimal) == .orderedSame + } + else if value is NSNumber { + return decimal.compare(to: (value as! NSNumber).decimalValue) == .orderedSame + } + switch value { + case let other as Int: + return intValue == other + case let other as Double: + return doubleValue == other + case let other as Bool: + return boolValue == other + default: + return false + } } override var _swiftValueOfOptimalType: Any { @@ -501,7 +527,15 @@ extension NSNumber { if let d = self as? NSDecimalNumber { return d.decimal } else { - return Decimal(self.doubleValue) + let type = self.objCType.pointee + if type == 0x64 || type == 0x66 { + return Decimal(self.doubleValue) + } + else if type == 0x51 { + return Decimal(uint64Value) + } else { + return Decimal(int64Value) + } } } } diff --git a/Foundation/NSNumber.swift b/Foundation/NSNumber.swift index b693bbee5c..f0b45b0d5f 100644 --- a/Foundation/NSNumber.swift +++ b/Foundation/NSNumber.swift @@ -1,10 +1,10 @@ // This source file is part of the Swift.org open source project // -// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // -// See http://swift.org/LICENSE.txt for license information -// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // @@ -934,6 +934,10 @@ open class NSNumber : NSValue { } open func compare(_ otherNumber: NSNumber) -> ComparisonResult { + if otherNumber is NSDecimalNumber { + return otherNumber.compare(NSDecimalNumber(decimal: self.decimalValue)) + } + switch (_cfNumberType(), otherNumber._cfNumberType()) { case (kCFNumberFloatType, _), (_, kCFNumberFloatType): fallthrough case (kCFNumberDoubleType, _), (_, kCFNumberDoubleType): diff --git a/TestFoundation/TestDecimal.swift b/TestFoundation/TestDecimal.swift index 4a1312fbfc..e9912c4816 100644 --- a/TestFoundation/TestDecimal.swift +++ b/TestFoundation/TestDecimal.swift @@ -31,7 +31,8 @@ class TestDecimal: XCTestCase { ("test_SimpleMultiplication", test_SimpleMultiplication), ("test_SmallerNumbers", test_SmallerNumbers), ("test_ZeroPower", test_ZeroPower), - ("test_doubleValue", test_doubleValue) + ("test_doubleValue", test_doubleValue), + ("test_NSDecimalNumberValues", test_NSDecimalNumberValues), ] } @@ -797,4 +798,26 @@ class TestDecimal: XCTestCase { XCTAssertEqual(nf.string(from: NSDecimalNumber(decimal: a)), "0.00") XCTAssertEqual(nf.string(from: NSDecimalNumber(decimal: b)), "0.00") } + + func test_NSDecimalNumberValues() { + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).int8Value, -1) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).int16Value, -1) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).int32Value, -1) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).int64Value, -1) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).intValue, -1) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).uint64Value, UInt64.max) + + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).int8Value, -128) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).int16Value, -128) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).int32Value, -128) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).int64Value, -128) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).intValue, -128) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).uint64Value, 18446744073709551488) + + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "9223372036854775807")!).intValue, Int.max) + XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-9223372036854775808")!).intValue, Int.min) + + let nsd = NSDecimalNumber(decimal: Decimal(string: "-9223372036854775808")!) + XCTAssertEqual(nsd, NSNumber(value: nsd.int64Value)) + } }