Skip to content

Commit af3c20e

Browse files
committed
Decimal and NSDecimalNumber fixes.
- NSDecimalNumber int/uint getters: Dont convert the Decimal value using the doubleValue as this can lose the lower digits for large integers which cant be accuately represented as a Double. - NSDecimalNumber.isEqual: Dont use as? to cast every value to an NSDecimalNumber but test individual types. - NSNumber.compare: If the other value is a NSDecimalNumber, upgrade self to an NSDecimalNumber to avoid loss of precison in the other value.
1 parent 3c95c9c commit af3c20e

File tree

4 files changed

+146
-22
lines changed

4 files changed

+146
-22
lines changed

Foundation/Decimal.swift

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// This source file is part of the Swift.org open source project
22
//
3-
// Copyright (c) 2016 Apple Inc. and the Swift project authors
3+
// Copyright (c) 2016 - 2018 Apple Inc. and the Swift project authors
44
// Licensed under Apache License v2.0 with Runtime Library Exception
55
//
6-
// See http://swift.org/LICENSE.txt for license information
7-
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
6+
// See https://swift.org/LICENSE.txt for license information
7+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
88
//
99

1010
import CoreFoundation
@@ -226,9 +226,71 @@ extension Decimal : Hashable, Comparable {
226226
}
227227
return _isNegative != 0 ? -d : d
228228
}
229+
230+
// Return the low 64bits of the integer part
231+
private var _unsignedInt64Value: UInt64 {
232+
if _exponent < -20 || _exponent > 20 {
233+
return 0
234+
}
235+
236+
if _length == 0 || isZero || magnitude < Decimal(0) {
237+
return 0
238+
}
239+
240+
var copy = self.significand
241+
242+
if _exponent < 0 {
243+
for _ in _exponent..<0 {
244+
_ = divideByShort(&copy, 10)
245+
}
246+
} else if _exponent > 0 {
247+
for _ in 0..<_exponent {
248+
_ = multiplyByShort(&copy, 10)
249+
}
250+
}
251+
let uint64 = UInt64(copy._mantissa.3) << 48 | UInt64(copy._mantissa.2) << 32 | UInt64(copy._mantissa.1) << 16 | UInt64(copy._mantissa.0)
252+
return uint64
253+
}
254+
255+
// Perform a best effort conversion of the integer value, matching Darwin for
256+
// values outside of UInt64.min .. UInt64.max. Used by NSDecimalNumber.
257+
internal var uint64Value: UInt64 {
258+
let value = _unsignedInt64Value
259+
if !self.isNegative {
260+
return value
261+
}
262+
263+
if value == UInt64(Int64.max) + 1 {
264+
return UInt64(bitPattern: Int64.min)
265+
} else if value <= UInt64(Int64.max) {
266+
var value = Int64(value)
267+
value.negate()
268+
return UInt64(bitPattern: value)
269+
} else {
270+
return value
271+
}
272+
}
273+
274+
// Perform a best effort conversion of the integer value, matching Darwin for
275+
// values outside of Int64.min .. Int64.max. Used by NSDecimalNumber.
276+
internal var int64Value: Int64 {
277+
let uint64Value = _unsignedInt64Value
278+
if self.isNegative {
279+
if uint64Value == UInt64(Int64.max) + 1 {
280+
return Int64.min
281+
} else if uint64Value <= UInt64(Int64.max) {
282+
var value = Int64(uint64Value)
283+
value.negate()
284+
return value
285+
}
286+
}
287+
return Int64(bitPattern: uint64Value)
288+
}
289+
229290
public var hashValue: Int {
230291
return Int(bitPattern: __CFHashDouble(doubleValue))
231292
}
293+
232294
public static func ==(lhs: Decimal, rhs: Decimal) -> Bool {
233295
if lhs.isNaN {
234296
return rhs.isNaN
@@ -249,6 +311,7 @@ extension Decimal : Hashable, Comparable {
249311
var rhsVal = rhs
250312
return NSDecimalCompare(&lhsVal, &rhsVal) == .orderedSame
251313
}
314+
252315
public static func <(lhs: Decimal, rhs: Decimal) -> Bool {
253316
var lhsVal = lhs
254317
var rhsVal = rhs

Foundation/NSDecimalNumber.swift

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// This source file is part of the Swift.org open source project
22
//
3-
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
3+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
44
// Licensed under Apache License v2.0 with Runtime Library Exception
55
//
6-
// See http://swift.org/LICENSE.txt for license information
7-
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
6+
// See https://swift.org/LICENSE.txt for license information
7+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
88
//
99

1010
/*************** Exceptions ***********/
@@ -354,48 +354,74 @@ open class NSDecimalNumber : NSNumber {
354354
// return 'd' for double
355355

356356
open override var int8Value: Int8 {
357-
return Int8(exactly: decimal.doubleValue) ?? 0 as Int8
357+
return Int8(truncatingIfNeeded: decimal.int64Value)
358358
}
359+
359360
open override var uint8Value: UInt8 {
360-
return UInt8(exactly: decimal.doubleValue) ?? 0 as UInt8
361+
return UInt8(truncatingIfNeeded: decimal.uint64Value)
361362
}
363+
362364
open override var int16Value: Int16 {
363-
return Int16(exactly: decimal.doubleValue) ?? 0 as Int16
365+
return Int16(truncatingIfNeeded: decimal.int64Value)
364366
}
367+
365368
open override var uint16Value: UInt16 {
366-
return UInt16(exactly: decimal.doubleValue) ?? 0 as UInt16
369+
return UInt16(truncatingIfNeeded: decimal.uint64Value)
367370
}
371+
368372
open override var int32Value: Int32 {
369-
return Int32(exactly: decimal.doubleValue) ?? 0 as Int32
373+
return Int32(truncatingIfNeeded: decimal.int64Value)
370374
}
375+
371376
open override var uint32Value: UInt32 {
372-
return UInt32(exactly: decimal.doubleValue) ?? 0 as UInt32
377+
return UInt32(truncatingIfNeeded: decimal.uint64Value)
373378
}
379+
374380
open override var int64Value: Int64 {
375-
return Int64(exactly: decimal.doubleValue) ?? 0 as Int64
381+
return decimal.int64Value
376382
}
383+
377384
open override var uint64Value: UInt64 {
378-
return UInt64(exactly: decimal.doubleValue) ?? 0 as UInt64
385+
return decimal.uint64Value
379386
}
387+
380388
open override var floatValue: Float {
381389
return Float(decimal.doubleValue)
382390
}
391+
383392
open override var doubleValue: Double {
384393
return decimal.doubleValue
385394
}
395+
386396
open override var boolValue: Bool {
387397
return !decimal.isZero
388398
}
399+
389400
open override var intValue: Int {
390-
return Int(exactly: decimal.doubleValue) ?? 0 as Int
401+
return Int(truncatingIfNeeded: decimal.int64Value)
391402
}
403+
392404
open override var uintValue: UInt {
393-
return UInt(exactly: decimal.doubleValue) ?? 0 as UInt
405+
return UInt(truncatingIfNeeded: decimal.uint64Value)
394406
}
395407

396408
open override func isEqual(_ value: Any?) -> Bool {
397-
guard let other = value as? NSDecimalNumber else { return false }
398-
return self.decimal == other.decimal
409+
if value is NSDecimalNumber {
410+
return decimal.compare(to: (value as! NSDecimalNumber).decimal) == .orderedSame
411+
}
412+
else if value is NSNumber {
413+
return decimal.compare(to: (value as! NSNumber).decimalValue) == .orderedSame
414+
}
415+
switch value {
416+
case let other as Int:
417+
return intValue == other
418+
case let other as Double:
419+
return doubleValue == other
420+
case let other as Bool:
421+
return boolValue == other
422+
default:
423+
return false
424+
}
399425
}
400426

401427
override var _swiftValueOfOptimalType: Any {
@@ -501,7 +527,15 @@ extension NSNumber {
501527
if let d = self as? NSDecimalNumber {
502528
return d.decimal
503529
} else {
504-
return Decimal(self.doubleValue)
530+
let type = self.objCType.pointee
531+
if type == 0x64 || type == 0x66 {
532+
return Decimal(self.doubleValue)
533+
}
534+
else if type == 0x51 {
535+
return Decimal(uint64Value)
536+
} else {
537+
return Decimal(int64Value)
538+
}
505539
}
506540
}
507541
}

Foundation/NSNumber.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
// This source file is part of the Swift.org open source project
22
//
3-
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
3+
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
44
// Licensed under Apache License v2.0 with Runtime Library Exception
55
//
6-
// See http://swift.org/LICENSE.txt for license information
7-
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
6+
// See https://swift.org/LICENSE.txt for license information
7+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
88
//
99

1010

@@ -931,6 +931,10 @@ open class NSNumber : NSValue {
931931
}
932932

933933
open func compare(_ otherNumber: NSNumber) -> ComparisonResult {
934+
if otherNumber is NSDecimalNumber {
935+
return otherNumber.compare(NSDecimalNumber(decimal: self.decimalValue))
936+
}
937+
934938
switch (_cfNumberType(), otherNumber._cfNumberType()) {
935939
case (kCFNumberFloatType, _), (_, kCFNumberFloatType): fallthrough
936940
case (kCFNumberDoubleType, _), (_, kCFNumberDoubleType):

TestFoundation/TestDecimal.swift

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class TestDecimal: XCTestCase {
3131
("test_SimpleMultiplication", test_SimpleMultiplication),
3232
("test_SmallerNumbers", test_SmallerNumbers),
3333
("test_ZeroPower", test_ZeroPower),
34+
("test_NSDecimalNumberValues", test_NSDecimalNumberValues),
3435
]
3536
}
3637

@@ -712,4 +713,26 @@ class TestDecimal: XCTestCase {
712713
XCTAssertEqual(1, negativeSix.raising(toPower: 0))
713714
}
714715

716+
func test_NSDecimalNumberValues() {
717+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).int8Value, -1)
718+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).int16Value, -1)
719+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).int32Value, -1)
720+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).int64Value, -1)
721+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).intValue, -1)
722+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).uint64Value, UInt64.max)
723+
724+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).int8Value, -128)
725+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).int16Value, -128)
726+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).int32Value, -128)
727+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).int64Value, -128)
728+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).intValue, -128)
729+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).uint64Value, 18446744073709551488)
730+
731+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "9223372036854775807")!).intValue, Int.max)
732+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-9223372036854775808")!).intValue, Int.min)
733+
734+
let nsd = NSDecimalNumber(decimal: Decimal(string: "-9223372036854775808")!)
735+
XCTAssertEqual(nsd, NSNumber(value: nsd.int64Value))
736+
}
737+
715738
}

0 commit comments

Comments
 (0)