-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feature: Fractional Exponents #7
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
Comment on lines
+12
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be public? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. I guess the tests do a testable import so I didn't catch that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've made some more things public |
||
} | ||
|
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Were there type inference issues with using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah. That was what I originally tried. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On that note, how do you feel about You could do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I went ahead and did this - lmk if you like/dislike. |
||
Fraction(numerator: Int(self), denominator: Int(denominator)) | ||
} | ||
} | ||
|
||
extension Int { | ||
public static func |(_ lhs: Self, _ rhs: Self) -> Fraction { | ||
Fraction(numerator: lhs, denominator: rhs) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we provide documentation about this type? Specific areas that would be helpful to document after reading through:
init
automatically reduces the input fraction|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Will do. I'll make a doc pass for everything I've added.