Last active
September 3, 2017 19:38
-
-
Save mklbtz/e8ebca10e82eec657593c6bab69ab635 to your computer and use it in GitHub Desktop.
A Number-like type that can represent algebraic expressions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Requires Swift 4.0