Skip to content

Commit 78bb21c

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 17f150f commit 78bb21c

File tree

4 files changed

+145
-23
lines changed

4 files changed

+145
-23
lines changed

Foundation/Decimal.swift

Lines changed: 64 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,6 +226,66 @@ extension Decimal : Hashable, Comparable {
226226
return _isNegative != 0 ? -d : d
227227
}
228228

229+
// Return the low 64bits of the integer part
230+
private var _unsignedInt64Value: UInt64 {
231+
if _exponent < -20 || _exponent > 20 {
232+
return 0
233+
}
234+
235+
if _length == 0 || isZero || magnitude < Decimal(0) {
236+
return 0
237+
}
238+
239+
var copy = self.significand
240+
241+
if _exponent < 0 {
242+
for _ in _exponent..<0 {
243+
_ = divideByShort(&copy, 10)
244+
}
245+
} else if _exponent > 0 {
246+
for _ in 0..<_exponent {
247+
_ = multiplyByShort(&copy, 10)
248+
}
249+
}
250+
let uint64 = UInt64(copy._mantissa.3) << 48 | UInt64(copy._mantissa.2) << 32 | UInt64(copy._mantissa.1) << 16 | UInt64(copy._mantissa.0)
251+
return uint64
252+
}
253+
254+
// Perform a best effort conversion of the integer value, matching Darwin for
255+
// values outside of UInt64.min .. UInt64.max. Used by NSDecimalNumber.
256+
internal var uint64Value: UInt64 {
257+
let value = _unsignedInt64Value
258+
if !self.isNegative {
259+
return value
260+
}
261+
262+
if value == UInt64(Int64.max) + 1 {
263+
return UInt64(bitPattern: Int64.min)
264+
} else if value <= UInt64(Int64.max) {
265+
var value = Int64(value)
266+
value.negate()
267+
return UInt64(bitPattern: value)
268+
} else {
269+
return value
270+
}
271+
}
272+
273+
// Perform a best effort conversion of the integer value, matching Darwin for
274+
// values outside of Int64.min .. Int64.max. Used by NSDecimalNumber.
275+
internal var int64Value: Int64 {
276+
let uint64Value = _unsignedInt64Value
277+
if self.isNegative {
278+
if uint64Value == UInt64(Int64.max) + 1 {
279+
return Int64.min
280+
} else if uint64Value <= UInt64(Int64.max) {
281+
var value = Int64(uint64Value)
282+
value.negate()
283+
return value
284+
}
285+
}
286+
return Int64(bitPattern: uint64Value)
287+
}
288+
229289
public var hashValue: Int {
230290
return Int(bitPattern: __CFHashDouble(doubleValue))
231291
}
@@ -250,6 +310,7 @@ extension Decimal : Hashable, Comparable {
250310
var rhsVal = rhs
251311
return NSDecimalCompare(&lhsVal, &rhsVal) == .orderedSame
252312
}
313+
253314
public static func <(lhs: Decimal, rhs: Decimal) -> Bool {
254315
var lhsVal = lhs
255316
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

@@ -934,6 +934,10 @@ open class NSNumber : NSValue {
934934
}
935935

936936
open func compare(_ otherNumber: NSNumber) -> ComparisonResult {
937+
if otherNumber is NSDecimalNumber {
938+
return otherNumber.compare(NSDecimalNumber(decimal: self.decimalValue))
939+
}
940+
937941
switch (_cfNumberType(), otherNumber._cfNumberType()) {
938942
case (kCFNumberFloatType, _), (_, kCFNumberFloatType): fallthrough
939943
case (kCFNumberDoubleType, _), (_, kCFNumberDoubleType):

TestFoundation/TestDecimal.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ class TestDecimal: XCTestCase {
3131
("test_SimpleMultiplication", test_SimpleMultiplication),
3232
("test_SmallerNumbers", test_SmallerNumbers),
3333
("test_ZeroPower", test_ZeroPower),
34-
("test_doubleValue", test_doubleValue)
34+
("test_doubleValue", test_doubleValue),
35+
("test_NSDecimalNumberValues", test_NSDecimalNumberValues),
3536
]
3637
}
3738

@@ -797,4 +798,26 @@ class TestDecimal: XCTestCase {
797798
XCTAssertEqual(nf.string(from: NSDecimalNumber(decimal: a)), "0.00")
798799
XCTAssertEqual(nf.string(from: NSDecimalNumber(decimal: b)), "0.00")
799800
}
801+
802+
func test_NSDecimalNumberValues() {
803+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).int8Value, -1)
804+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).int16Value, -1)
805+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).int32Value, -1)
806+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).int64Value, -1)
807+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).intValue, -1)
808+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-1")!).uint64Value, UInt64.max)
809+
810+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).int8Value, -128)
811+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).int16Value, -128)
812+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).int32Value, -128)
813+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).int64Value, -128)
814+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).intValue, -128)
815+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-128")!).uint64Value, 18446744073709551488)
816+
817+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "9223372036854775807")!).intValue, Int.max)
818+
XCTAssertEqual(NSDecimalNumber(decimal: Decimal(string: "-9223372036854775808")!).intValue, Int.min)
819+
820+
let nsd = NSDecimalNumber(decimal: Decimal(string: "-9223372036854775808")!)
821+
XCTAssertEqual(nsd, NSNumber(value: nsd.int64Value))
822+
}
800823
}

0 commit comments

Comments
 (0)