From a4d338f93b209dbd0b8d1a3092fff96dc922ee38 Mon Sep 17 00:00:00 2001 From: Michael Griebling Date: Mon, 3 Jul 2023 17:14:49 -0400 Subject: [PATCH] Changed how special negative numbers like NaN, SNan, and infinity are handled. All tests pass. --- Sources/BigDecimal/BigDecimal.swift | 141 +++++++++++++----- Sources/BigDecimal/Decimal128.swift | 7 +- Sources/BigDecimal/Decimal32.swift | 31 ++-- Sources/BigDecimal/Decimal64.swift | 8 +- Sources/BigDecimal/DecimalFloatingPoint.swift | 10 +- Sources/BigDecimal/Rounding.swift | 16 +- Tests/BigDecimalTests/Decimal32Tests.swift | 29 ++-- Tests/BigDecimalTests/TestInfinityNaN.swift | 11 +- 8 files changed, 156 insertions(+), 97 deletions(-) diff --git a/Sources/BigDecimal/BigDecimal.swift b/Sources/BigDecimal/BigDecimal.swift index 0e316ef..eb71d4e 100644 --- a/Sources/BigDecimal/BigDecimal.swift +++ b/Sources/BigDecimal/BigDecimal.swift @@ -35,28 +35,30 @@ public struct BigDecimal : Comparable, Equatable, Hashable, Codable { public static let ten = Self(10) /// BigDecimal('NaN') - public static let nan = Self(.qnan) + public static let nan = Self(.nanPos) /// BigDecimal('Infinity') - public static let infinity = Self(.infinite) + public static let infinity = Self(.infPos) /// BigDecimal('sNaN') - public static let signalingNaN = Self(.snan) + public static let signalingNaN = Self(.snanPos) // MARK: - Special encodings for infinite, NaN, sNaN, etc. enum Special : Codable { - case none, qnan, snan, infinite + case none, nanPos, nanNeg, snanPos, snanNeg, infPos, infNeg + + var isNegative: Bool { [.nanNeg,.snanNeg,.infNeg].contains(self) } + var isPositive: Bool { [.nanPos,.snanPos,.infPos].contains(self) } + var isInfinity: Bool { [.infPos,.infNeg].contains(self) } + var isNan: Bool { [.nanPos,.nanNeg,.snanPos,.snanNeg].contains(self) } + var isSignalingNan: Bool { [.snanPos,.snanNeg].contains(self) } } // MARK: - Initializers - init(_ type: Special, _ payload: BInt = 0, sign: Sign = .plus) { - var load = payload.magnitude - // BInt doesn't negate 0s so kludge - if type != .none && load == 0 { load = 1 } - if sign == .minus { load.negate() } + init(_ type: Special, _ payload: Int = 0) { self.special = type - self.digits = load + self.digits = BInt(payload) self.exponent = 0 self.precision = 1 } @@ -258,7 +260,7 @@ extension BigDecimal : SignedNumeric { if x.isNaN { return Self.flagNaN() } else if x.isInfinite { - return x.isNegative ? Self.infinity : Self(.infinite, sign: .minus) + return x.isNegative ? Self.infinity : Self(.infNeg) } else { return Self(-x.digits, x.exponent) } @@ -359,13 +361,42 @@ extension BigDecimal : FloatingPoint { // MARK: - FloatingPoint Basic Operations + /// Replaces this value with the remainder of itself divided by the given + /// value. + /// + /// For two finite values `x` and `y`, the remainder `r` of dividing `x` by + /// `y` satisfies `x == y * q + r`, where `q` is the integer nearest to + /// `x / y`. If `x / y` is exactly halfway between two integers, `q` is + /// chosen to be even. Note that `q` is *not* `x / y` computed in + /// floating-point arithmetic, and that `q` may not be representable in any + /// available integer type. + /// + /// The following example calculates the remainder of dividing 8.625 by 0.75: + /// + /// var x = 8.625 + /// print(x / 0.75) + /// // Prints "11.5" + /// + /// let q = (x / 0.75).rounded(.toNearestOrEven) + /// // q == 12.0 + /// x.formRemainder(dividingBy: 0.75) + /// // x == -0.375 + /// + /// let x1 = 0.75 * q + x + /// // x1 == 8.625 + /// + /// If this value and `other` are finite numbers, the remainder is in the + /// closed range `-abs(other / 2)...abs(other / 2)`. The + /// `formRemainder(dividingBy:)` method is always exact. + /// + /// - Parameter other: The value to use when dividing this value. public mutating func formRemainder(dividingBy other: Self) { - self = self.quotientAndRemainder(other).remainder + let q = self.divide(other).rounded(.toNearestOrEven) + self -= q * other } public mutating func formTruncatingRemainder(dividingBy other: Self) { - let (q, _) = self.quotientAndRemainder(other) - self -= q * other + self = self.quotientAndRemainder(other).remainder } public mutating func formSquareRoot() { @@ -376,8 +407,42 @@ extension BigDecimal : FloatingPoint { self = self.fma(lhs, rhs, Rounding.decimal128) } + /// Rounds the value to an integral value using the specified rounding rule. + /// + /// The following example rounds a value using four different rounding rules: + /// + /// // Equivalent to the C 'round' function: + /// var w = 6.5 + /// w.round(.toNearestOrAwayFromZero) + /// // w == 7.0 + /// + /// // Equivalent to the C 'trunc' function: + /// var x = 6.5 + /// x.round(.towardZero) + /// // x == 6.0 + /// + /// // Equivalent to the C 'ceil' function: + /// var y = 6.5 + /// y.round(.up) + /// // y == 7.0 + /// + /// // Equivalent to the C 'floor' function: + /// var z = 6.5 + /// z.round(.down) + /// // z == 6.0 + /// + /// For more information about the available rounding rules, see the + /// `FloatingPointRoundingRule` enumeration. To round a value using the + /// default "schoolbook rounding", you can use the shorter `round()` method + /// instead. + /// + /// var w1 = 6.5 + /// w1.round() + /// // w1 == 7.0 + /// + /// - Parameter rule: The rounding rule to use. public mutating func round(_ rule: FloatingPointRoundingRule) { - self = Rounding(rule, Self.mc.precision).round(self) + self = self.quantize(BigDecimal.one, rule) } public var nextUp: Self { @@ -401,10 +466,6 @@ extension BigDecimal : FloatingPoint { return self <= other } - public func isTotallyOrdered(belowOrEqualTo other: Self) -> Bool { - self <= other // FIXME: - flesh out - } - public var isNormal: Bool { if self.isNaN || self.isInfinite { return false } return true @@ -469,27 +530,31 @@ extension BigDecimal { public var isFinite: Bool { !self.isNaN && !self.isInfinite } /// Is *true* if *self* is either a NaN or SNaN number - public var isNaN: Bool { [Special.qnan, .snan].contains(special) } + public var isNaN: Bool { special.isNan } /// Is *true* if *self* is a signaling NaN number - public var isSignalingNaN: Bool { self.special == .snan } + public var isSignalingNaN: Bool { special.isSignalingNan } /// Is *true* if *self* is an infinite number - public var isInfinite: Bool { self.special == .infinite } - + public var isInfinite: Bool { special.isInfinity } + /// Is *true* if *self* < 0, *false* otherwise - public var isNegative: Bool { self.signum < 0 } + public var isNegative: Bool { self.signum < 0 || special.isNegative } /// Is *true* if *self* > 0, *false* otherwise - public var isPositive: Bool { self.signum > 0 } + public var isPositive: Bool { self.signum > 0 || special.isPositive } /// Is *true* if *self* = 0, *false* otherwise - public var isZero: Bool { self.special != .none ? false : self.signum == 0 } + public var isZero: Bool { special != .none ? false : self.signum == 0 } - /// Is 0 if *self* = 0 or *self* is NaN, 1 if *self* > 0, and -1 if *self* < 0 - public var signum: Int { self.digits.signum } + /// Is 0 if *self* = 0 or *self* is NaN, 1 if *self* > 0, and -1 + /// if *self* < 0 + public var signum: Int { + special == .none ? self.digits.signum : special.isNegative ? -1 : 1 + } - /// The same value as *self* with any trailing zeros removed from its significand + /// The same value as *self* with any trailing zeros removed from its + /// significand public var trim: Self { if self.isNaN { return Self.flagNaN() @@ -542,10 +607,10 @@ extension BigDecimal { if self.isNaN { var flag = "NaN" if self.isSignalingNaN { flag = "S" + flag } - if self.digits.isNegative { return "-" + flag } + if self.isNegative { return "-" + flag } return flag } else if self.isInfinite { - return self.digits.isNegative ? "-Infinity" : "+Infinity" + return self.isNegative ? "-Infinity" : "+Infinity" } var exp = self.precision + self.exponent - 1 var s = self.digits.abs.asString() @@ -1238,13 +1303,17 @@ extension BigDecimal { } // detect nan, snan, and inf - if sl == "nan" { - let _ = Self.flagNaN() - return Self(.qnan, sign: sign) + if sl.hasPrefix("nan") { + sl.removeFirst(3) + Self.nanFlag = true // set flag + if let payload = Int(sl) { + return Self(sign == .minus ? .nanNeg : .nanPos, payload) + } + return Self(sign == .minus ? .nanNeg : .nanPos) } else if sl.hasPrefix("inf") { - return Self(.infinite, sign: sign) + return Self(sign == .minus ? .infNeg : .infPos) } else if sl == "snan" { - return Self(.snan, sign: sign) + return Self(sign == .minus ? .snanNeg : .snanPos) } for c in sl { switch c { diff --git a/Sources/BigDecimal/Decimal128.swift b/Sources/BigDecimal/Decimal128.swift index c737097..e6a0e6a 100644 --- a/Sources/BigDecimal/Decimal128.swift +++ b/Sources/BigDecimal/Decimal128.swift @@ -25,8 +25,7 @@ import BigInt /// the binary encoding format for decimal floating-point values, but the /// decimal encoding format is supported too in the library, by means of /// conversion functions between the two encoding formats. -public struct Decimal128 : DecimalType, Codable, Hashable { - +public struct Decimal128 : DecimalType, Codable, Hashable { // Decimal64 characteristics static let largestNumber = UInt128(9_999_999_999_999_999_999_999_999_999_999_999) @@ -350,9 +349,7 @@ extension Decimal128 { func asBigDecimal() -> BigDecimal { let isNegative = self.sign == .minus if self.isNaN { - return BigDecimal.flagNaN() - } else if self.isSignalingNaN { - return BigDecimal(.snan) + return BigDecimal.flagNaN(self.isSignalingNaN) } else if self.isInfinite { return isNegative ? -BigDecimal.infinity : BigDecimal.infinity } else { diff --git a/Sources/BigDecimal/Decimal32.swift b/Sources/BigDecimal/Decimal32.swift index ae1670f..cc97190 100644 --- a/Sources/BigDecimal/Decimal32.swift +++ b/Sources/BigDecimal/Decimal32.swift @@ -114,7 +114,7 @@ extension Decimal32 : FloatingPoint { } public mutating func round(_ rule: RoundingRule) { - let digits = Rounding.decimal32.precision + bid = Self(self.bd.rounded(rule)).bid } /////////////////////////////////////////////////////////////////////////// @@ -147,8 +147,7 @@ extension Decimal32 : FloatingPoint { // MARK: - Floating-point basic operations with rounding public func adding(other: Self, rounding rule: RoundingRule) -> Self { - let round = Rounding(rule, Rounding.decimal32.precision) - return Self(bid: self.bd.add(other.bd, round)) + return Self(bid: (self.bd + other.bd).rounded(rule)) } public mutating func add(other: Self, rounding rule: RoundingRule) { @@ -160,13 +159,11 @@ extension Decimal32 : FloatingPoint { } public func subtracting(other: Self, rounding rule: RoundingRule) -> Self { - let round = Rounding(rule, Rounding.decimal32.precision) - return Self(bid: self.bd.subtract(other.bd, round)) + return Self(bid: (self.bd + other.bd).rounded(rule)) } public func multiplied(by other:Self, rounding rule:RoundingRule) -> Self { - let round = Rounding(rule, Rounding.decimal32.precision) - return Self(bid: self.bd.multiply(other.bd, round)) + return Self(bid: (self.bd * other.bd).rounded(rule)) } public mutating func multiply(by other: Self, rounding rule:RoundingRule) { @@ -174,8 +171,7 @@ extension Decimal32 : FloatingPoint { } public func divided(by other: Self, rounding rule: RoundingRule) -> Self { - let round = Rounding(rule, Rounding.decimal32.precision) - return Self(bid: self.bd.divide(other.bd, round)) + return Self(bid: self.bd.divide(other.bd).rounded(rule)) } public mutating func divide(by other: Self, rounding rule: RoundingRule) { @@ -198,10 +194,7 @@ extension Decimal32 : FloatingPoint { public static func /= (lhs: inout Self, rhs: Self) { lhs = lhs / rhs } public mutating func formRemainder(dividingBy other: Self) { - let q = self / other - let qp = q.rounded(.toNearestOrEven) - let r = self.bd.remainder(dividingBy: other.bd) - print(q,r) + bid = Self(bid:self.bd.remainder(dividingBy: other.bd)).bid } public mutating func formTruncatingRemainder(dividingBy other: Self) { @@ -236,8 +229,8 @@ extension Decimal32 : FloatingPoint { /// Rounding method equivalent of the `addProduct` public mutating func addProduct(_ lhs: Self, _ rhs: Self, rounding rule: RoundingRule) { - let round = Rounding(rule, Rounding.decimal32.precision) - bid = Self(bid: self.bd.fma(lhs.bd, rhs.bd, round)).bid + bid = Self(bid: self.bd.fma(lhs.bd, rhs.bd, Rounding.decimal32) + .rounded(rule)).bid } public func isEqual(to other: Self) -> Bool { self == other } @@ -357,14 +350,12 @@ extension Decimal32 { func asBigDecimal() -> BigDecimal { let isNegative = self.sign == .minus if self.isNaN { - return BigDecimal.flagNaN() - } else if self.isSignalingNaN { - return BigDecimal(.snan) + return BigDecimal.flagNaN(self.isSignalingNaN) } else if self.isInfinite { return isNegative ? -BigDecimal.infinity : BigDecimal.infinity } else { - return BigDecimal(BInt(isNegative ? -Int(significandBitPattern) - : Int(significandBitPattern)), self.exponent) + let big = BigDecimal(BInt(significandBitPattern), self.exponent) + return isNegative ? -big : big } } diff --git a/Sources/BigDecimal/Decimal64.swift b/Sources/BigDecimal/Decimal64.swift index c35e986..e9add46 100644 --- a/Sources/BigDecimal/Decimal64.swift +++ b/Sources/BigDecimal/Decimal64.swift @@ -353,14 +353,12 @@ extension Decimal64 { func asBigDecimal() -> BigDecimal { let isNegative = self.sign == .minus if self.isNaN { - return BigDecimal.flagNaN() - } else if self.isSignalingNaN { - return BigDecimal(.snan) + return BigDecimal.flagNaN(self.isSignalingNaN) } else if self.isInfinite { return isNegative ? -BigDecimal.infinity : BigDecimal.infinity } else { - return BigDecimal(BInt(isNegative ? -Int(significandBitPattern) - : Int(significandBitPattern)), self.exponent) + let big = BigDecimal(BInt(significandBitPattern), self.exponent) + return isNegative ? -big : big } } diff --git a/Sources/BigDecimal/DecimalFloatingPoint.swift b/Sources/BigDecimal/DecimalFloatingPoint.swift index 476b9e6..1222088 100644 --- a/Sources/BigDecimal/DecimalFloatingPoint.swift +++ b/Sources/BigDecimal/DecimalFloatingPoint.swift @@ -58,7 +58,7 @@ protocol DecimalType : Codable, Hashable { /// - signaling: Pass `true` to create a signaling NaN or `false` to /// create a quiet NaN. /// - sign: Sets the sign bit in the number when `.minus`. - init(nan payload: RawSignificand, signaling: Bool, sign: Sign) + init(nan payload: BInt, signaling: Bool, sign: Sign) ////////////////////////////////////////////////////////////////// /// Essential data to extract or update from the fields @@ -312,8 +312,7 @@ extension DecimalType { } } - public init(nan payload: RawSignificand, signaling: Bool, - sign: Sign = .plus) { + public init(nan payload: BInt, signaling: Bool, sign: Sign = .plus) { let pattern = signaling ? Self.snanPattern : Self.nanPattern let man = payload > Self.largestNumber/10 ? 0 : RawBitPattern(payload) self.init(0) @@ -326,7 +325,8 @@ extension DecimalType { let max = BigDecimal(BInt(Self.largestNumber), Self.maxExponent) let sign = value.sign if value.isNaN || value.isSignalingNaN { - self.init(nan: 0, signaling: value.isSignalingNaN, sign: sign) + let load = value.magnitude.digits + self.init(nan: load, signaling: value.isSignalingNaN, sign: sign) } else if value.isInfinite { let x = Self.infinite(sign) self.init(RawData(x)) @@ -573,7 +573,7 @@ extension DecimalType { } if nan { - return RawSignificand(Self(nan: mant, signaling: false).bid) // ( sign, RawSignificand(mant) } + return RawSignificand(Self(nan: BInt(mant), signaling: false).bid) } else { let value = Self(sign: sign, exponentBitPattern: exp, significandBitPattern: mant) diff --git a/Sources/BigDecimal/Rounding.swift b/Sources/BigDecimal/Rounding.swift index 418099c..114abd0 100644 --- a/Sources/BigDecimal/Rounding.swift +++ b/Sources/BigDecimal/Rounding.swift @@ -39,11 +39,14 @@ public struct Rounding: Equatable { // MARK: Constants /// Decimal32 rounding: .toNearestOrEven, 7 - public static let decimal32=Rounding(.toNearestOrEven, Decimal32.maxDigits) + public static let decimal32 = Rounding(.toNearestOrEven, + Decimal32.maxDigits) /// Decimal64 rounding: .toNearestOrEven, 16 - public static let decimal64 = Rounding(.toNearestOrEven, Decimal64.maxDigits) + public static let decimal64 = Rounding(.toNearestOrEven, + Decimal64.maxDigits) /// Decimal128 rounding: .toNearestOrEven, 34 - public static let decimal128 = Rounding(.toNearestOrEven, Decimal128.maxDigits) + public static let decimal128 = Rounding(.toNearestOrEven, + Decimal128.maxDigits) // MARK: - Initializer @@ -76,11 +79,8 @@ public struct Rounding: Equatable { /// - x: The value to be rounded /// - Returns: The value of *x* rounded according to *self* public func round(_ x: BigDecimal) -> BigDecimal { - if x.isNaN { - if x.isSignalingNaN { return x } - let _ = BigDecimal.flagNaN() - return x - } else if x.isInfinite { return x } + if x.isNaN { return BigDecimal.flagNaN() } + else if x.isInfinite { return x } let d = x.precision - self.precision if d <= 0 { return x diff --git a/Tests/BigDecimalTests/Decimal32Tests.swift b/Tests/BigDecimalTests/Decimal32Tests.swift index faf3a84..6c340a7 100644 --- a/Tests/BigDecimalTests/Decimal32Tests.swift +++ b/Tests/BigDecimalTests/Decimal32Tests.swift @@ -5114,9 +5114,6 @@ final class Decimal32Tests: XCTestCase { switch test.id { case "bid32_from_string": - if testID == 57 { - print() - } let t1 = getNumber(test.istr) let dtest = Decimal32(UInt32(test.res)) let error = String(format: "0x%08X[\(dtest)] != 0x%08X[\(t1)]", @@ -5425,16 +5422,14 @@ final class Decimal32Tests: XCTestCase { XCTAssert(Decimal32.signalingNaN.description == "SNaN") print("Decimal32.Infinity =", Decimal32.infinity) XCTAssert(Decimal32.infinity.description == "+Infinity") - - var a1 = Decimal32(8.625); let b1 = Decimal32(0.75) - let rem = a1.remainder(dividingBy: b1) - let rem2 = a1.truncatingRemainder(dividingBy: b1) - print("\(a1).formRemainder(dividingBy: \(b1) = ", rem, rem2) - XCTAssert(rem == Decimal32(-0.375)) - a1 = Decimal32(8.625) - let q = (a1/b1).rounded(.towardZero); print(q) - a1 = a1 - q * b1 - print("\(a1)") + + var a1 = Decimal32(8.625); let b1 = Decimal32(0.75) + let rem = a1.remainder(dividingBy: b1) + XCTAssert(rem == Decimal32(-0.375)) + a1 = Decimal32(8.625) + let q = a1.divided(by:b1, rounding: .toNearestOrEven); print(q) + a1 = a1 - q * b1 + print("\(a1)") // let x2 = Decimal32(56.7) @@ -5495,9 +5490,9 @@ final class Decimal32Tests: XCTestCase { /// Check min/max values XCTAssertEqual(Decimal32.greatestFiniteMagnitude.description, - "9.999999e+96") - XCTAssertEqual(Decimal32.leastNonzeroMagnitude.description, "1e-101") - XCTAssertEqual(Decimal32.leastNormalMagnitude.description, "9.999999e-95") + "9.999999E+96") + XCTAssertEqual(Decimal32.leastNonzeroMagnitude.description, "1E-101") + XCTAssertEqual(Decimal32.leastNormalMagnitude.description, "9.999999E-95") /// Verify various string and integer encodings test("-7.50", result: "A23003D0") @@ -5614,7 +5609,7 @@ final class Decimal32Tests: XCTestCase { test("NaN123456", result: "7c028e56") test("NaN799799", result: "7c0f7fdf") test("NaN999999", result: "7c03fcff") - + test("-0E+90", result: "c3f00000") // fold-down full sequence test("1E+96", result: "47f00000") diff --git a/Tests/BigDecimalTests/TestInfinityNaN.swift b/Tests/BigDecimalTests/TestInfinityNaN.swift index 4e5d73d..7072661 100644 --- a/Tests/BigDecimalTests/TestInfinityNaN.swift +++ b/Tests/BigDecimalTests/TestInfinityNaN.swift @@ -101,7 +101,13 @@ final class TestInfinityNaN: XCTestCase { func testSub() throws { for t in testsSub { - XCTAssertEqual((BigDecimal(t.x) - BigDecimal(t.y)).asString(), t.result) + var n = BigDecimal(t.x) - BigDecimal(t.y) + let s = n.asString() + if s != t.result { + print(s, t.result) + n = BigDecimal(t.x) - BigDecimal(t.y) + } + XCTAssertEqual(s, t.result) } XCTAssertTrue(BigDecimal.nanFlag) } @@ -360,6 +366,9 @@ final class TestInfinityNaN: XCTestCase { func testQuantize() throws { for t in testsQuantize { + let x = BigDecimal(t.x) + let y = BigDecimal(t.y) + let r = x.quantize(y, .toNearestOrEven) XCTAssertEqual(BigDecimal(t.x).quantize(BigDecimal(t.y), RoundingRule.toNearestOrEven).asString(), t.result) }