Skip to content

Commit 91c645f

Browse files
authored
Merge pull request #2926 from spevans/pr_sr_13837
2 parents 3e7ee58 + 965a32f commit 91c645f

File tree

4 files changed

+100
-24
lines changed

4 files changed

+100
-24
lines changed

Sources/Foundation/Decimal.swift

+27-5
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public struct Decimal {
2020
return Int32(__exponent)
2121
}
2222
set {
23-
__exponent = Int8(truncatingIfNeeded: newValue)
23+
__exponent = Int8(newValue)
2424
}
2525
}
2626

@@ -83,8 +83,9 @@ public struct Decimal {
8383
}
8484

8585
public init(_exponent: Int32, _length: UInt32, _isNegative: UInt32, _isCompact: UInt32, _reserved: UInt32, _mantissa: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)) {
86+
precondition(_length <= 15)
8687
self._mantissa = _mantissa
87-
self.__exponent = Int8(truncatingIfNeeded: _exponent)
88+
self.__exponent = Int8(_exponent)
8889
self.__lengthAndFlags = UInt8(_length & 0b1111)
8990
self.__reserved = 0
9091
self._isNegative = _isNegative
@@ -627,16 +628,36 @@ extension Decimal {
627628
self = Decimal()
628629
let negative = value < 0
629630
var val = negative ? -1 * value : value
630-
var exponent = 0
631+
var exponent: Int8 = 0
632+
633+
// Try to get val as close to UInt64.max whilst adjusting the exponent
634+
// to reduce the number of digits after the decimal point.
631635
while val < Double(UInt64.max - 1) {
636+
guard exponent > Int8.min else {
637+
setNaN()
638+
return
639+
}
632640
val *= 10.0
633641
exponent -= 1
634642
}
635-
while Double(UInt64.max - 1) < val {
643+
while Double(UInt64.max) <= val {
644+
guard exponent < Int8.max else {
645+
setNaN()
646+
return
647+
}
636648
val /= 10.0
637649
exponent += 1
638650
}
639-
var mantissa = UInt64(val)
651+
652+
var mantissa: UInt64
653+
let maxMantissa = Double(UInt64.max).nextDown
654+
if val > maxMantissa {
655+
// UInt64(Double(UInt64.max)) gives an overflow error, this is the largest
656+
// mantissa that can be set.
657+
mantissa = UInt64(maxMantissa)
658+
} else {
659+
mantissa = UInt64(val)
660+
}
640661

641662
var i: Int32 = 0
642663
// This is a bit ugly but it is the closest approximation of the C
@@ -1924,6 +1945,7 @@ extension Decimal {
19241945
fileprivate static let maxSize: UInt32 = UInt32(NSDecimalMaxSize)
19251946

19261947
fileprivate init(length: UInt32, mantissa: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)) {
1948+
precondition(length <= 15)
19271949
self._mantissa = mantissa
19281950
self.__exponent = 0
19291951
self.__lengthAndFlags = 0

Tests/Foundation/Tests/TestDecimal.swift

+55-2
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ class TestDecimal: XCTestCase {
132132
XCTAssertEqual(d1._exponent, 0)
133133
XCTAssertEqual(d1._length, 4)
134134
}
135+
135136
func test_Constants() {
136137
XCTAssertEqual(8, NSDecimalMaxSize)
137138
XCTAssertEqual(32767, NSDecimalNoScale)
@@ -217,8 +218,8 @@ class TestDecimal: XCTestCase {
217218
let reserved: UInt32 = (1<<18 as UInt32) + (1<<17 as UInt32) + 1
218219
let mantissa: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16) = (6, 7, 8, 9, 10, 11, 12, 13)
219220
var explicit = Decimal(
220-
_exponent: 0x17f,
221-
_length: 0xff,
221+
_exponent: 0x7f,
222+
_length: 0x0f,
222223
_isNegative: 3,
223224
_isCompact: 4,
224225
_reserved: reserved,
@@ -501,6 +502,11 @@ class TestDecimal: XCTestCase {
501502
XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN e5")
502503

503504
XCTAssertFalse(Double(truncating: NSDecimalNumber(decimal: Decimal(0))).isNaN)
505+
XCTAssertTrue(Decimal(Double.leastNonzeroMagnitude).isNaN)
506+
XCTAssertTrue(Decimal(Double.leastNormalMagnitude).isNaN)
507+
XCTAssertTrue(Decimal(Double.greatestFiniteMagnitude).isNaN)
508+
XCTAssertTrue(Decimal(Double("1e-129")!).isNaN)
509+
XCTAssertTrue(Decimal(Double("0.1e-128")!).isNaN)
504510
}
505511

506512
func test_NegativeAndZeroMultiplication() {
@@ -827,6 +833,52 @@ class TestDecimal: XCTestCase {
827833
XCTAssertEqual(1, negativeSix.raising(toPower: 0))
828834
}
829835

836+
func test_parseDouble() throws {
837+
XCTAssertEqual(Decimal(Double(0.0)), Decimal(Int.zero))
838+
XCTAssertEqual(Decimal(Double(-0.0)), Decimal(Int.zero))
839+
840+
// These values can only be represented as Decimal.nan
841+
XCTAssertEqual(Decimal(Double.nan), Decimal.nan)
842+
XCTAssertEqual(Decimal(Double.signalingNaN), Decimal.nan)
843+
844+
// These values are out out range for Decimal
845+
XCTAssertEqual(Decimal(-Double.leastNonzeroMagnitude), Decimal.nan)
846+
XCTAssertEqual(Decimal(Double.leastNonzeroMagnitude), Decimal.nan)
847+
XCTAssertEqual(Decimal(-Double.leastNormalMagnitude), Decimal.nan)
848+
XCTAssertEqual(Decimal(Double.leastNormalMagnitude), Decimal.nan)
849+
XCTAssertEqual(Decimal(-Double.greatestFiniteMagnitude), Decimal.nan)
850+
XCTAssertEqual(Decimal(Double.greatestFiniteMagnitude), Decimal.nan)
851+
852+
// SR-13837
853+
let testDoubles: [(Double, String)] = [
854+
(1.8446744073709550E18, "1844674407370954752"),
855+
(1.8446744073709551E18, "1844674407370954752"),
856+
(1.8446744073709552E18, "1844674407370955264"),
857+
(1.8446744073709553E18, "1844674407370955264"),
858+
(1.8446744073709554E18, "1844674407370955520"),
859+
(1.8446744073709555E18, "1844674407370955520"),
860+
861+
(1.8446744073709550E19, "18446744073709547520"),
862+
(1.8446744073709551E19, "18446744073709552640"),
863+
(1.8446744073709552E19, "18446744073709552640"),
864+
(1.8446744073709553E19, "18446744073709552640"),
865+
(1.8446744073709554E19, "18446744073709555200"),
866+
(1.8446744073709555E19, "18446744073709555200"),
867+
868+
(1.8446744073709550E20, "184467440737095526400"),
869+
(1.8446744073709551E20, "184467440737095526400"),
870+
(1.8446744073709552E20, "184467440737095526400"),
871+
(1.8446744073709553E20, "184467440737095526400"),
872+
(1.8446744073709554E20, "184467440737095552000"),
873+
(1.8446744073709555E20, "184467440737095552000"),
874+
]
875+
876+
for (d, s) in testDoubles {
877+
XCTAssertEqual(Decimal(d), Decimal(string: s))
878+
XCTAssertEqual(Decimal(d).description, try XCTUnwrap(Decimal(string: s)).description)
879+
}
880+
}
881+
830882
func test_doubleValue() {
831883
XCTAssertEqual(NSDecimalNumber(decimal:Decimal(0)).doubleValue, 0)
832884
XCTAssertEqual(NSDecimalNumber(decimal:Decimal(1)).doubleValue, 1)
@@ -1379,6 +1431,7 @@ class TestDecimal: XCTestCase {
13791431
("test_SimpleMultiplication", test_SimpleMultiplication),
13801432
("test_SmallerNumbers", test_SmallerNumbers),
13811433
("test_ZeroPower", test_ZeroPower),
1434+
("test_parseDouble", test_parseDouble),
13821435
("test_doubleValue", test_doubleValue),
13831436
("test_NSDecimalNumberValues", test_NSDecimalNumberValues),
13841437
("test_bridging", test_bridging),

Tests/Foundation/Tests/TestJSONEncoder.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class TestJSONEncoder : XCTestCase {
4949
_testFragment(value: true, fragment: "true")
5050
_testFragment(value: Float(1), fragment: "1")
5151
_testFragment(value: Double(2), fragment: "2")
52-
_testFragment(value: Decimal(Double.leastNormalMagnitude), fragment: "0.0000000000000000000000000000000000000000000000000002225073858507201792")
52+
_testFragment(value: Decimal(Double(Float.leastNormalMagnitude)), fragment: "0.000000000000000000000000000000000000011754943508222875648")
5353
_testFragment(value: "test", fragment: "\"test\"")
5454
let v: Int? = nil
5555
_testFragment(value: v, fragment: "null")

Tests/Foundation/Tests/TestJSONSerialization.swift

+17-16
Original file line numberDiff line numberDiff line change
@@ -968,8 +968,9 @@ extension TestJSONSerialization {
968968
XCTAssertTrue(JSONSerialization.isValidJSONObject([NSNumber(value: true), NSNumber(value: Float.greatestFiniteMagnitude), NSNumber(value: Double.greatestFiniteMagnitude)]))
969969
XCTAssertTrue(JSONSerialization.isValidJSONObject([NSNumber(value: Int.max), NSNumber(value: Int8.max), NSNumber(value: Int16.max), NSNumber(value: Int32.max), NSNumber(value: Int64.max)]))
970970
XCTAssertTrue(JSONSerialization.isValidJSONObject([NSNumber(value: UInt.max), NSNumber(value: UInt8.max), NSNumber(value: UInt16.max), NSNumber(value: UInt32.max), NSNumber(value: UInt64.max)]))
971-
XCTAssertTrue(JSONSerialization.isValidJSONObject([NSDecimalNumber(booleanLiteral: true), NSDecimalNumber(decimal: Decimal.greatestFiniteMagnitude), NSDecimalNumber(floatLiteral: Double.greatestFiniteMagnitude), NSDecimalNumber(integerLiteral: Int.min)]))
972-
XCTAssertTrue(JSONSerialization.isValidJSONObject([Decimal(123), Decimal(Double.leastNonzeroMagnitude)]))
971+
XCTAssertTrue(JSONSerialization.isValidJSONObject([NSDecimalNumber(booleanLiteral: true), NSDecimalNumber(decimal: Decimal.greatestFiniteMagnitude)]))
972+
XCTAssertTrue(JSONSerialization.isValidJSONObject([NSDecimalNumber(floatLiteral: Double(Float.greatestFiniteMagnitude)), NSDecimalNumber(integerLiteral: Int.min)]))
973+
XCTAssertTrue(JSONSerialization.isValidJSONObject([Decimal(123), Decimal(Double(Float.leastNonzeroMagnitude))]))
973974

974975
XCTAssertFalse(JSONSerialization.isValidJSONObject(Float.nan))
975976
XCTAssertFalse(JSONSerialization.isValidJSONObject(Float.infinity))
@@ -1320,19 +1321,19 @@ extension TestJSONSerialization {
13201321
}
13211322

13221323
func test_serialize_NSDecimalNumber() {
1323-
let dn0: [Any] = [NSDecimalNumber(floatLiteral: -Double.leastNonzeroMagnitude)]
1324-
let dn1: [Any] = [NSDecimalNumber(floatLiteral: Double.leastNonzeroMagnitude)]
1325-
let dn2: [Any] = [NSDecimalNumber(floatLiteral: -Double.leastNormalMagnitude)]
1326-
let dn3: [Any] = [NSDecimalNumber(floatLiteral: Double.leastNormalMagnitude)]
1327-
let dn4: [Any] = [NSDecimalNumber(floatLiteral: -Double.greatestFiniteMagnitude)]
1328-
let dn5: [Any] = [NSDecimalNumber(floatLiteral: Double.greatestFiniteMagnitude)]
1329-
1330-
XCTAssertEqual(try trySerialize(dn0), "[-0.00000000000000000000000000000000000000000000000000000000000000000004940656458412464128]")
1331-
XCTAssertEqual(try trySerialize(dn1), "[0.00000000000000000000000000000000000000000000000000000000000000000004940656458412464128]")
1332-
XCTAssertEqual(try trySerialize(dn2), "[-0.0000000000000000000000000000000000000000000000000002225073858507201792]")
1333-
XCTAssertEqual(try trySerialize(dn3), "[0.0000000000000000000000000000000000000000000000000002225073858507201792]")
1334-
XCTAssertEqual(try trySerialize(dn4), "[-17976931348623167488000000000000000000000000000000000]")
1335-
XCTAssertEqual(try trySerialize(dn5), "[17976931348623167488000000000000000000000000000000000]")
1324+
let dn0: [Any] = [NSDecimalNumber(floatLiteral: Double(-Float.leastNonzeroMagnitude))]
1325+
let dn1: [Any] = [NSDecimalNumber(floatLiteral: Double(Float.leastNonzeroMagnitude))]
1326+
let dn2: [Any] = [NSDecimalNumber(floatLiteral: Double(-Float.leastNormalMagnitude))]
1327+
let dn3: [Any] = [NSDecimalNumber(floatLiteral: Double(Float.leastNormalMagnitude))]
1328+
let dn4: [Any] = [NSDecimalNumber(floatLiteral: Double(-Float.greatestFiniteMagnitude))]
1329+
let dn5: [Any] = [NSDecimalNumber(floatLiteral: Double(Float.greatestFiniteMagnitude))]
1330+
1331+
XCTAssertEqual(try trySerialize(dn0), "[-0.0000000000000000000000000000000000000000000014012984643248173056]")
1332+
XCTAssertEqual(try trySerialize(dn1), "[0.0000000000000000000000000000000000000000000014012984643248173056]")
1333+
XCTAssertEqual(try trySerialize(dn2), "[-0.000000000000000000000000000000000000011754943508222875648]")
1334+
XCTAssertEqual(try trySerialize(dn3), "[0.000000000000000000000000000000000000011754943508222875648]")
1335+
XCTAssertEqual(try trySerialize(dn4), "[-340282346638528921600000000000000000000]")
1336+
XCTAssertEqual(try trySerialize(dn5), "[340282346638528921600000000000000000000]")
13361337
XCTAssertEqual(try trySerialize([NSDecimalNumber(string: "0.0001"), NSDecimalNumber(string: "0.00"), NSDecimalNumber(string: "-0.0")]), "[0.0001,0,0]")
13371338
XCTAssertEqual(try trySerialize([NSDecimalNumber(integerLiteral: Int(Int16.min)), NSDecimalNumber(integerLiteral: 0), NSDecimalNumber(integerLiteral: Int(Int16.max))]), "[-32768,0,32767]")
13381339
XCTAssertEqual(try trySerialize([NSDecimalNumber(booleanLiteral: true), NSDecimalNumber(booleanLiteral: false)]), "[1,0]")
@@ -1379,7 +1380,7 @@ extension TestJSONSerialization {
13791380
XCTAssertEqual(try trySerialize(true, options: .fragmentsAllowed), "true")
13801381
XCTAssertEqual(try trySerialize(Float(1), options: .fragmentsAllowed), "1")
13811382
XCTAssertEqual(try trySerialize(Double(2), options: .fragmentsAllowed), "2")
1382-
XCTAssertEqual(try trySerialize(Decimal(Double.leastNormalMagnitude), options: .fragmentsAllowed), "0.0000000000000000000000000000000000000000000000000002225073858507201792")
1383+
XCTAssertEqual(try trySerialize(Decimal(Double(Float.leastNormalMagnitude)), options: .fragmentsAllowed), "0.000000000000000000000000000000000000011754943508222875648")
13831384
XCTAssertEqual(try trySerialize("test", options: .fragmentsAllowed), "\"test\"")
13841385
}
13851386

0 commit comments

Comments
 (0)