Skip to content

Commit

Permalink
Fix a subtle bug in Decimal.ulp and adjust tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
xwu committed Sep 5, 2021
1 parent e8c418c commit aed94d4
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 111 deletions.
40 changes: 33 additions & 7 deletions Darwin/Foundation-swiftoverlay-Tests/TestDecimal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -604,28 +604,54 @@ class TestDecimal : XCTestCase {
XCTAssertEqual(x.nextDown, x - Decimal(string: "1e127")!)
XCTAssertTrue(x.nextUp.isNaN)

// '4' is an important value to test because the max supported
// significand of this type is not 10 ** 38 - 1 but rather 2 ** 128 - 1,
// for which reason '4.ulp' is not equal to '1.ulp' despite having the
// same decimal exponent.
x = 4
XCTAssertEqual(x.ulp, Decimal(string: "1e-37")!)
XCTAssertEqual(x.nextDown, x - Decimal(string: "1e-37")!)
XCTAssertEqual(x.nextUp, x + Decimal(string: "1e-37")!)
XCTAssertEqual(x.nextDown.nextUp, x)
XCTAssertEqual(x.nextUp.nextDown, x)
XCTAssertNotEqual(x.nextDown, x)
XCTAssertNotEqual(x.nextUp, x)

x = 1
XCTAssertEqual(x.ulp, Decimal(string: "1e-38")!)
XCTAssertEqual(x.nextDown, x - Decimal(string: "1e-38")!)
XCTAssertEqual(x.nextUp, x + Decimal(string: "1e-38")!)
XCTAssertEqual(x.nextDown.nextUp, x)
XCTAssertEqual(x.nextUp.nextDown, x)
XCTAssertNotEqual(x.nextDown, x)
XCTAssertNotEqual(x.nextUp, x)

x = .leastNonzeroMagnitude
// XCTAssertEqual(x.ulp, x) // SR-6671
// XCTAssertEqual(x.nextDown, 0) // SR-6671
// XCTAssertEqual(x.nextUp, x + x) // SR-6671
XCTAssertNotEqual(x.ulp, 0)
XCTAssertEqual(x.ulp, x)
XCTAssertEqual(x.nextDown, 0)
XCTAssertEqual(x.nextUp, x + x)
XCTAssertEqual(x.nextDown.nextUp, x)
XCTAssertEqual(x.nextUp.nextDown, x)
XCTAssertNotEqual(x.nextDown, x)
XCTAssertNotEqual(x.nextUp, x)

x = 0
XCTAssertEqual(x.ulp, Decimal(string: "1e-128")!)
XCTAssertEqual(x.nextDown, -Decimal(string: "1e-128")!)
XCTAssertEqual(x.nextUp, Decimal(string: "1e-128")!)
XCTAssertEqual(x.nextDown.nextUp, x)
XCTAssertEqual(x.nextUp.nextDown, x)
XCTAssertNotEqual(x.nextDown, x)
XCTAssertNotEqual(x.nextUp, x)

x = -1
XCTAssertEqual(x.ulp, (1 as Decimal).ulp)
XCTAssertEqual(x.nextDown, -((1 as Decimal).nextUp))
XCTAssertEqual(x.nextUp, -((1 as Decimal).nextDown))
XCTAssertEqual(x.ulp, Decimal(string: "1e-38")!)
XCTAssertEqual(x.nextDown, x - Decimal(string: "1e-38")!)
XCTAssertEqual(x.nextUp, x + Decimal(string: "1e-38")!)
XCTAssertEqual(x.nextDown.nextUp, x)
XCTAssertEqual(x.nextUp.nextDown, x)
XCTAssertNotEqual(x.nextDown, x)
XCTAssertNotEqual(x.nextUp, x)
}

func test_unconditionallyBridgeFromObjectiveC() {
Expand Down
99 changes: 50 additions & 49 deletions Darwin/Foundation-swiftoverlay/Decimal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -324,55 +324,55 @@ extension Decimal : Strideable {
}
}

extension Decimal {
// (Used by `_powersOfTen` and `ulp`; note that the representation isn't compact.)
fileprivate init(_length: UInt32, _mantissa: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)) {
self.init(_exponent: 0, _length: _length, _isNegative: 0, _isCompact: 0,
private extension Decimal {
// Creates a value with zero exponent.
// (Used by `_powersOfTenDividingUInt128Max`.)
init(_length: UInt32, _isCompact: UInt32, _mantissa: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)) {
self.init(_exponent: 0, _length: _length, _isNegative: 0, _isCompact: _isCompact,
_reserved: 0, _mantissa: _mantissa)
}
}

private let _powersOfTen = [
/*^00*/ 1 as Decimal,
/*^01*/ Decimal(_length: 1, _mantissa: (0x000a,0,0,0,0,0,0,0)),
/*^02*/ Decimal(_length: 1, _mantissa: (0x0064,0,0,0,0,0,0,0)),
/*^03*/ Decimal(_length: 1, _mantissa: (0x03e8,0,0,0,0,0,0,0)),
/*^04*/ Decimal(_length: 1, _mantissa: (0x2710,0,0,0,0,0,0,0)),
/*^05*/ Decimal(_length: 2, _mantissa: (0x86a0, 0x0001,0,0,0,0,0,0)),
/*^06*/ Decimal(_length: 2, _mantissa: (0x4240, 0x000f,0,0,0,0,0,0)),
/*^07*/ Decimal(_length: 2, _mantissa: (0x9680, 0x0098,0,0,0,0,0,0)),
/*^08*/ Decimal(_length: 2, _mantissa: (0xe100, 0x05f5,0,0,0,0,0,0)),
/*^09*/ Decimal(_length: 2, _mantissa: (0xca00, 0x3b9a,0,0,0,0,0,0)),
/*^10*/ Decimal(_length: 3, _mantissa: (0xe400, 0x540b, 0x0002,0,0,0,0,0)),
/*^11*/ Decimal(_length: 3, _mantissa: (0xe800, 0x4876, 0x0017,0,0,0,0,0)),
/*^12*/ Decimal(_length: 3, _mantissa: (0x1000, 0xd4a5, 0x00e8,0,0,0,0,0)),
/*^13*/ Decimal(_length: 3, _mantissa: (0xa000, 0x4e72, 0x0918,0,0,0,0,0)),
/*^14*/ Decimal(_length: 3, _mantissa: (0x4000, 0x107a, 0x5af3,0,0,0,0,0)),
/*^15*/ Decimal(_length: 4, _mantissa: (0x8000, 0xa4c6, 0x8d7e, 0x0003,0,0,0,0)),
/*^16*/ Decimal(_length: 4, _mantissa: (0x0000, 0x6fc1, 0x86f2, 0x0023,0,0,0,0)),
/*^17*/ Decimal(_length: 4, _mantissa: (0x0000, 0x5d8a, 0x4578, 0x0163,0,0,0,0)),
/*^18*/ Decimal(_length: 4, _mantissa: (0x0000, 0xa764, 0xb6b3, 0x0de0,0,0,0,0)),
/*^19*/ Decimal(_length: 4, _mantissa: (0x0000, 0x89e8, 0x2304, 0x8ac7,0,0,0,0)),
/*^20*/ Decimal(_length: 5, _mantissa: (0x0000, 0x6310, 0x5e2d, 0x6bc7, 0x0005,0,0,0)),
/*^21*/ Decimal(_length: 5, _mantissa: (0x0000, 0xdea0, 0xadc5, 0x35c9, 0x0036,0,0,0)),
/*^22*/ Decimal(_length: 5, _mantissa: (0x0000, 0xb240, 0xc9ba, 0x19e0, 0x021e,0,0,0)),
/*^23*/ Decimal(_length: 5, _mantissa: (0x0000, 0xf680, 0xe14a, 0x02c7, 0x152d,0,0,0)),
/*^24*/ Decimal(_length: 5, _mantissa: (0x0000, 0xa100, 0xcced, 0x1bce, 0xd3c2,0,0,0)),
/*^25*/ Decimal(_length: 6, _mantissa: (0x0000, 0x4a00, 0x0148, 0x1614, 0x4595, 0x0008,0,0)),
/*^26*/ Decimal(_length: 6, _mantissa: (0x0000, 0xe400, 0x0cd2, 0xdcc8, 0xb7d2, 0x0052,0,0)),
/*^27*/ Decimal(_length: 6, _mantissa: (0x0000, 0xe800, 0x803c, 0x9fd0, 0x2e3c, 0x033b,0,0)),
/*^28*/ Decimal(_length: 6, _mantissa: (0x0000, 0x1000, 0x0261, 0x3e25, 0xce5e, 0x204f,0,0)),
/*^29*/ Decimal(_length: 7, _mantissa: (0x0000, 0xa000, 0x17ca, 0x6d72, 0x0fae, 0x431e, 0x0001,0)),
/*^30*/ Decimal(_length: 7, _mantissa: (0x0000, 0x4000, 0xedea, 0x4674, 0x9cd0, 0x9f2c, 0x000c,0)),
/*^31*/ Decimal(_length: 7, _mantissa: (0x0000, 0x8000, 0x4b26, 0xc091, 0x2022, 0x37be, 0x007e,0)),
/*^32*/ Decimal(_length: 7, _mantissa: (0x0000, 0x0000, 0xef81, 0x85ac, 0x415b, 0x2d6d, 0x04ee,0)),
/*^33*/ Decimal(_length: 7, _mantissa: (0x0000, 0x0000, 0x5b0a, 0x38c1, 0x8d93, 0xc644, 0x314d,0)),
/*^34*/ Decimal(_length: 8, _mantissa: (0x0000, 0x0000, 0x8e64, 0x378d, 0x87c0, 0xbead, 0xed09, 0x0001)),
/*^35*/ Decimal(_length: 8, _mantissa: (0x0000, 0x0000, 0x8fe8, 0x2b87, 0x4d82, 0x72c7, 0x4261, 0x0013)),
/*^36*/ Decimal(_length: 8, _mantissa: (0x0000, 0x0000, 0x9f10, 0xb34b, 0x0715, 0x7bc9, 0x97ce, 0x00c0)),
/*^37*/ Decimal(_length: 8, _mantissa: (0x0000, 0x0000, 0x36a0, 0x00f4, 0x46d9, 0xd5da, 0xee10, 0x0785)),
/*^38*/ Decimal(_length: 8, _mantissa: (0x0000, 0x0000, 0x2240, 0x098a, 0xc47a, 0x5a86, 0x4ca8, 0x4b3b))
/*^39 is on 9 shorts.*/
private let _powersOfTenDividingUInt128Max = [
/* 10**00 dividing UInt128.max is deliberately omitted. */
/* 10**01 */ Decimal(_length: 8, _isCompact: 1, _mantissa: (0x9999, 0x9999, 0x9999, 0x9999, 0x9999, 0x9999, 0x9999, 0x1999)),
/* 10**02 */ Decimal(_length: 8, _isCompact: 1, _mantissa: (0xf5c2, 0x5c28, 0xc28f, 0x28f5, 0x8f5c, 0xf5c2, 0x5c28, 0x028f)),
/* 10**03 */ Decimal(_length: 8, _isCompact: 1, _mantissa: (0x1893, 0x5604, 0x2d0e, 0x9db2, 0xa7ef, 0x4bc6, 0x8937, 0x0041)),
/* 10**04 */ Decimal(_length: 8, _isCompact: 1, _mantissa: (0x0275, 0x089a, 0x9e1b, 0x295e, 0x10cb, 0xbac7, 0x8db8, 0x0006)),
/* 10**05 */ Decimal(_length: 7, _isCompact: 1, _mantissa: (0x3372, 0x80dc, 0x0fcf, 0x8423, 0x1b47, 0xac47, 0xa7c5,0)),
/* 10**06 */ Decimal(_length: 7, _isCompact: 1, _mantissa: (0x3858, 0xf349, 0xb4c7, 0x8d36, 0xb5ed, 0xf7a0, 0x10c6,0)),
/* 10**07 */ Decimal(_length: 7, _isCompact: 1, _mantissa: (0xec08, 0x6520, 0x787a, 0xf485, 0xabca, 0x7f29, 0x01ad,0)),
/* 10**08 */ Decimal(_length: 7, _isCompact: 1, _mantissa: (0x4acd, 0x7083, 0xbf3f, 0x1873, 0xc461, 0xf31d, 0x002a,0)),
/* 10**09 */ Decimal(_length: 7, _isCompact: 1, _mantissa: (0x5447, 0x8b40, 0x2cb9, 0xb5a5, 0xfa09, 0x4b82, 0x0004,0)),
/* 10**10 */ Decimal(_length: 6, _isCompact: 1, _mantissa: (0xa207, 0x5ab9, 0xeadf, 0x5ef6, 0x7f67, 0x6df3,0,0)),
/* 10**11 */ Decimal(_length: 6, _isCompact: 1, _mantissa: (0xf69a, 0xef78, 0x4aaf, 0xbcb2, 0xbff0, 0x0afe,0,0)),
/* 10**12 */ Decimal(_length: 6, _isCompact: 1, _mantissa: (0x7f0f, 0x97f2, 0xa111, 0x12de, 0x7998, 0x0119,0,0)),
/* 10**13 */ Decimal(_length: 6, _isCompact: 0, _mantissa: (0x0cb4, 0xc265, 0x7681, 0x6849, 0x25c2, 0x001c,0,0)),
/* 10**14 */ Decimal(_length: 6, _isCompact: 1, _mantissa: (0x4e12, 0x603d, 0x2573, 0x70d4, 0xd093, 0x0002,0,0)),
/* 10**15 */ Decimal(_length: 5, _isCompact: 1, _mantissa: (0x87ce, 0x566c, 0x9d58, 0xbe7b, 0x480e,0,0,0)),
/* 10**16 */ Decimal(_length: 5, _isCompact: 1, _mantissa: (0xda61, 0x6f0a, 0xf622, 0xaca5, 0x0734,0,0,0)),
/* 10**17 */ Decimal(_length: 5, _isCompact: 1, _mantissa: (0x4909, 0xa4b4, 0x3236, 0x77aa, 0x00b8,0,0,0)),
/* 10**18 */ Decimal(_length: 5, _isCompact: 1, _mantissa: (0xa0e7, 0x43ab, 0xd1d2, 0x725d, 0x0012,0,0,0)),
/* 10**19 */ Decimal(_length: 5, _isCompact: 1, _mantissa: (0xc34a, 0x6d2a, 0x94fb, 0xd83c, 0x0001,0,0,0)),
/* 10**20 */ Decimal(_length: 4, _isCompact: 1, _mantissa: (0x46ba, 0x2484, 0x4219, 0x2f39,0,0,0,0)),
/* 10**21 */ Decimal(_length: 4, _isCompact: 1, _mantissa: (0xd3df, 0x83a6, 0xed02, 0x04b8,0,0,0,0)),
/* 10**22 */ Decimal(_length: 4, _isCompact: 1, _mantissa: (0x7b96, 0x405d, 0xe480, 0x0078,0,0,0,0)),
/* 10**23 */ Decimal(_length: 4, _isCompact: 1, _mantissa: (0x5928, 0xa009, 0x16d9, 0x000c,0,0,0,0)),
/* 10**24 */ Decimal(_length: 4, _isCompact: 1, _mantissa: (0x88ea, 0x299a, 0x357c, 0x0001,0,0,0,0)),
/* 10**25 */ Decimal(_length: 3, _isCompact: 1, _mantissa: (0xda7d, 0xd0f5, 0x1ef2,0,0,0,0,0)),
/* 10**26 */ Decimal(_length: 3, _isCompact: 1, _mantissa: (0x95d9, 0x4818, 0x0318,0,0,0,0,0)),
/* 10**27 */ Decimal(_length: 3, _isCompact: 0, _mantissa: (0xdbc8, 0x3a68, 0x004f,0,0,0,0,0)),
/* 10**28 */ Decimal(_length: 3, _isCompact: 1, _mantissa: (0xaf94, 0xec3d, 0x0007,0,0,0,0,0)),
/* 10**29 */ Decimal(_length: 2, _isCompact: 1, _mantissa: (0xf7f5, 0xcad2,0,0,0,0,0,0)),
/* 10**30 */ Decimal(_length: 2, _isCompact: 1, _mantissa: (0x4bfe, 0x1448,0,0,0,0,0,0)),
/* 10**31 */ Decimal(_length: 2, _isCompact: 1, _mantissa: (0x3acc, 0x0207,0,0,0,0,0,0)),
/* 10**32 */ Decimal(_length: 2, _isCompact: 1, _mantissa: (0xec47, 0x0033,0,0,0,0,0,0)),
/* 10**33 */ Decimal(_length: 2, _isCompact: 1, _mantissa: (0x313a, 0x0005,0,0,0,0,0,0)),
/* 10**34 */ Decimal(_length: 1, _isCompact: 1, _mantissa: (0x84ec,0,0,0,0,0,0,0)),
/* 10**35 */ Decimal(_length: 1, _isCompact: 1, _mantissa: (0x0d4a,0,0,0,0,0,0,0)),
/* 10**36 */ Decimal(_length: 1, _isCompact: 0, _mantissa: (0x0154,0,0,0,0,0,0,0)),
/* 10**37 */ Decimal(_length: 1, _isCompact: 1, _mantissa: (0x0022,0,0,0,0,0,0,0)),
/* 10**38 */ Decimal(_length: 1, _isCompact: 1, _mantissa: (0x0003,0,0,0,0,0,0,0))
]

// The methods in this extension exist to match the protocol requirements of
Expand Down Expand Up @@ -607,10 +607,11 @@ extension Decimal {
if isZero {
exponent = .min
} else {
let significand = Decimal(_length: _length, _mantissa: _mantissa)
let maxPowerOfTen = _powersOfTen.count
let powerOfTen = _powersOfTen.firstIndex { $0 > significand } ?? maxPowerOfTen
exponent = _exponent &- Int32(maxPowerOfTen &- powerOfTen)
let significand_ = significand
let shift =
_powersOfTenDividingUInt128Max.firstIndex { significand_ > $0 }
?? _powersOfTenDividingUInt128Max.count
exponent = _exponent &- Int32(shift)
}

return Decimal(
Expand Down
Loading

0 comments on commit aed94d4

Please sign in to comment.