Skip to content

Commit

Permalink
feature: Fractional Exponents
Browse files Browse the repository at this point in the history
  • Loading branch information
Jairon Terrero committed Jan 11, 2024
1 parent 4e14254 commit 03652e7
Show file tree
Hide file tree
Showing 8 changed files with 409 additions and 23 deletions.
4 changes: 2 additions & 2 deletions Sources/Units/Measurement/Measurement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,9 @@ public struct Measurement: Equatable, Codable {
/// Exponentiate the measurement. This is equavalent to multiple `*` operations.
/// - Parameter raiseTo: The exponent to raise the measurement to
/// - Returns: A new measurement with an exponentiated scalar value and an exponentiated unit of measure
public func pow(_ raiseTo: Int) -> Measurement {
public func pow(_ raiseTo: Fraction) -> Measurement {
return Measurement(
value: Foundation.pow(value, Double(raiseTo)),
value: Foundation.pow(value, raiseTo.asDouble),
unit: unit.pow(raiseTo)
)
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/Units/Registry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ internal class Registry {

/// Returns a list of defined units and their exponents, given a composite unit symbol. It is expected that the caller has
/// verified that this is a composite unit.
internal func compositeUnitsFromSymbol(symbol: String) throws -> [DefinedUnit: Int] {
internal func compositeUnitsFromSymbol(symbol: String) throws -> [DefinedUnit: Fraction] {
let symbolsAndExponents = try deserializeSymbolicEquation(symbol)

var compositeUnits = [DefinedUnit: Int]()
var compositeUnits = [DefinedUnit: Fraction]()
for (definedUnitSymbol, exponent) in symbolsAndExponents {
guard exponent != 0 else {
continue
Expand Down Expand Up @@ -70,7 +70,7 @@ internal class Registry {
internal func addUnit(
name: String,
symbol: String,
dimension: [Quantity: Int],
dimension: [Quantity: Fraction],
coefficient: Double = 1,
constant: Double = 0
) throws {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Units/Unit/DefinedUnit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
struct DefinedUnit: Hashable, Sendable {
let name: String
let symbol: String
let dimension: [Quantity: Int]
let dimension: [Quantity: Fraction]
let coefficient: Double
let constant: Double

init(name: String, symbol: String, dimension: [Quantity: Int], coefficient: Double = 1, constant: Double = 0) throws {
init(name: String, symbol: String, dimension: [Quantity: Fraction], coefficient: Double = 1, constant: Double = 0) throws {
guard !symbol.isEmpty else {
throw UnitError.invalidSymbol(message: "Symbol cannot be empty")
}
Expand Down
12 changes: 6 additions & 6 deletions Sources/Units/Unit/Equations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
/// - spaceAroundOperators: Whether to include space characters before and after multiplication and division characters.
/// - Returns: A string that represents the equation of the object symbols and their respective exponentiation.
func serializeSymbolicEquation<T>(
of dict: [T: Int],
of dict: [T: Fraction],
symbolPath: KeyPath<T, String>,
spaceAroundOperators: Bool = false
) -> String {
Expand Down Expand Up @@ -76,7 +76,7 @@ func serializeSymbolicEquation<T>(
}
let symbol = object[keyPath: symbolPath]
var expStr = ""
if abs(exp) > 1 {
if abs(exp) != 0, abs(exp) != 1 {
expStr = "\(expSymbol)\(abs(exp))"
}

Expand All @@ -93,19 +93,19 @@ func serializeSymbolicEquation<T>(
/// - Returns: A dictionary containing object symbols and exponents
func deserializeSymbolicEquation(
_ equation: String
) throws -> [String: Int] {
) throws -> [String: Fraction] {
let expSymbol = OperatorSymbols.exp.rawValue
let multSymbol = OperatorSymbols.mult.rawValue
let divSymbol = OperatorSymbols.div.rawValue

var result = [String: Int]()
var result = [String: Fraction]()
for multChunks in equation.split(separator: multSymbol, omittingEmptySubsequences: false) {
for (index, divChunks) in multChunks.split(separator: divSymbol, omittingEmptySubsequences: false).enumerated() {
let symbolChunks = divChunks.split(separator: expSymbol, omittingEmptySubsequences: false)
let subSymbol = String(symbolChunks[0]).trimmingCharacters(in: .whitespaces)
var exp = 1
var exp: Fraction = 1
if symbolChunks.count == 2 {
guard let expInt = Int(String(symbolChunks[1])) else {
guard let expInt = Fraction(String(symbolChunks[1])) else {
throw UnitError.invalidSymbol(message: "Symbol '^' must be followed by an integer: \(equation)")
}
exp = expInt
Expand Down
178 changes: 178 additions & 0 deletions Sources/Units/Unit/Fraction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@

/// Represents a reduced fractional number.
/// An invariant exists such that it is not possible to create a ``Fraction``
/// that is not represented in its most reduced form.
public struct Fraction: Hashable, Equatable, Sendable {
public let numerator: Int
public let denominator: Int

/// Combines the provided `numerator` and `denominator` into a reduced ``Fraction``.
/// - Warning: Attempts to create a ``Fraction`` with a zero denominator will fatally error.
public init(numerator: Int, denominator: Int) {
let gcd = Self.gcd(numerator, denominator)
self.numerator = numerator / gcd
self.denominator = denominator / gcd
}

public var positive: Bool {
switch (numerator, denominator) {
// 0/0 is not positive in this logic
case let (n, d) where n >= 0 && d > 0: true

// Seems like this case can't happen because
// all Fractions are reduced.
case let (n, d) where n < 0 && d < 0: true

default: false
}
}
}

private extension Fraction {
static func gcd(_ a: Int, _ b: Int) -> Int {
// See: https://en.wikipedia.org/wiki/Euclidean_algorithm
var latestRemainder = max(a, b)
var previousRemainder = min(a, b)

while latestRemainder != 0 {
let tmp = latestRemainder
latestRemainder = previousRemainder % latestRemainder
previousRemainder = tmp
}
return previousRemainder
}
}


extension Fraction {
public static func * (lhs: Self, rhs: Self) -> Self {
Self(numerator: lhs.numerator * rhs.numerator, denominator: lhs.denominator * rhs.denominator)
}

public static func / (lhs: Self, rhs: Self) -> Self {
Self(numerator: lhs.numerator * rhs.denominator, denominator: lhs.denominator * rhs.numerator)
}

public static func + (lhs: Self, rhs: Self) -> Self {
Self(numerator: (lhs.numerator * rhs.denominator) + (rhs.numerator * lhs.denominator), denominator: lhs.denominator * rhs.denominator)
}

public static func - (lhs: Self, rhs: Self) -> Self {
Self(numerator: (lhs.numerator * rhs.denominator) - (rhs.numerator * lhs.denominator), denominator: lhs.denominator * rhs.denominator)
}
}
extension Fraction {
public static func * (lhs: Self, rhs: Int) -> Self {
lhs * Self(integerLiteral: rhs)
}

public static func / (lhs: Self, rhs: Int) -> Self {
lhs / Self(integerLiteral: rhs)
}

public static func * (lhs: Int, rhs: Self) -> Self {
Self(integerLiteral: lhs) * rhs
}

public static func / (lhs: Int, rhs: Self) -> Self {
Self(integerLiteral: lhs) / rhs
}

public static func + (lhs: Self, rhs: Int) -> Self {
lhs + Self(integerLiteral: rhs)
}

public static func - (lhs: Self, rhs: Int) -> Self {
lhs - Self(integerLiteral: rhs)
}

public static func + (lhs: Int, rhs: Self) -> Self {
Self(integerLiteral: lhs) + rhs
}

public static func - (lhs: Int, rhs: Self) -> Self {
Self(integerLiteral: lhs) - rhs
}
}

extension Fraction: ExpressibleByIntegerLiteral {
public typealias IntegerLiteralType = Int

public init(integerLiteral value: Int) {
self.init(numerator: value, denominator: 1)
}
}

extension Fraction: SignedNumeric {

public init?<T>(exactly source: T) where T : BinaryInteger {
self.init(integerLiteral: Int(source))
}

public static func *= (lhs: inout Fraction, rhs: Fraction) {
lhs = lhs * rhs
}

public var magnitude: Fraction {
Self(numerator: abs(numerator), denominator: abs(denominator))
}

public typealias Magnitude = Self

}

extension Fraction {
public var asDouble: Double {
Double(numerator) / Double(denominator)
}
}

extension Fraction: Comparable {
public static func < (lhs: Fraction, rhs: Fraction) -> Bool {
lhs.numerator * rhs.denominator < rhs.numerator * lhs.denominator
}
}

extension Fraction: LosslessStringConvertible {
/// The format for string conversion is: `(<integer>|<integer>)` or `<integer>`
public init?(_ description: String) {
if
description.first == "(",
description.last == ")"
{
let parts = description.dropFirst().dropLast().split(separator: "|").compactMap({ Int(String($0)) })
guard
parts.count == 2,
let numerator = parts.first,
let denominator = parts.last
else {
return nil
}
self.init(numerator: numerator, denominator: denominator)
} else if let number = Int(description) {
self.init(integerLiteral: number)
} else {
return nil
}
}

public var description: String {
if denominator == 1 {
"\(!positive && numerator != 0 ? "-" : "")\(abs(numerator))"
} else {
"(\(positive ? "" : "-")\(abs(numerator))|\(abs(denominator)))"
}
}
}

extension SignedInteger {
func over<T: SignedInteger>(_ denominator: T) -> Fraction {
Fraction(numerator: Int(self), denominator: Int(denominator))
}
}

extension Int {
public static func |(_ lhs: Self, _ rhs: Self) -> Fraction {
Fraction(numerator: lhs, denominator: rhs)
}
}
20 changes: 10 additions & 10 deletions Sources/Units/Unit/Unit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public struct Unit {
/// Create a new from the sub-unit dictionary.
/// - Parameter subUnits: A dictionary of defined units and exponents. If this dictionary has only a single unit with an exponent of one,
/// we return that defined unit directly.
internal init(composedOf subUnits: [DefinedUnit: Int]) {
internal init(composedOf subUnits: [DefinedUnit: Fraction]) {
if subUnits.count == 1, let subUnit = subUnits.first, subUnit.value == 1 {
type = .defined(subUnit.key)
} else {
Expand Down Expand Up @@ -88,7 +88,7 @@ public struct Unit {
public static func define(
name: String,
symbol: String,
dimension: [Quantity: Int],
dimension: [Quantity: Fraction],
coefficient: Double = 1,
constant: Double = 0
) throws -> Unit {
Expand Down Expand Up @@ -123,7 +123,7 @@ public struct Unit {
public static func register(
name: String,
symbol: String,
dimension: [Quantity: Int],
dimension: [Quantity: Fraction],
coefficient: Double = 1,
constant: Double = 0
) throws -> Unit {
Expand All @@ -144,14 +144,14 @@ public struct Unit {
}

/// The dimension of the unit in terms of base quanties
public var dimension: [Quantity: Int] {
public var dimension: [Quantity: Fraction] {
switch type {
case .none:
return [:]
case let .defined(definition):
return definition.dimension
case let .composite(subUnits):
var dimensions: [Quantity: Int] = [:]
var dimensions: [Quantity: Fraction] = [:]
for (subUnit, exp) in subUnits {
let subDimensions = subUnit.dimension.mapValues { value in
exp * value
Expand Down Expand Up @@ -259,7 +259,7 @@ public struct Unit {
/// Exponentiate the unit. This is equavalent to multiple `*` operations.
/// - Parameter raiseTo: The exponent to raise the unit to
/// - Returns: A new unit modeling the original raised to the provided power
public func pow(_ raiseTo: Int) -> Unit {
public func pow(_ raiseTo: Fraction) -> Unit {
switch type {
case .none:
return .none
Expand Down Expand Up @@ -300,7 +300,7 @@ public struct Unit {
guard subUnit.constant == 0 else { // subUnit must not have constant
throw UnitError.invalidCompositeUnit(message: "Nonlinear unit prevents conversion: \(subUnit)")
}
totalCoefficient *= Foundation.pow(subUnit.coefficient, Double(exponent))
totalCoefficient *= Foundation.pow(subUnit.coefficient, exponent.asDouble)
}
return number * totalCoefficient
}
Expand All @@ -324,7 +324,7 @@ public struct Unit {
guard subUnit.constant == 0 else { // subUnit must not have constant
throw UnitError.invalidCompositeUnit(message: "Nonlinear unit prevents conversion: \(subUnit)")
}
totalCoefficient *= Foundation.pow(subUnit.coefficient, Double(exponent))
totalCoefficient *= Foundation.pow(subUnit.coefficient, exponent.asDouble)
}
return number / totalCoefficient
}
Expand All @@ -334,7 +334,7 @@ public struct Unit {

/// Returns a dictionary that represents the unique defined units and their exponents. For a
/// composite unit, this is simply the `subUnits`, but for a defined unit, this is `[self: 1]`
private var subUnits: [DefinedUnit: Int] {
private var subUnits: [DefinedUnit: Fraction] {
switch type {
case .none:
return [:]
Expand All @@ -349,7 +349,7 @@ public struct Unit {
private enum UnitType: Sendable {
case none
case defined(DefinedUnit)
case composite([DefinedUnit: Int])
case composite([DefinedUnit: Fraction])
}
}

Expand Down
Loading

0 comments on commit 03652e7

Please sign in to comment.