Skip to content

Commit

Permalink
Changed how special negative numbers like NaN, SNan, and infinity are…
Browse files Browse the repository at this point in the history
… handled.

All tests pass.
  • Loading branch information
mgriebling committed Jul 3, 2023
1 parent 7ef8f7d commit a4d338f
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 97 deletions.
141 changes: 105 additions & 36 deletions Sources/BigDecimal/BigDecimal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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() {
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 2 additions & 5 deletions Sources/BigDecimal/Decimal128.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand Down
31 changes: 11 additions & 20 deletions Sources/BigDecimal/Decimal32.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

///////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -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) {
Expand All @@ -160,22 +159,19 @@ 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) {
self = self.multiplied(by: other, rounding: rule)
}

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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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
}
}

Expand Down
8 changes: 3 additions & 5 deletions Sources/BigDecimal/Decimal64.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
10 changes: 5 additions & 5 deletions Sources/BigDecimal/DecimalFloatingPoint.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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))
Expand Down Expand Up @@ -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)
Expand Down
Loading

0 comments on commit a4d338f

Please sign in to comment.