Skip to content

Synchronize Decimal overlay and corelibs-foundation implementations #3056

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions Darwin/Foundation-swiftoverlay-Tests/TestDecimal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,13 @@ class TestDecimal : XCTestCase {
XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN e4")
XCTAssertNotEqual(.noError, NSDecimalMultiplyByPowerOf10(&result, &NaN, 5, .plain))
XCTAssertTrue(NSDecimalIsNotANumber(&result), "NaN e5")

XCTAssertFalse(Double(truncating: NSDecimalNumber(decimal: Decimal(0))).isNaN)
XCTAssertTrue(Decimal(Double.leastNonzeroMagnitude).isNaN)
XCTAssertTrue(Decimal(Double.leastNormalMagnitude).isNaN)
XCTAssertTrue(Decimal(Double.greatestFiniteMagnitude).isNaN)
XCTAssertTrue(Decimal(Double("1e-129")!).isNaN)
XCTAssertTrue(Decimal(Double("0.1e-128")!).isNaN)
}

func test_NegativeAndZeroMultiplication() {
Expand Down Expand Up @@ -597,4 +604,95 @@ class TestDecimal : XCTestCase {
func test_unconditionallyBridgeFromObjectiveC() {
XCTAssertEqual(Decimal(), Decimal._unconditionallyBridgeFromObjectiveC(nil))
}

func test_parseDouble() throws {
XCTAssertEqual(Decimal(Double(0.0)), Decimal(Int.zero))
XCTAssertEqual(Decimal(Double(-0.0)), Decimal(Int.zero))

// These values can only be represented as Decimal.nan
XCTAssertEqual(Decimal(Double.nan), Decimal.nan)
XCTAssertEqual(Decimal(Double.signalingNaN), Decimal.nan)

// These values are out out range for Decimal
XCTAssertEqual(Decimal(-Double.leastNonzeroMagnitude), Decimal.nan)
XCTAssertEqual(Decimal(Double.leastNonzeroMagnitude), Decimal.nan)
XCTAssertEqual(Decimal(-Double.leastNormalMagnitude), Decimal.nan)
XCTAssertEqual(Decimal(Double.leastNormalMagnitude), Decimal.nan)
XCTAssertEqual(Decimal(-Double.greatestFiniteMagnitude), Decimal.nan)
XCTAssertEqual(Decimal(Double.greatestFiniteMagnitude), Decimal.nan)

// SR-13837
let testDoubles: [(Double, String)] = [
(1.8446744073709550E18, "1844674407370954752"),
(1.8446744073709551E18, "1844674407370954752"),
(1.8446744073709552E18, "1844674407370955264"),
(1.8446744073709553E18, "1844674407370955264"),
(1.8446744073709554E18, "1844674407370955520"),
(1.8446744073709555E18, "1844674407370955520"),

(1.8446744073709550E19, "18446744073709547520"),
(1.8446744073709551E19, "18446744073709552640"),
(1.8446744073709552E19, "18446744073709552640"),
(1.8446744073709553E19, "18446744073709552640"),
(1.8446744073709554E19, "18446744073709555200"),
(1.8446744073709555E19, "18446744073709555200"),

(1.8446744073709550E20, "184467440737095526400"),
(1.8446744073709551E20, "184467440737095526400"),
(1.8446744073709552E20, "184467440737095526400"),
(1.8446744073709553E20, "184467440737095526400"),
(1.8446744073709554E20, "184467440737095552000"),
(1.8446744073709555E20, "184467440737095552000"),
]

for (d, s) in testDoubles {
XCTAssertEqual(Decimal(d), Decimal(string: s))
XCTAssertEqual(Decimal(d).description, try XCTUnwrap(Decimal(string: s)).description)
}
}

func test_initExactly() {
// This really requires some tests using a BinaryInteger of bitwidth > 128 to test failures.
let d1 = Decimal(exactly: UInt64.max)
XCTAssertNotNil(d1)
XCTAssertEqual(d1?.description, UInt64.max.description)
XCTAssertEqual(d1?._length, 4)

let d2 = Decimal(exactly: Int64.min)
XCTAssertNotNil(d2)
XCTAssertEqual(d2?.description, Int64.min.description)
XCTAssertEqual(d2?._length, 4)

let d3 = Decimal(exactly: Int64.max)
XCTAssertNotNil(d3)
XCTAssertEqual(d3?.description, Int64.max.description)
XCTAssertEqual(d3?._length, 4)

let d4 = Decimal(exactly: Int32.min)
XCTAssertNotNil(d4)
XCTAssertEqual(d4?.description, Int32.min.description)
XCTAssertEqual(d4?._length, 2)

let d5 = Decimal(exactly: Int32.max)
XCTAssertNotNil(d5)
XCTAssertEqual(d5?.description, Int32.max.description)
XCTAssertEqual(d5?._length, 2)

let d6 = Decimal(exactly: 0)
XCTAssertNotNil(d6)
XCTAssertEqual(d6, Decimal.zero)
XCTAssertEqual(d6?.description, "0")
XCTAssertEqual(d6?._length, 0)

let d7 = Decimal(exactly: 1)
XCTAssertNotNil(d7)
XCTAssertEqual(d7?.description, "1")
XCTAssertEqual(d7?._length, 1)

let d8 = Decimal(exactly: -1)
XCTAssertNotNil(d8)
XCTAssertEqual(d8?.description, "-1")
XCTAssertEqual(d8?._length, 1)
}

}
101 changes: 82 additions & 19 deletions Darwin/Foundation-swiftoverlay/Decimal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public func pow(_ x: Decimal, _ y: Int) -> Decimal {
}

extension Decimal : Hashable, Comparable {
// (Used by `doubleValue`.)
private subscript(index: UInt32) -> UInt16 {
get {
switch index {
Expand All @@ -42,6 +43,7 @@ extension Decimal : Hashable, Comparable {
}
}

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

var mantissaContainer = try container.nestedUnkeyedContainer(forKey: .mantissa)
var mantissa: (CUnsignedShort, CUnsignedShort, CUnsignedShort, CUnsignedShort,
CUnsignedShort, CUnsignedShort, CUnsignedShort, CUnsignedShort) = (0,0,0,0,0,0,0,0)
CUnsignedShort, CUnsignedShort, CUnsignedShort, CUnsignedShort) = (0,0,0,0,0,0,0,0)
mantissa.0 = try mantissaContainer.decode(CUnsignedShort.self)
mantissa.1 = try mantissaContainer.decode(CUnsignedShort.self)
mantissa.2 = try mantissaContainer.decode(CUnsignedShort.self)
Expand All @@ -134,12 +136,12 @@ extension Decimal : Codable {
mantissa.6 = try mantissaContainer.decode(CUnsignedShort.self)
mantissa.7 = try mantissaContainer.decode(CUnsignedShort.self)

self = Decimal(_exponent: exponent,
_length: length,
_isNegative: CUnsignedInt(isNegative ? 1 : 0),
_isCompact: CUnsignedInt(isCompact ? 1 : 0),
_reserved: 0,
_mantissa: mantissa)
self.init(_exponent: exponent,
_length: length,
_isNegative: CUnsignedInt(isNegative ? 1 : 0),
_isCompact: CUnsignedInt(isCompact ? 1 : 0),
_reserved: 0,
_mantissa: mantissa)
}

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

// FIXME(integers): implement properly
public init?<T : BinaryInteger>(exactly source: T) {
fatalError()
let zero = 0 as T

if source == zero {
self = Decimal.zero
return
}

let negative: UInt32 = (T.isSigned && source < zero) ? 1 : 0
var mantissa = source.magnitude
var exponent: Int32 = 0

let maxExponent = Int8.max
while mantissa.isMultiple(of: 10) && (exponent < maxExponent) {
exponent += 1
mantissa /= 10
}

// If the mantissa still requires more than 128 bits of storage then it is too large.
if mantissa.bitWidth > 128 && (mantissa >> 128 != zero) { return nil }

let mantissaParts: (UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16, UInt16)
let loWord = UInt64(truncatingIfNeeded: mantissa)
var length = ((loWord.bitWidth - loWord.leadingZeroBitCount) + (UInt16.bitWidth - 1)) / UInt16.bitWidth
mantissaParts.0 = UInt16(truncatingIfNeeded: loWord >> 0)
mantissaParts.1 = UInt16(truncatingIfNeeded: loWord >> 16)
mantissaParts.2 = UInt16(truncatingIfNeeded: loWord >> 32)
mantissaParts.3 = UInt16(truncatingIfNeeded: loWord >> 48)

let hiWord = mantissa.bitWidth > 64 ? UInt64(truncatingIfNeeded: mantissa >> 64) : 0
if hiWord != 0 {
length = 4 + ((hiWord.bitWidth - hiWord.leadingZeroBitCount) + (UInt16.bitWidth - 1)) / UInt16.bitWidth
mantissaParts.4 = UInt16(truncatingIfNeeded: hiWord >> 0)
mantissaParts.5 = UInt16(truncatingIfNeeded: hiWord >> 16)
mantissaParts.6 = UInt16(truncatingIfNeeded: hiWord >> 32)
mantissaParts.7 = UInt16(truncatingIfNeeded: hiWord >> 48)
} else {
mantissaParts.4 = 0
mantissaParts.5 = 0
mantissaParts.6 = 0
mantissaParts.7 = 0
}

self = Decimal(_exponent: exponent, _length: UInt32(length), _isNegative: negative, _isCompact: 1, _reserved: 0, _mantissa: mantissaParts)
}

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

@available(*, unavailable, message: "Decimal does not yet fully adopt FloatingPoint.")
public static var infinity: Decimal { fatalError("Decimal does not yet fully adopt FloatingPoint") }
@available(*, unavailable, message: "Decimal does not fully adopt FloatingPoint.")
public static var infinity: Decimal { fatalError("Decimal does not fully adopt FloatingPoint") }

@available(*, unavailable, message: "Decimal does not yet fully adopt FloatingPoint.")
public static var signalingNaN: Decimal { fatalError("Decimal does not yet fully adopt FloatingPoint") }
@available(*, unavailable, message: "Decimal does not fully adopt FloatingPoint.")
public static var signalingNaN: Decimal { fatalError("Decimal does not fully adopt FloatingPoint") }

public static var quietNaN: Decimal {
return Decimal(
Expand Down Expand Up @@ -411,7 +454,7 @@ extension Decimal {
}

public init(_ value: Double) {
precondition(!value.isInfinite, "Decimal does not yet fully adopt FloatingPoint")
precondition(!value.isInfinite, "Decimal does not fully adopt FloatingPoint")
if value.isNaN {
self = Decimal.nan
} else if value == 0.0 {
Expand All @@ -420,16 +463,36 @@ extension Decimal {
self = Decimal()
let negative = value < 0
var val = negative ? -1 * value : value
var exponent = 0
var exponent: Int8 = 0

// Try to get val as close to UInt64.max whilst adjusting the exponent
// to reduce the number of digits after the decimal point.
while val < Double(UInt64.max - 1) {
guard exponent > Int8.min else {
self = Decimal.nan
return
}
val *= 10.0
exponent -= 1
}
while Double(UInt64.max - 1) < val {
while Double(UInt64.max) <= val {
guard exponent < Int8.max else {
self = Decimal.nan
return
}
val /= 10.0
exponent += 1
}
var mantissa = UInt64(val)

var mantissa: UInt64
let maxMantissa = Double(UInt64.max).nextDown
if val > maxMantissa {
// UInt64(Double(UInt64.max)) gives an overflow error; this is the largest
// mantissa that can be set.
mantissa = UInt64(maxMantissa)
} else {
mantissa = UInt64(val)
}

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

@available(*, unavailable, message: "Decimal does not yet fully adopt FloatingPoint.")
public mutating func formTruncatingRemainder(dividingBy other: Decimal) { fatalError("Decimal does not yet fully adopt FloatingPoint") }
@available(*, unavailable, message: "Decimal does not fully adopt FloatingPoint.")
public mutating func formTruncatingRemainder(dividingBy other: Decimal) { fatalError("Decimal does not fully adopt FloatingPoint") }
}

extension Decimal : _ObjectiveCBridgeable {
Expand Down
Loading