NumericAnnex/Sources/Rational+SignedNumeric.swift

269 lines
8.1 KiB
Swift

//
// Rational+SignedNumeric.swift
// NumericAnnex
//
// Created by Xiaodi Wu on 4/15/17.
//
extension Rational : Numeric {
// @_transparent // @_inlineable
public init?<U>(exactly source: U) where U : BinaryInteger {
guard let t = T(exactly: source) else { return nil }
// Ensure that `t.magnitude` is representable as a `T`.
guard let _ = T(exactly: t.magnitude) else { return nil }
self.numerator = t
self.denominator = 1
}
// @_transparent // @_inlineable
public static func + (lhs: Rational, rhs: Rational) -> Rational {
if lhs.denominator == 0 {
if rhs.denominator != 0 || lhs.numerator == 0 { return lhs }
if lhs.numerator > 0 { return rhs.numerator < 0 ? .nan : rhs }
return rhs.numerator > 0 ? .nan : rhs
}
if rhs.denominator == 0 { return rhs }
let ldm = lhs.denominator.magnitude
let rdm = rhs.denominator.magnitude
let gcd = T.Magnitude.gcd(ldm, rdm)
let a = T(rdm / gcd * lhs.numerator.magnitude)
let b = T(ldm / gcd * rhs.numerator.magnitude)
let n = lhs.sign == .plus
? (rhs.sign == .plus ? a + b : a - b)
: (rhs.sign == .plus ? b - a : -a - b)
let d = T(ldm / gcd * rdm)
return Rational(numerator: n, denominator: d).canonical
}
@_transparent // @_inlineable
public static func += (lhs: inout Rational, rhs: Rational) {
lhs = lhs + rhs
}
@_transparent // @_inlineable
public static func - (lhs: Rational, rhs: Rational) -> Rational {
return lhs + (-rhs)
}
@_transparent // @_inlineable
public static func -= (lhs: inout Rational, rhs: Rational) {
lhs = lhs + (-rhs)
}
// @_transparent // @_inlineable
public static func * (lhs: Rational, rhs: Rational) -> Rational {
if lhs.denominator == 0 {
if rhs.numerator == 0 { return .nan }
return rhs.sign == .minus ? -lhs : lhs
}
if rhs.denominator == 0 {
if lhs.numerator == 0 { return .nan }
return lhs.sign == .minus ? -rhs : rhs
}
let lnm = lhs.numerator.magnitude, ldm = lhs.denominator.magnitude
let rnm = rhs.numerator.magnitude, rdm = rhs.denominator.magnitude
// Note that if `T` is a signed fixed-width integer type, `gcd(lnm, rdm)` or
// `gcd(rnm, ldm)` could be equal to `-T.min`, which is not representable as
// a `T`. This is why the following arithmetic is performed with values of
// type `T.Magnitude`.
let a = T.Magnitude.gcd(lnm, rdm)
let b = T.Magnitude.gcd(rnm, ldm)
let n = lhs.sign == rhs.sign
? T(lnm / a * (rnm / b))
: -T(lnm / a * (rnm / b))
let d = T(ldm / b * (rdm / a))
return Rational(numerator: n, denominator: d)
}
@_transparent // @_inlineable
public static func *= (lhs: inout Rational, rhs: Rational) {
lhs = lhs * rhs
}
}
extension BinaryInteger {
/// Creates a new binary integer from the given rational value, if it can be
/// represented exactly.
///
/// If `source` is not representable exactly, the result is `nil`.
///
/// - Parameters:
/// - source: A rational value to convert to a binary integer.
@_transparent // @_inlineable
public init?<U>(exactly source: Rational<U>) {
let (whole, fraction) = source.mixed
guard fraction.isZero, let exact = Self(exactly: whole) else { return nil }
self = exact
}
/// Creates a new binary integer from the given rational value, truncating any
/// fractional part.
///
/// If `source` is outside the bounds of this type after truncation, a runtime
/// error may occur.
///
/// - Parameters:
/// - source: A rational value to convert to a binary integer.
@_transparent // @_inlineable
public init<U>(_ source: Rational<U>) {
self = Self(source.mixed.whole)
}
}
extension Rational : SignedNumeric {
@_transparent // @_inlineable
public static prefix func - (operand: Rational) -> Rational {
return Rational(
numerator: -operand.numerator, denominator: operand.denominator
)
}
@_transparent // @_inlineable
public mutating func negate() {
numerator.negate()
}
}
extension Rational {
/// Returns the quotient obtained by dividing the first value by the second,
/// trapping in case of arithmetic overflow.
///
/// - Parameters:
/// - lhs: The value to divide.
/// - rhs: The value by which to divide `lhs`.
@_transparent // @_inlineable
public static func / (lhs: Rational, rhs: Rational) -> Rational {
return lhs * rhs.reciprocal()
}
/// Divides the left-hand side by the right-hand side and stores the quotient
/// in the left-hand side, trapping in case of arithmetic overflow.
///
/// - Parameters:
/// - lhs: The value to divide.
/// - rhs: The value by which to divide `lhs`.
@_transparent // @_inlineable
public static func /= (lhs: inout Rational, rhs: Rational) {
lhs = lhs * rhs.reciprocal()
}
/// Returns this value rounded to an integral value using the specified
/// rounding rule.
///
/// ```swift
/// let x = 7 / 2 as Rational<Int>
/// print(x.rounded()) // Prints "4"
/// print(x.rounded(.towardZero)) // Prints "3"
/// print(x.rounded(.up)) // Prints "4"
/// print(x.rounded(.down)) // Prints "3"
/// ```
///
/// See the `FloatingPointRoundingRule` enumeration for more information about
/// the available rounding rules.
///
/// - Parameters:
/// - rule: The rounding rule to use.
///
/// - SeeAlso: `round(_:)`, `FloatingPointRoundingRule`
@_transparent // @_inlineable
public func rounded(
_ rule: RoundingRule = .toNearestOrAwayFromZero
) -> Rational {
var t = self
t.round(rule)
return t
}
/// Rounds the value to an integral value using the specified rounding rule.
///
/// ```swift
/// var x = 7 / 2 as Rational<Int>
/// x.round() // x == 4
///
/// var x = 7 / 2 as Rational<Int>
/// x.round(.towardZero) // x == 3
///
/// var x = 7 / 2 as Rational<Int>
/// x.round(.up) // x == 4
///
/// var x = 7 / 2 as Rational<Int>
/// x.round(.down) // x == 3
/// ```
///
/// See the `FloatingPointRoundingRule` enumeration for more information about
/// the available rounding rules.
///
/// - Parameters:
/// - rule: The rounding rule to use.
///
/// - SeeAlso: `round(_:)`, `FloatingPointRoundingRule`
@_transparent // @_inlineable
public mutating func round(_ rule: RoundingRule = .toNearestOrAwayFromZero) {
if denominator == 0 { return }
let f: T
(numerator, f) = numerator.quotientAndRemainder(dividingBy: denominator)
// Rounding rules only come into play if the fractional part is non-zero.
if f != 0 {
switch rule {
case .toNearestOrAwayFromZero:
fallthrough
case .toNearestOrEven:
switch denominator.magnitude.quotientAndRemainder(
dividingBy: f.magnitude
) {
case (2, 0): // Tie.
if rule == .toNearestOrEven && numerator % 2 == 0 { break }
fallthrough
case (1, _): // Nearest is away from zero.
if f > 0 { numerator += 1 } else { numerator -= 1 }
default: // Nearest is toward zero.
break
}
case .up:
if f > 0 { numerator += 1 }
case .down:
if f < 0 { numerator -= 1 }
case .towardZero:
break
case .awayFromZero:
if f > 0 { numerator += 1 } else { numerator -= 1 }
}
}
denominator = 1
}
}
/// Returns the absolute value (magnitude) of `x`.
@_transparent
public func abs<T>(_ x: Rational<T>) -> Rational<T> {
return x.magnitude
}
/// Returns the closest integral value greater than or equal to `x`.
@_transparent
public func ceil<T>(_ x: Rational<T>) -> Rational<T> {
return x.rounded(.up)
}
/// Returns the closest integral value less than or equal to `x`.
@_transparent
public func floor<T>(_ x: Rational<T>) -> Rational<T> {
return x.rounded(.down)
}
/// Returns the closest integral value; if two values are equally close, returns
/// the one with greater magnitude.
@_transparent
public func round<T>(_ x: Rational<T>) -> Rational<T> {
return x.rounded()
}
/// Returns the closest integral value with magnitude less than or equal to `x`.
@_transparent
public func trunc<T>(_ x: Rational<T>) -> Rational<T> {
return x.rounded(.towardZero)
}