Skip to content

Commit b3b87b6

Browse files
authored
Merge pull request #3070 from xwu/decimal-ulp-nextup-nextdown
[SR-14384][SR-14974] Fix `Decimal.ulp`, `nextUp`, `nextDown`
2 parents ac4445d + 376c6b2 commit b3b87b6

File tree

4 files changed

+358
-108
lines changed

4 files changed

+358
-108
lines changed

Darwin/Foundation-swiftoverlay-Tests/TestDecimal.swift

+72-18
Original file line numberDiff line numberDiff line change
@@ -206,21 +206,6 @@ class TestDecimal : XCTestCase {
206206
XCTAssertEqual(11, sm5)
207207
XCTAssertEqual(12, sm6)
208208
XCTAssertEqual(13, sm7)
209-
210-
let ulp = explicit.ulp
211-
XCTAssertEqual(0x7f, ulp.exponent)
212-
XCTAssertEqual(1, ulp._length)
213-
XCTAssertEqual(0, ulp._isNegative)
214-
XCTAssertEqual(1, ulp._isCompact)
215-
XCTAssertEqual(0, ulp._reserved)
216-
XCTAssertEqual(1, ulp._mantissa.0)
217-
XCTAssertEqual(0, ulp._mantissa.1)
218-
XCTAssertEqual(0, ulp._mantissa.2)
219-
XCTAssertEqual(0, ulp._mantissa.3)
220-
XCTAssertEqual(0, ulp._mantissa.4)
221-
XCTAssertEqual(0, ulp._mantissa.5)
222-
XCTAssertEqual(0, ulp._mantissa.6)
223-
XCTAssertEqual(0, ulp._mantissa.7)
224209
}
225210

