Skip to content

Conversation

@wildthink
Copy link

Added Measurement and Percent FormatStyle. Tests and updated README included.

wildthink and others added 2 commits October 22, 2025 10:31
* FormatStyle WIP

* added FormatStyle tests

* updated README and @available

---------

Co-authored-by: Jason Jobe <box2019@jasonjobe.com>
Copy link
Owner

@NeedleInAJayStack NeedleInAJayStack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! Thanks for contributing!

Comment on lines +1 to +8
//
// Formatter.swift
// Units
// (aka Fountation.FormatStyle)
//
// Created by Jason Jobe on 10/24/25.
//

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this header; we can track dates/contributions using Git.

Comment on lines +9 to +32
public extension Measurement {
struct Formatter<Output> {
let format: (Measurement) -> Output
}

func formatted<Output>(_ formatter: Formatter<Output>) -> Output {
formatter.format(self)
}

func formatted(_ formatter: Formatter<String> = .measurement()) -> String {
formatter.format(self)
}

func formatted(
minimumFractionDigits: Int = 0,
maximumFractionDigits: Int = 4
) -> String {
Formatter
.measurement(
minimumFractionDigits: minimumFractionDigits,
maximumFractionDigits: maximumFractionDigits)
.format(self)
}
}
Copy link
Owner

@NeedleInAJayStack NeedleInAJayStack Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about using Swift's built-in NumberFormatter instead of building our own format system? Example:

// Implementation
extension NumberFormatter {
    func string(from measurement: Measurement) {
        return "\(self.string(from: .init(value: measurement.value)) \(measurement.unit.symbol)"
    }
}

// Usage
let measurement = 28.123.measured(in: .meter)
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
print(formatter.string(from: measurement)) // Prints `28.12 m`

This seems like this approach would inherit very configurable options, while also simplifying the implementation and usage. Thoughts?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was leaning into the newer Format API. The custom implementation is limiting. I'll work on that.

Comment on lines +68 to +74
extension Percent: Equatable {
/// Implemented as "nearly" equal
public static func ==(lhs: Percent, rhs: Percent) -> Bool {
lhs.magnitude >= rhs.magnitude.nextDown
&& lhs.magnitude <= lhs.magnitude.nextUp
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change Equatable implementation to nearly equal? Is this related to the tests that were added?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The significant digits of a "percent" is usually small so representing them with a Double in the arithmetic can lead to values that might result in something like 37.989488484% leading to comparisons that break our intuition (like == 38%). This felt like a reasonable compromise.

Comment on lines +219 to +231
extension Percent {
/**
Returns a random value within the given range.

```
Percent.random(in: 10%...20%)
// 10%, 11%, 12%, 19.98%, etc.
```
*/
public static func random(in range: ClosedRange<Self>) -> Self {
self.init(magnitude: .random(in: range.lowerBound.magnitude...range.upperBound.magnitude))
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Is Percent.random(in: 10%...20%)) a big improvement over Percent(magnitude: .random(in: 10...20))? I'm inclined to think that the existing approach is fine, but I'd be interested in your thoughts.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In one sense, it is just syntactic sugar but it makes the intent clearer and more succinct and encapsulates the internals reducing developer cognitive load.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants