Skip to content

Instantly share code, notes, and snippets.

@mklbtz
Last active September 3, 2017 19:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mklbtz/e8ebca10e82eec657593c6bab69ab635 to your computer and use it in GitHub Desktop.
Save mklbtz/e8ebca10e82eec657593c6bab69ab635 to your computer and use it in GitHub Desktop.
A Number-like type that can represent algebraic expressions
// A Number-like type that can represent algebraic expressions.
public enum Algebra {
case scalar(Double)
case variable(name: String, () -> Double)
indirect case add(Algebra, Algebra)
indirect case subtract(Algebra, Algebra)
indirect case multiply(Algebra, by: Algebra)
indirect case divide(Algebra, by: Algebra)
indirect case magnitude(Algebra)
indirect case raise(Algebra, toPower: Int)
indirect case sqrt(Algebra)
indirect case rounding(Algebra, FloatingPointRoundingRule)
}
extension Algebra {
internal var value: Double {
return result
}
public var result: Double {
switch self {
case let .scalar(value):
return value
case let .variable(_, f):
return f()
case let .add(lhs, rhs):
return lhs.value + rhs.value
case let .subtract(lhs, rhs):
return lhs.value - rhs.value
case let .multiply(lhs, rhs):
return lhs.value * rhs.value
case let .divide(lhs, rhs):
return lhs.value / rhs.value
case let .magnitude(expr):
return expr.value.magnitude
case let .raise(base, power):
return base.value.raised(toPower: power)
case let .sqrt(expr):
return expr.value.squareRoot()
case let .rounding(expr, rule):
return expr.value.rounded(rule)
}
}
}
extension Algebra: CustomStringConvertible {
public var description: String {
return "\(expressionDescription) = \(value)"
}
private func munge(_ value: Double) -> String {
let description = "\(value)"
if value == value.rounded() {
return String(description.dropLast(2))
} else {
return description
}
}
public var expressionDescription: String {
switch self {
// Scalar Values
case let .scalar(value):
return munge(value)
case let .variable(name, _):
return name
// Addition
case let .add(lexpr, .scalar(rval)) where rval < 0:
return Algebra.subtract(lexpr, Algebra(-rval)).expressionDescription
case let .add(lexpr, rexpr):
return "(\(lexpr.expressionDescription) + \(rexpr.expressionDescription))"
// Subtraction
case let .subtract(lexpr, .scalar(rhs)) where rhs < 0:
return Algebra.add(lexpr, Algebra(-rhs)).expressionDescription
case let .subtract(lexpr, rexpr):
return "(\(lexpr.expressionDescription) ﹣ \(rexpr.expressionDescription))"
// Multiplication
case .multiply(.scalar(-1.0), .scalar(let value)),
.multiply(.scalar(let value), .scalar(-1.0)):
return "-\(munge(value))"
case .multiply(.scalar(-1.0), let expr),
.multiply(let expr, .scalar(-1.0)):
return "-(\(expr.expressionDescription))"
case let .multiply(lexpr, rexpr):
return "\(lexpr.expressionDescription) × \(rexpr.expressionDescription)"
// Division
case let .divide(.scalar(lval), .scalar(rval)):
return "\(munge(lval))/\(munge(rval))"
case let .divide(.scalar(lval), rexpr):
return "\(munge(lval))/(\(rexpr.expressionDescription))"
case let .divide(lexpr, .scalar(rval)):
return "(\(lexpr.expressionDescription))/\(munge(rval))"
case let .divide(lexpr, rexpr):
return "(\(lexpr.expressionDescription)) ÷ (\(rexpr.expressionDescription))"
// Miscellaneous
case let .magnitude(expr):
return "|\(expr.expressionDescription)|"
case let .raise(expr, power):
return "\(expr.expressionDescription)^\(power)"
case let .sqrt(expr):
return "√(\(expr.expressionDescription)"
case let .rounding(expr, rule):
return "round(\(expr.expressionDescription), \(rule)"
}
}
}
extension Algebra: ExpressibleByFloatLiteral {
public init(floatLiteral value: Double) {
self = .scalar(value)
}
}
extension Algebra: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) {
self = .scalar(Double(value))
}
}
extension Algebra: Equatable {
public static func ==(lhs: Algebra, rhs: Algebra) -> Bool {
return lhs.value == rhs.value
}
}
extension Algebra: Hashable {
public var hashValue: Int {
return description.hashValue
}
}
extension Algebra: Comparable {
public static func <(lhs: Algebra, rhs: Algebra) -> Bool {
return lhs.value < rhs.value
}
}
extension Algebra: Strideable {
public typealias Stride = Double
public func distance(to other: Algebra) -> Stride {
return value.distance(to: other.value)
}
public func advanced(by n: Stride) -> Algebra {
return .add(self, .scalar(n))
}
}
extension Algebra: SignedNumeric {
public init?<T>(exactly source: T) where T : BinaryInteger {
guard let dbl = Double(exactly: source) else { return nil }
self = .scalar(dbl)
}
public var magnitude: Algebra {
return .magnitude(self)
}
public static func +=(lhs: inout Algebra, rhs: Algebra) {
lhs = .add(lhs, rhs)
}
public static func -=(lhs: inout Algebra, rhs: Algebra) {
lhs = .subtract(lhs, rhs)
}
public static func *=(lhs: inout Algebra, rhs: Algebra) {
lhs = .multiply(lhs, by: rhs)
}
public static func +(lhs: Algebra, rhs: Algebra) -> Algebra {
return .add(lhs, rhs)
}
public static func -(lhs: Algebra, rhs: Algebra) -> Algebra {
return .subtract(lhs, rhs)
}
public static func *(lhs: Algebra, rhs: Algebra) -> Algebra {
return .multiply(lhs, by: rhs)
}
}
extension Algebra: FloatingPoint {
public typealias Exponent = Double.Exponent
public init(sign: FloatingPointSign, exponent: Double.Exponent, significand: Algebra) {
self = Algebra(Double(sign: sign, exponent: exponent, significand: significand.value))
}
public init(signOf s: Algebra, magnitudeOf m: Algebra) {
switch s.sign {
case .plus:
self = .magnitude(m)
case .minus:
self = -Algebra.magnitude(m)
}
}
public init(_ value: UInt8) {
self = Algebra(Double(value))
}
public init(_ value: Int8) {
self = Algebra(Double(value))
}
public init(_ value: UInt16) {
self = Algebra(Double(value))
}
public init(_ value: Int16) {
self = Algebra(Double(value))
}
public init(_ value: UInt32) {
self = Algebra(Double(value))
}
public init(_ value: Int32) {
self = Algebra(Double(value))
}
public init(_ value: UInt64) {
self = Algebra(Double(value))
}
public init(_ value: Int64) {
self = Algebra(Double(value))
}
public init(_ value: UInt) {
self = Algebra(Double(value))
}
public init(_ value: Int) {
self = Algebra(Double(value))
}
public static var radix: Int {
return Double.radix
}
public static var nan: Algebra {
return Algebra(Double.nan)
}
public static var signalingNaN: Algebra {
return Algebra(Double.signalingNaN)
}
public static var infinity: Algebra {
return Algebra(Double.infinity)
}
public static var greatestFiniteMagnitude: Algebra {
return Algebra(Double.greatestFiniteMagnitude)
}
public static var pi: Algebra {
return Algebra(Double.pi)
}
public static var leastNormalMagnitude: Algebra {
return Algebra(Double.leastNormalMagnitude)
}
public static var leastNonzeroMagnitude: Algebra {
return Algebra(Double.leastNonzeroMagnitude)
}
public var ulp: Algebra {
return Algebra(value.ulp)
}
public var sign: FloatingPointSign {
return value.sign
}
public var exponent: Double.Exponent {
return value.exponent
}
public var significand: Algebra {
return Algebra(value.significand)
}
public static func /(lhs: Algebra, rhs: Algebra) -> Algebra {
return .divide(lhs, by: rhs)
}
public static func /=(lhs: inout Algebra, rhs: Algebra) {
lhs = lhs / rhs
}
public mutating func formRemainder(dividingBy other: Algebra) {
// NOTE: Remainders are not represented by a case
self = Algebra(self.value.remainder(dividingBy: other.value))
}
public mutating func formTruncatingRemainder(dividingBy other: Algebra) {
// NOTE: Remainders are not represented by a case
self = Algebra(self.value.truncatingRemainder(dividingBy: other.value))
}
public mutating func addProduct(_ lhs: Algebra, _ rhs: Algebra) {
self += lhs * rhs
}
public mutating func formSquareRoot() {
self = .sqrt(self)
}
public mutating func round(_ rule: FloatingPointRoundingRule) {
self = .rounding(self, rule)
}
public func isEqual(to other: Algebra) -> Bool {
return self.value.isEqual(to: other.value)
}
public func isLess(than other: Algebra) -> Bool {
return self.value.isLess(than: other.value)
}
public func isLessThanOrEqualTo(_ other: Algebra) -> Bool {
return self.value.isLessThanOrEqualTo(other.value)
}
public func isTotallyOrdered(belowOrEqualTo other: Algebra) -> Bool {
return self.value.isTotallyOrdered(belowOrEqualTo: other.value)
}
public var nextUp: Algebra {
return self + Algebra.leastNonzeroMagnitude
}
public var isZero: Bool {
return value.isZero
}
public var isNormal: Bool {
switch self {
case .scalar, .variable, .raise :
return value.isNormal
case .magnitude(let expr), .sqrt(let expr), .rounding(let expr, _):
return expr.isNormal
case .add(let lhs, let rhs),
.subtract(let lhs, let rhs),
.multiply(let lhs, let rhs),
.divide(let lhs, let rhs):
return lhs.isNormal && rhs.isNormal
}
}
public var isSubnormal: Bool {
switch self {
case .scalar, .variable, .raise :
return value.isSubnormal
case .magnitude(let expr),
.sqrt(let expr),
.rounding(let expr, _):
return expr.isSubnormal
case .add(let lhs, let rhs),
.subtract(let lhs, let rhs),
.multiply(let lhs, let rhs),
.divide(let lhs, let rhs):
return lhs.isSubnormal || rhs.isSubnormal
}
}
public var isFinite: Bool {
switch self {
case .scalar, .variable :
return value.isSubnormal
case .magnitude(let expr),
.raise(let expr, _),
.sqrt(let expr),
.rounding(let expr, _):
return expr.isFinite
case .add(let lhs, let rhs),
.subtract(let lhs, let rhs),
.multiply(let lhs, let rhs),
.divide(let lhs, let rhs):
return lhs.isFinite && rhs.isFinite
}
}
public var isInfinite: Bool {
switch self {
case .scalar, .variable :
return value.isInfinite
case .magnitude(let expr),
.raise(let expr, _),
.sqrt(let expr),
.rounding(let expr, _):
return expr.isInfinite
case .add(let lhs, let rhs),
.subtract(let lhs, let rhs),
.multiply(let lhs, let rhs),
.divide(let lhs, let rhs):
return lhs.isInfinite || rhs.isInfinite
}
}
public var isNaN: Bool {
switch self {
case .scalar, .variable :
return value.isNaN
case .magnitude(let expr),
.raise(let expr, _),
.sqrt(let expr),
.rounding(let expr, _):
return expr.isNaN
case .add(let lhs, let rhs),
.subtract(let lhs, let rhs),
.multiply(let lhs, let rhs):
return lhs.isNaN || rhs.isNaN
case .divide(let lhs, let rhs):
return lhs.isNaN || rhs.isNaN || value.isNaN
}
}
public var isSignalingNaN: Bool {
switch self {
case .scalar, .variable :
return value.isSignalingNaN
case .magnitude(let expr),
.raise(let expr, _),
.sqrt(let expr),
.rounding(let expr, _):
return expr.isSignalingNaN
case .add(let lhs, let rhs),
.subtract(let lhs, let rhs),
.multiply(let lhs, let rhs):
return lhs.isSignalingNaN && rhs.isSignalingNaN
case .divide(let lhs, let rhs):
return lhs.isSignalingNaN || rhs.isSignalingNaN || value.isSignalingNaN
}
}
public var isCanonical: Bool {
return true
}
}
extension Algebra: BinaryFloatingPoint {
public init(_ value: Float) {
self = .scalar(Double(value))
}
public init(_ value: Double) {
self = .scalar(value)
}
public init(_ value: Float80) {
self = .scalar(Double(value))
}
public init(sign: FloatingPointSign, exponentBitPattern: Double.RawExponent, significandBitPattern: Double.RawSignificand) {
self = .scalar(Double(sign: sign, exponentBitPattern: exponentBitPattern, significandBitPattern: significandBitPattern))
}
public static var exponentBitCount: Int {
return Double.exponentBitCount
}
public static var significandBitCount: Int {
return Double.significandBitCount
}
public var binade: Algebra {
return Algebra(value.binade)
}
public var exponentBitPattern: UInt {
return value.exponentBitPattern
}
public var significandBitPattern: UInt64 {
return value.significandBitPattern
}
public var significandWidth: Int {
return value.significandWidth
}
}
extension Algebra: CustomPlaygroundQuickLookable {
public var customPlaygroundQuickLook: PlaygroundQuickLook {
return PlaygroundQuickLook.text(description)
}
}
extension Double {
mutating func raise(toPower power: Int) {
let base = self
for _ in 1..<power {
self *= base
}
}
func raised(toPower power: Int ) -> Double {
var copy = self
copy.raise(toPower: power)
return copy
}
}
struct Order {
func quantity(of part: Part) -> Int {
return 10
}
}
struct Part {
var machiningRate: Double
var machiningTime: Double
var machiningCost: Algebra {
return .variable(name: "machiningTime") { self.machiningTime } *
.variable(name: "machiningRate") { self.machiningRate }
}
}
extension Algebra {
static func machiningCost(of part: Part) -> Algebra {
return part.machiningCost
}
static func quantity(of part: Part, in order: Order) -> Algebra {
return .variable(name: "quantity") {
Double(order.quantity(of: part))
}
}
static func totalMachiningPrice(of part: Part, in order: Order) -> Algebra {
return .multiply(.machiningCost(of: part), by: .quantity(of: part, in: order))
}
func discounted(byPercent percent: Double) -> Algebra {
return .multiply(self, by: .scalar(1.0 - percent))
}
}
let formula: Algebra = (3 + 5).rounded() * 4
let order = Order()
let part = Part(machiningRate: 10, machiningTime: 1)
let price: Algebra = 123 + .totalMachiningPrice(of: part, in: order)
price.description
// => "(123 + machiningTime × machiningRate × quantity) = 223.0"
let discounted = price.discounted(byPercent: 0.10)
discounted.description
// => "(123 + machiningTime × machiningRate × quantity) × 0.9 = 200.7"
@mklbtz
Copy link
Author

mklbtz commented Sep 3, 2017

Requires Swift 4.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment