Skip to content

Instantly share code, notes, and snippets.

@mklbtz
Last active September 3, 2017 19:38
Show Gist options
  • Select an option

  • Save mklbtz/e8ebca10e82eec657593c6bab69ab635 to your computer and use it in GitHub Desktop.

Select an option

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

mklbtz commented Sep 3, 2017

Copy link
Copy Markdown
Author

Requires Swift 4.0

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