Skip to content

Commit 59346dd

Browse files
authored
Merge pull request #3056 from xwu/sr-13837
Synchronize `Decimal` overlay and corelibs-foundation implementations
2 parents 1bc603e + 25e3c38 commit 59346dd

File tree

3 files changed

+203
-39
lines changed

3 files changed

+203
-39
lines changed

Darwin/Foundation-swiftoverlay-Tests/TestDecimal.swift

+98
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

+82-19
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public func pow(_ x: Decimal, _ y: Int) -> Decimal {
2626
}
2727

2828
extension Decimal : Hashable, Comparable {
29+
// (Used by `doubleValue`.)
2930
private subscript(index: UInt32) -> UInt16 {
3031
get {
3132
switch index {
@@ -42,6 +43,7 @@ extension Decimal : Hashable, Comparable {
4243
}
4344
}
4445

46+
// (Used by `NSDecimalNumber` and `hash(into:)`.)
4547
internal var doubleValue: Double {
4648
if _length == 0 {
4749
return _isNegative == 1 ? Double.nan : 0
@@ -124,7 +126,7 @@ extension Decimal : Codable {
124126

125127
var mantissaContainer = try container.nestedUnkeyedContainer(forKey: .mantissa)
126128
var mantissa: (CUnsignedShort, CUnsignedShort, CUnsignedShort, CUnsignedShort,
127-
CUnsignedShort, CUnsignedShort, CUnsignedShort, CUnsignedShort) = (0,0,0,0,0,0,0,0)
129+
CUnsignedShort, CUnsignedShort, CUnsignedShort, CUnsignedShort) = (0,0,0,0,0,0,0,0)
128130
mantissa.0 = try mantissaContainer.decode(CUnsignedShort.self)
129131
mantissa.1 = try mantissaContainer.decode(CUnsignedShort.self)
130132
mantissa.2 = try mantissaContainer.decode(CUnsignedShort.self)
@@ -134,12 +136,12 @@ extension Decimal : Codable {
134136
mantissa.6 = try mantissaContainer.decode(CUnsignedShort.self)
135137
mantissa.7 = try mantissaContainer.decode(CUnsignedShort.self)
136138

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)
139+
self.init(_exponent: exponent,
140+
_length: length,
141+
_isNegative: CUnsignedInt(isNegative ? 1 : 0),
142+
_isCompact: CUnsignedInt(isCompact ? 1 : 0),
143+
_reserved: 0,
144+
_mantissa: mantissa)
143145
}
144146

145147
public func encode(to encoder: Encoder) throws {
@@ -182,9 +184,50 @@ extension Decimal : SignedNumeric {
182184
_reserved: 0, _mantissa: self._mantissa)
183185
}
184186

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

190233
public static func +=(lhs: inout Decimal, rhs: Decimal) {
@@ -332,11 +375,11 @@ extension Decimal {
332375
_mantissa: (0x6623, 0x7d57, 0x16e7, 0xad0d, 0xaf52, 0x4641, 0xdfa7, 0xec58)
333376
)
334377

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") }
378+
@available(*, unavailable, message: "Decimal does not fully adopt FloatingPoint.")
379+
public static var infinity: Decimal { fatalError("Decimal does not fully adopt FloatingPoint") }
337380

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") }
381+
@available(*, unavailable, message: "Decimal does not fully adopt FloatingPoint.")
382+
public static var signalingNaN: Decimal { fatalError("Decimal does not fully adopt FloatingPoint") }
340383

341384
public static var quietNaN: Decimal {
342385
return Decimal(
@@ -411,7 +454,7 @@ extension Decimal {
411454
}
412455

413456
public init(_ value: Double) {
414-
precondition(!value.isInfinite, "Decimal does not yet fully adopt FloatingPoint")
457+
precondition(!value.isInfinite, "Decimal does not fully adopt FloatingPoint")
415458
if value.isNaN {
416459
self = Decimal.nan
417460
} else if value == 0.0 {
@@ -420,16 +463,36 @@ extension Decimal {
420463
self = Decimal()
421464
let negative = value < 0
422465
var val = negative ? -1 * value : value
423-
var exponent = 0
466+
var exponent: Int8 = 0
467+
468+
// Try to get val as close to UInt64.max whilst adjusting the exponent
469+
// to reduce the number of digits after the decimal point.
424470
while val < Double(UInt64.max - 1) {
471+
guard exponent > Int8.min else {
472+
self = Decimal.nan
473+
return
474+
}
425475
val *= 10.0
426476
exponent -= 1
427477
}
428-
while Double(UInt64.max - 1) < val {
478+
while Double(UInt64.max) <= val {
479+
guard exponent < Int8.max else {
480+
self = Decimal.nan
481+
return
482+
}
429483
val /= 10.0
430484
exponent += 1
431485
}
432-
var mantissa = UInt64(val)
486+
487+
var mantissa: UInt64
488+
let maxMantissa = Double(UInt64.max).nextDown
489+
if val > maxMantissa {
490+
// UInt64(Double(UInt64.max)) gives an overflow error; this is the largest
491+
// mantissa that can be set.
492+
mantissa = UInt64(maxMantissa)
493+
} else {
494+
mantissa = UInt64(val)
495+
}
433496

434497
var i: UInt32 = 0
435498
// This is a bit ugly but it is the closest approximation of the C
@@ -601,8 +664,8 @@ extension Decimal {
601664
return true
602665
}
603666

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") }
667+
@available(*, unavailable, message: "Decimal does not fully adopt FloatingPoint.")
668+
public mutating func formTruncatingRemainder(dividingBy other: Decimal) { fatalError("Decimal does not fully adopt FloatingPoint") }
606669
}
607670

608671
extension Decimal : _ObjectiveCBridgeable {

0 commit comments

Comments
 (0)