226211
func test_Maths() {
@@ -285,8 +270,6 @@ class TestDecimal : XCTestCase {
285270
XCTAssertTrue(Decimal(2) < Decimal(3))
286271
XCTAssertTrue(Decimal(3) > Decimal(2))
287272
XCTAssertEqual(Decimal(-9), Decimal(1) - Decimal(10))
288-
XCTAssertEqual(Decimal(3), Decimal(2).nextUp)
289-
XCTAssertEqual(Decimal(2), Decimal(3).nextDown)
290273
XCTAssertEqual(Decimal(1.234), abs(Decimal(1.234)))
291274
XCTAssertEqual(Decimal(1.234), abs(Decimal(-1.234)))
292275
if #available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) {
@@ -629,8 +612,79 @@ class TestDecimal : XCTestCase {
629612
}
630613

631614
func test_ULP() {
632-
let x = 0.1 as Decimal
615+
var x = 0.1 as Decimal
633616
XCTAssertFalse(x.ulp > x)
617+
618+
x = .nan
619+
XCTAssertTrue(x.ulp.isNaN)
620+
XCTAssertTrue(x.nextDown.isNaN)
621+
XCTAssertTrue(x.nextUp.isNaN)
622+
623+
x = .greatestFiniteMagnitude
624+
XCTAssertEqual(x.ulp, Decimal(string: "1e127")!)
625+
XCTAssertEqual(x.nextDown, x - Decimal(string: "1e127")!)
626+
XCTAssertTrue(x.nextUp.isNaN)
627+
628+
// '4' is an important value to test because the max supported
629+
// significand of this type is not 10 ** 38 - 1 but rather 2 ** 128 - 1,
630+
// for which reason '4.ulp' is not equal to '1.ulp' despite having the
631+
// same decimal exponent.
632+
x = 4
633+
XCTAssertEqual(x.ulp, Decimal(string: "1e-37")!)
634+
XCTAssertEqual(x.nextDown, x - Decimal(string: "1e-37")!)
635+
XCTAssertEqual(x.nextUp, x + Decimal(string: "1e-37")!)
636+
XCTAssertEqual(x.nextDown.nextUp, x)
637+
XCTAssertEqual(x.nextUp.nextDown, x)
638+
XCTAssertNotEqual(x.nextDown, x)
639+
XCTAssertNotEqual(x.nextUp, x)
640+
641+
// For similar reasons, '3.40282366920938463463374607431768211455',
642+
// which has the same significand as 'Decimal.greatestFiniteMagnitude',
643+
// is an important value to test because the distance to the next
644+
// representable value is more than 'ulp' and instead requires
645+
// incrementing '_exponent'.
646+
x = Decimal(string: "3.40282366920938463463374607431768211455")!
647+
XCTAssertEqual(x.ulp, Decimal(string: "0.00000000000000000000000000000000000001")!)
648+
XCTAssertEqual(x.nextUp, Decimal(string: "3.4028236692093846346337460743176821146")!)
649+
x = Decimal(string: "3.4028236692093846346337460743176821146")!
650+
XCTAssertEqual(x.ulp, Decimal(string: "0.0000000000000000000000000000000000001")!)
651+
XCTAssertEqual(x.nextDown, Decimal(string: "3.40282366920938463463374607431768211455")!)
652+
653+
x = 1
654+
XCTAssertEqual(x.ulp, Decimal(string: "1e-38")!)
655+
XCTAssertEqual(x.nextDown, x - Decimal(string: "1e-38")!)
656+
XCTAssertEqual(x.nextUp, x + Decimal(string: "1e-38")!)
657+
XCTAssertEqual(x.nextDown.nextUp, x)
658+
XCTAssertEqual(x.nextUp.nextDown, x)
659+
XCTAssertNotEqual(x.nextDown, x)
660+
XCTAssertNotEqual(x.nextUp, x)
661+
662+
x = .leastNonzeroMagnitude
663+
XCTAssertEqual(x.ulp, x)
664+
XCTAssertEqual(x.nextDown, 0)
665+
XCTAssertEqual(x.nextUp, x + x)
666+
XCTAssertEqual(x.nextDown.nextUp, x)
667+
XCTAssertEqual(x.nextUp.nextDown, x)
668+
XCTAssertNotEqual(x.nextDown, x)
669+
XCTAssertNotEqual(x.nextUp, x)
670+
671+
x = 0
672+
XCTAssertEqual(x.ulp, Decimal(string: "1e-128")!)
673+
XCTAssertEqual(x.nextDown, -Decimal(string: "1e-128")!)
674+
XCTAssertEqual(x.nextUp, Decimal(string: "1e-128")!)
675+
XCTAssertEqual(x.nextDown.nextUp, x)
676+
XCTAssertEqual(x.nextUp.nextDown, x)
677+
XCTAssertNotEqual(x.nextDown, x)
678+
XCTAssertNotEqual(x.nextUp, x)
679+
680+
x = -1
681+
XCTAssertEqual(x.ulp, Decimal(string: "1e-38")!)
682+
XCTAssertEqual(x.nextDown, x - Decimal(string: "1e-38")!)
683+
XCTAssertEqual(x.nextUp, x + Decimal(string: "1e-38")!)
684+
XCTAssertEqual(x.nextDown.nextUp, x)
685+
XCTAssertEqual(x.nextUp.nextDown, x)
686+
XCTAssertNotEqual(x.nextDown, x)
687+
XCTAssertNotEqual(x.nextUp, x)
634688
}
635689

636690
func test_unconditionallyBridgeFromObjectiveC() {

Darwin/Foundation-swiftoverlay/Decimal.swift

+84-8
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,57 @@ extension Decimal : Strideable {
324324
}
325325
}
326326

327+
private extension Decimal {
328+
// Creates a value with zero exponent.
329+
// (Used by `_powersOfTenDividingUInt128Max`.)
330+
init(_length: UInt32, _isCompact: UInt32, _mantissa: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)) {
331+
self.init(_exponent: 0, _length: _length, _isNegative: 0, _isCompact: _isCompact,
332+
_reserved: 0, _mantissa: _mantissa)
333+
}
334+
}
335+
336+
private let _powersOfTenDividingUInt128Max = [
337+
/* 10**00 dividing UInt128.max is deliberately omitted. */
338+
/* 10**01 */ Decimal(_length: 8, _isCompact: 1, _mantissa: (0x9999, 0x9999, 0x9999, 0x9999, 0x9999, 0x9999, 0x9999, 0x1999)),
339+
/* 10**02 */ Decimal(_length: 8, _isCompact: 1, _mantissa: (0xf5c2, 0x5c28, 0xc28f, 0x28f5, 0x8f5c, 0xf5c2, 0x5c28, 0x028f)),
340+
/* 10**03 */ Decimal(_length: 8, _isCompact: 1, _mantissa: (0x1893, 0x5604, 0x2d0e, 0x9db2, 0xa7ef, 0x4bc6, 0x8937, 0x0041)),
341+
/* 10**04 */ Decimal(_length: 8, _isCompact: 1, _mantissa: (0x0275, 0x089a, 0x9e1b, 0x295e, 0x10cb, 0xbac7, 0x8db8, 0x0006)),
342+
/* 10**05 */ Decimal(_length: 7, _isCompact: 1, _mantissa: (0x3372, 0x80dc, 0x0fcf, 0x8423, 0x1b47, 0xac47, 0xa7c5,0)),
343+
/* 10**06 */ Decimal(_length: 7, _isCompact: 1, _mantissa: (0x3858, 0xf349, 0xb4c7, 0x8d36, 0xb5ed, 0xf7a0, 0x10c6,0)),
344+
/* 10**07 */ Decimal(_length: 7, _isCompact: 1, _mantissa: (0xec08, 0x6520, 0x787a, 0xf485, 0xabca, 0x7f29, 0x01ad,0)),
345+
/* 10**08 */ Decimal(_length: 7, _isCompact: 1, _mantissa: (0x4acd, 0x7083, 0xbf3f, 0x1873, 0xc461, 0xf31d, 0x002a,0)),
346+
/* 10**09 */ Decimal(_length: 7, _isCompact: 1, _mantissa: (0x5447, 0x8b40, 0x2cb9, 0xb5a5, 0xfa09, 0x4b82, 0x0004,0)),
347+
/* 10**10 */ Decimal(_length: 6, _isCompact: 1, _mantissa: (0xa207, 0x5ab9, 0xeadf, 0x5ef6, 0x7f67, 0x6df3,0,0)),
348+
/* 10**11 */ Decimal(_length: 6, _isCompact: 1, _mantissa: (0xf69a, 0xef78, 0x4aaf, 0xbcb2, 0xbff0, 0x0afe,0,0)),
349+
/* 10**12 */ Decimal(_length: 6, _isCompact: 1, _mantissa: (0x7f0f, 0x97f2, 0xa111, 0x12de, 0x7998, 0x0119,0,0)),
350+
/* 10**13 */ Decimal(_length: 6, _isCompact: 0, _mantissa: (0x0cb4, 0xc265, 0x7681, 0x6849, 0x25c2, 0x001c,0,0)),
351+
/* 10**14 */ Decimal(_length: 6, _isCompact: 1, _mantissa: (0x4e12, 0x603d, 0x2573, 0x70d4, 0xd093, 0x0002,0,0)),
352+
/* 10**15 */ Decimal(_length: 5, _isCompact: 1, _mantissa: (0x87ce, 0x566c, 0x9d58, 0xbe7b, 0x480e,0,0,0)),
353+
/* 10**16 */ Decimal(_length: 5, _isCompact: 1, _mantissa: (0xda61, 0x6f0a, 0xf622, 0xaca5, 0x0734,0,0,0)),
354+
/* 10**17 */ Decimal(_length: 5, _isCompact: 1, _mantissa: (0x4909, 0xa4b4, 0x3236, 0x77aa, 0x00b8,0,0,0)),
355+
/* 10**18 */ Decimal(_length: 5, _isCompact: 1, _mantissa: (0xa0e7, 0x43ab, 0xd1d2, 0x725d, 0x0012,0,0,0)),
356+
/* 10**19 */ Decimal(_length: 5, _isCompact: 1, _mantissa: (0xc34a, 0x6d2a, 0x94fb, 0xd83c, 0x0001,0,0,0)),
357+
/* 10**20 */ Decimal(_length: 4, _isCompact: 1, _mantissa: (0x46ba, 0x2484, 0x4219, 0x2f39,0,0,0,0)),
358+
/* 10**21 */ Decimal(_length: 4, _isCompact: 1, _mantissa: (0xd3df, 0x83a6, 0xed02, 0x04b8,0,0,0,0)),
359+
/* 10**22 */ Decimal(_length: 4, _isCompact: 1, _mantissa: (0x7b96, 0x405d, 0xe480, 0x0078,0,0,0,0)),
360+
/* 10**23 */ Decimal(_length: 4, _isCompact: 1, _mantissa: (0x5928, 0xa009, 0x16d9, 0x000c,0,0,0,0)),
361+
/* 10**24 */ Decimal(_length: 4, _isCompact: 1, _mantissa: (0x88ea, 0x299a, 0x357c, 0x0001,0,0,0,0)),
362+
/* 10**25 */ Decimal(_length: 3, _isCompact: 1, _mantissa: (0xda7d, 0xd0f5, 0x1ef2,0,0,0,0,0)),
363+
/* 10**26 */ Decimal(_length: 3, _isCompact: 1, _mantissa: (0x95d9, 0x4818, 0x0318,0,0,0,0,0)),
364+
/* 10**27 */ Decimal(_length: 3, _isCompact: 0, _mantissa: (0xdbc8, 0x3a68, 0x004f,0,0,0,0,0)),
365+
/* 10**28 */ Decimal(_length: 3, _isCompact: 1, _mantissa: (0xaf94, 0xec3d, 0x0007,0,0,0,0,0)),
366+
/* 10**29 */ Decimal(_length: 2, _isCompact: 1, _mantissa: (0xf7f5, 0xcad2,0,0,0,0,0,0)),
367+
/* 10**30 */ Decimal(_length: 2, _isCompact: 1, _mantissa: (0x4bfe, 0x1448,0,0,0,0,0,0)),
368+
/* 10**31 */ Decimal(_length: 2, _isCompact: 1, _mantissa: (0x3acc, 0x0207,0,0,0,0,0,0)),
369+
/* 10**32 */ Decimal(_length: 2, _isCompact: 1, _mantissa: (0xec47, 0x0033,0,0,0,0,0,0)),
370+
/* 10**33 */ Decimal(_length: 2, _isCompact: 1, _mantissa: (0x313a, 0x0005,0,0,0,0,0,0)),
371+
/* 10**34 */ Decimal(_length: 1, _isCompact: 1, _mantissa: (0x84ec,0,0,0,0,0,0,0)),
372+
/* 10**35 */ Decimal(_length: 1, _isCompact: 1, _mantissa: (0x0d4a,0,0,0,0,0,0,0)),
373+
/* 10**36 */ Decimal(_length: 1, _isCompact: 0, _mantissa: (0x0154,0,0,0,0,0,0,0)),
374+
/* 10**37 */ Decimal(_length: 1, _isCompact: 1, _mantissa: (0x0022,0,0,0,0,0,0,0)),
375+
/* 10**38 */ Decimal(_length: 1, _isCompact: 1, _mantissa: (0x0003,0,0,0,0,0,0,0))
376+
]
377+
327378
// The methods in this extension exist to match the protocol requirements of
328379
// FloatingPoint, even if we can't conform directly.
329380
//
@@ -551,22 +602,47 @@ extension Decimal {
551602
}
552603

553604
public var ulp: Decimal {
554-
if !self.isFinite { return Decimal.nan }
605+
guard isFinite else { return .nan }
606+
607+
let exponent: Int32
608+
if isZero {
609+
exponent = .min
610+
} else {
611+
let significand_ = significand
612+
let shift =
613+
_powersOfTenDividingUInt128Max.firstIndex { significand_ > $0 }
614+
?? _powersOfTenDividingUInt128Max.count
615+
exponent = _exponent &- Int32(shift)
616+
}
617+
555618
return Decimal(
556-
_exponent: _exponent, _length: 1, _isNegative: 0, _isCompact: 1,
619+
_exponent: max(exponent, -128), _length: 1, _isNegative: 0, _isCompact: 1,
557620
_reserved: 0, _mantissa: (0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000))
558621
}
559622

560623
public var nextUp: Decimal {
561-
return self + Decimal(
562-
_exponent: _exponent, _length: 1, _isNegative: 0, _isCompact: 1,
563-
_reserved: 0, _mantissa: (0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000))
624+
if _isNegative == 1 {
625+
if _exponent > -128
626+
&& (_mantissa.0, _mantissa.1, _mantissa.2, _mantissa.3) == (0x999a, 0x9999, 0x9999, 0x9999)
627+
&& (_mantissa.4, _mantissa.5, _mantissa.6, _mantissa.7) == (0x9999, 0x9999, 0x9999, 0x1999) {
628+
return Decimal(
629+
_exponent: _exponent &- 1, _length: 8, _isNegative: 1, _isCompact: 1,
630+
_reserved: 0, _mantissa: (0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff))
631+
}
632+
} else {
633+
if _exponent < 127
634+
&& (_mantissa.0, _mantissa.1, _mantissa.2, _mantissa.3) == (0xffff, 0xffff, 0xffff, 0xffff)
635+
&& (_mantissa.4, _mantissa.5, _mantissa.6, _mantissa.7) == (0xffff, 0xffff, 0xffff, 0xffff) {
636+
return Decimal(
637+
_exponent: _exponent &+ 1, _length: 8, _isNegative: 0, _isCompact: 1,
638+
_reserved: 0, _mantissa: (0x999a, 0x9999, 0x9999, 0x9999, 0x9999, 0x9999, 0x9999, 0x1999))
639+
}
640+
}
641+
return self + ulp
564642
}
565643

566644
public var nextDown: Decimal {
567-
return self - Decimal(
568-
_exponent: _exponent, _length: 1, _isNegative: 0, _isCompact: 1,
569-
_reserved: 0, _mantissa: (0x0001, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000))
645+
return -(-self).nextUp
570646
}
571647

572648
/// The IEEE 754 "class" of this type.

0 commit comments

Comments
 (0)