Skip to content

Commit deb8292

Browse files
committed
Synchronize Decimal overlay and corelibs-foundation implementations.
1 parent bb8e7d2 commit deb8292

File tree

3 files changed

+204
-39
lines changed

3 files changed

+204
-39
lines changed

Darwin/Foundation-swiftoverlay-Tests/TestDecimal.swift

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,13 @@ class TestDecimal : XCTestCase {
386386
XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN e4")
387387
XCTAssertNotEqual(.noError, NSDecimalMultiplyByPowerOf10(&result, &NaN, 5, .plain))
388388
XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN e5")
389+
390+
XCTAssertFalse(Double(truncating: NSDecimalNumber(decimal: Decimal(0))).isNaN)
391+
XCTAssertTrue(Decimal(Double.leastNonzeroMagnitude).isNaN)
392+
XCTAssertTrue(Decimal(Double.leastNormalMagnitude).isNaN)
393+
XCTAssertTrue(Decimal(Double.greatestFiniteMagnitude).isNaN)
394+
XCTAssertTrue(Decimal(Double("1e-129")!).isNaN)
395+
XCTAssertTrue(Decimal(Double("0.1e-128")!).isNaN)
389396
}
390397

391398
func test_NegativeAndZeroMultiplication() {
@@ -597,4 +604,95 @@ class TestDecimal : XCTestCase {
597604
func test_unconditionallyBridgeFromObjectiveC() {
598605
XCTAssertEqual(Decimal(), Decimal._unconditionallyBridgeFromObjectiveC(nil))
599606
}
607+
608+
func test_parseDouble() throws {
609+
XCTAssertEqual(Decimal(Double(0.0)), Decimal(Int.zero))
610+
XCTAssertEqual(Decimal(Double(-0.0)), Decimal(Int.zero))
611+
612+
// These values can only be represented as Decimal.nan
613+
XCTAssertEqual(Decimal(Double.nan), Decimal.nan)
614+
XCTAssertEqual(Decimal(Double.signalingNaN), Decimal.nan)
615+
616+
// These values are out out range for Decimal
617+
XCTAssertEqual(Decimal(-Double.leastNonzeroMagnitude), Decimal.nan)
618+
XCTAssertEqual(Decimal(Double.leastNonzeroMagnitude), Decimal.nan)
619+
XCTAssertEqual(Decimal(-Double.leastNormalMagnitude), Decimal.nan)
620+
XCTAssertEqual(Decimal(Double.leastNormalMagnitude), Decimal.nan)
621+
XCTAssertEqual(Decimal(-Double.greatestFiniteMagnitude), Decimal.nan)
622+
XCTAssertEqual(Decimal(Double.greatestFiniteMagnitude), Decimal.nan)
623+
624+
// SR-13837
625+
let testDoubles: [(Double, String)] = [
626+
(1.8446744073709550E18, "1844674407370954752"),
627+
(1.8446744073709551E18, "1844674407370954752"),
628+
(1.8446744073709552E18, "1844674407370955264"),
629+
(1.8446744073709553E18, "1844674407370955264"),
630+
(1.8446744073709554E18, "1844674407370955520"),
631+
(1.8446744073709555E18, "1844674407370955520"),
632+
633+
(1.8446744073709550E19, "18446744073709547520"),
634+
(1.8446744073709551E19, "18446744073709552640"),
635+
(1.8446744073709552E19, "18446744073709552640"),
636+
(1.8446744073709553E19, "18446744073709552640"),
637+
(1.8446744073709554E19, "18446744073709555200"),
638+
(1.8446744073709555E19, "18446744073709555200"),
639+
640+
(1.8446744073709550E20, "184467440737095526400"),
641+
(1.8446744073709551E20, "184467440737095526400"),
642+
(1.8446744073709552E20, "184467440737095526400"),
643+
(1.8446744073709553E20, "184467440737095526400"),
644+
(1.8446744073709554E20, "184467440737095552000"),
645+
(1.8446744073709555E20, "184467440737095552000"),
646+
]
647+
648+
for (d, s) in testDoubles {
649+
XCTAssertEqual(Decimal(d), Decimal(string: s))
650+
XCTAssertEqual(Decimal(d).description, try XCTUnwrap(Decimal(string: s)).description)
651+
}
652+
}
653+
654+
func test_initExactly() {
655+
// This really requires some tests using a BinaryInteger of bitwidth > 128 to test failures.
656+
let d1 = Decimal(exactly: UInt64.max)
657+
XCTAssertNotNil(d1)
658+
XCTAssertEqual(d1?.description, UInt64.max.description)
659+
XCTAssertEqual(d1?._length, 4)
660+
661+
let d2 = Decimal(exactly: Int64.min)
662+
XCTAssertNotNil(d2)
663+
XCTAssertEqual(d2?.description, Int64.min.description)
664+
XCTAssertEqual(d2?._length, 4)
665+
666+
let d3 = Decimal(exactly: Int64.max)
667+
XCTAssertNotNil(d3)
668+
XCTAssertEqual(d3?.description, Int64.max.description)
669+
XCTAssertEqual(d3?._length, 4)
670+
671+
let d4 = Decimal(exactly: Int32.min)
672+
XCTAssertNotNil(d4)
673+
XCTAssertEqual(d4?.description, Int32.min.description)
674+
XCTAssertEqual(d4?._length, 2)
675+
676+
let d5 = Decimal(exactly: Int32.max)
677+
XCTAssertNotNil(d5)
678+
XCTAssertEqual(d5?.description, Int32.max.description)
679+
XCTAssertEqual(d5?._length, 2)
680+
681+
let d6 = Decimal(exactly: 0)
682+
XCTAssertNotNil(d6)
683+
XCTAssertEqual(d6, Decimal.zero)
684+
XCTAssertEqual(d6?.description, "0")
685+
XCTAssertEqual(d6?._length, 0)
686+
687+
let d7 = Decimal(exactly: 1)
688+
XCTAssertNotNil(d7)
689+
XCTAssertEqual(d7?.description, "1")
690+
XCTAssertEqual(d7?._length, 1)
691+
692+
let d8 = Decimal(exactly: -1)
693+
XCTAssertNotNil(d8)
694+
XCTAssertEqual(d8?.description, "-1")
695+
XCTAssertEqual(d8?._length, 1)
696+
}
697+
600698
}

Darwin/Foundation-swiftoverlay/Decimal.swift

Lines changed: 83 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
@_exported import Foundation // Clang module
1414
@_implementationOnly import _CoreFoundationOverlayShims
15+
import AppKit
1516

1617
extension Decimal {
1718
public typealias RoundingMode = NSDecimalNumber.RoundingMode
@@ -26,6 +27,7 @@ public func pow(_ x: Decimal, _ y: Int) -> Decimal {
2627
}
2728

2829
extension Decimal : Hashable, Comparable {
30+
// (Used by `doubleValue`.)
2931
private subscript(index: UInt32) -> UInt16 {
3032
get {
3133
switch index {
@@ -42,6 +44,7 @@ extension Decimal : Hashable, Comparable {
4244
}
4345
}
4446

47+
// (Used by `NSDecimalNumber` and `hash(into:)`.)
4548
internal var doubleValue: Double {
4649
if _length == 0 {
4750
return _isNegative == 1 ? Double.nan : 0
@@ -124,7 +127,7 @@ extension Decimal : Codable {
124127

125128
var mantissaContainer = try container.nestedUnkeyedContainer(forKey: .mantissa)
126129
var mantissa: (CUnsignedShort, CUnsignedShort, CUnsignedShort, CUnsignedShort,
127-
CUnsignedShort, CUnsignedShort, CUnsignedShort, CUnsignedShort) = (0,0,0,0,0,0,0,0)
130+
CUnsignedShort, CUnsignedShort, CUnsignedShort, CUnsignedShort) = (0,0,0,0,0,0,0,0)
128131
mantissa.0 = try mantissaContainer.decode(CUnsignedShort.self)
129132
mantissa.1 = try mantissaContainer.decode(CUnsignedShort.self)
130133
mantissa.2 = try mantissaContainer.decode(CUnsignedShort.self)
@@ -134,12 +137,12 @@ extension Decimal : Codable {
134137
mantissa.6 = try mantissaContainer.decode(CUnsignedShort.self)
135138
mantissa.7 = try mantissaContainer.decode(CUnsignedShort.self)
136139

137-
self = Decimal(_exponent: exponent,
138-
_length: length,
139-
_isNegative: CUnsignedInt(isNegative ? 1 : 0),
140-
_isCompact: CUnsignedInt(isCompact ? 1 : 0),
141-
_reserved: 0,
142-
_mantissa: mantissa)
140+
self.init(_exponent: exponent,
141+
_length: length,
142+
_isNegative: CUnsignedInt(isNegative ? 1 : 0),
143+
_isCompact: CUnsignedInt(isCompact ? 1 : 0),
144+
_reserved: 0,
145+
_mantissa: mantissa)
143146
}
144147

145148
public func encode(to encoder: Encoder) throws {
@@ -182,9 +185,50 @@ extension Decimal : SignedNumeric {
182185
_reserved: 0, _mantissa: self._mantissa)
183186
}
184187

185-
// FIXME(integers): implement properly
186188
public init?<T : BinaryInteger>(exactly source: T) {
187-
fatalError()
189+
let zero = 0 as T
190+
191+
if source == zero {
192+
self = Decimal.zero
193+
return
194+
}
195+
196+
let negative: UInt32 = (T.isSigned && source < zero) ? 1 : 0
197+
var mantissa = source.magnitude
198+
var exponent: Int32 = 0
199+
200+
let maxExponent = Int8.max
201+
while mantissa.isMultiple(of: 10) && (exponent < maxExponent) {
202+
exponent += 1
203+
mantissa /= 10
204+
}
205+
206+
// If the mantissa still requires more than 128 bits of storage then it is too large.
207+
if mantissa.bitWidth > 128 && (mantissa >> 128 != zero) { return nil }
208+
209+
let mantissaParts: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)
210+
let loWord = UInt64(truncatingIfNeeded: mantissa)
211+
var length = ((loWord.bitWidth - loWord.leadingZeroBitCount) + (UInt16.bitWidth - 1)) / UInt16.bitWidth
212+
mantissaParts.0 = UInt16(truncatingIfNeeded: loWord >> 0)
213+
mantissaParts.1 = UInt16(truncatingIfNeeded: loWord >> 16)
214+
mantissaParts.2 = UInt16(truncatingIfNeeded: loWord >> 32)
215+
mantissaParts.3 = UInt16(truncatingIfNeeded: loWord >> 48)
216+
217+
let hiWord = mantissa.bitWidth > 64 ? UInt64(truncatingIfNeeded: mantissa >> 64) : 0
218+
if hiWord != 0 {
219+
length = 4 + ((hiWord.bitWidth - hiWord.leadingZeroBitCount) + (UInt16.bitWidth - 1)) / UInt16.bitWidth
220+
mantissaParts.4 = UInt16(truncatingIfNeeded: hiWord >> 0)
221+
mantissaParts.5 = UInt16(truncatingIfNeeded: hiWord >> 16)
222+
mantissaParts.6 = UInt16(truncatingIfNeeded: hiWord >> 32)
223+
mantissaParts.7 = UInt16(truncatingIfNeeded: hiWord >> 48)
224+
} else {
225+
mantissaParts.4 = 0
226+
mantissaParts.5 = 0
227+
mantissaParts.6 = 0
228+
mantissaParts.7 = 0
229+
}
230+
231+
self = Decimal(_exponent: exponent, _length: UInt32(length), _isNegative: negative, _isCompact: 1, _reserved: 0, _mantissa: mantissaParts)
188232
}
189233

190234
public static func +=(lhs: inout Decimal, rhs: Decimal) {
@@ -332,11 +376,11 @@ extension Decimal {
332376
_mantissa: (0x6623, 0x7d57, 0x16e7, 0xad0d, 0xaf52, 0x4641, 0xdfa7, 0xec58)
333377
)
334378

335-
@available(*, unavailable, message: "Decimal does not yet fully adopt FloatingPoint.")
336-
public static var infinity: Decimal { fatalError("Decimal does not yet fully adopt FloatingPoint") }
379+
@available(*, unavailable, message: "Decimal does not fully adopt FloatingPoint.")
380+
public static var infinity: Decimal { fatalError("Decimal does not fully adopt FloatingPoint") }
337381

338-
@available(*, unavailable, message: "Decimal does not yet fully adopt FloatingPoint.")
339-
public static var signalingNaN: Decimal { fatalError("Decimal does not yet fully adopt FloatingPoint") }
382+
@available(*, unavailable, message: "Decimal does not fully adopt FloatingPoint.")
383+
public static var signalingNaN: Decimal { fatalError("Decimal does not fully adopt FloatingPoint") }
340384

341385
public static var quietNaN: Decimal {
342386
return Decimal(
@@ -411,7 +455,7 @@ extension Decimal {
411455
}
412456

413457
public init(_ value: Double) {
414-
precondition(!value.isInfinite, "Decimal does not yet fully adopt FloatingPoint")
458+
precondition(!value.isInfinite, "Decimal does not fully adopt FloatingPoint")
415459
if value.isNaN {
416460
self = Decimal.nan
417461
} else if value == 0.0 {
@@ -420,16 +464,36 @@ extension Decimal {
420464
self = Decimal()
421465
let negative = value < 0
422466
var val = negative ? -1 * value : value
423-
var exponent = 0
467+
var exponent: Int8 = 0
468+
469+
// Try to get val as close to UInt64.max whilst adjusting the exponent
470+
// to reduce the number of digits after the decimal point.
424471
while val < Double(UInt64.max - 1) {
472+
guard exponent > Int8.min else {
473+
self = Decimal.nan
474+
return
475+
}
425476
val *= 10.0
426477
exponent -= 1
427478
}
428-
while Double(UInt64.max - 1) < val {
479+
while Double(UInt64.max) <= val {
480+
guard exponent < Int8.max else {
481+
self = Decimal.nan
482+
return
483+
}
429484
val /= 10.0
430485
exponent += 1
431486
}
432-
var mantissa = UInt64(val)
487+
488+
var mantissa: UInt64
489+
let maxMantissa = Double(UInt64.max).nextDown
490+
if val > maxMantissa {
491+
// UInt64(Double(UInt64.max)) gives an overflow error; this is the largest
492+
// mantissa that can be set.
493+
mantissa = UInt64(maxMantissa)
494+
} else {
495+
mantissa = UInt64(val)
496+
}
433497

434498
var i: UInt32 = 0
435499
// This is a bit ugly but it is the closest approximation of the C
@@ -601,8 +665,8 @@ extension Decimal {
601665
return true
602666
}
603667

604-
@available(*, unavailable, message: "Decimal does not yet fully adopt FloatingPoint.")
605-
public mutating func formTruncatingRemainder(dividingBy other: Decimal) { fatalError("Decimal does not yet fully adopt FloatingPoint") }
668+
@available(*, unavailable, message: "Decimal does not fully adopt FloatingPoint.")
669+
public mutating func formTruncatingRemainder(dividingBy other: Decimal) { fatalError("Decimal does not fully adopt FloatingPoint") }
606670
}
607671

608672
extension Decimal : _ObjectiveCBridgeable {

0 commit comments

Comments
 (0)