Skip to content

Commit 0319a5a

Browse files
committed
added Percent (not as Measurement)
1 parent 55df9cc commit 0319a5a

5 files changed

Lines changed: 252 additions & 1 deletion

File tree

Justfile

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Units Justfile
2+
# Command line support
3+
4+
5+
# About Units Commands
6+
_default:
7+
@echo '{{ style("warning") }}Units Script Commands{{ NORMAL }}'
8+
@echo @{{source_file()}}
9+
@echo ""
10+
@just -f {{source_file()}} --list
11+
12+
# Install 'units' in /usr/local/bin
13+
install:
14+
swift build -c release
15+
cp .build/release/unit /usr/local/bin/
16+
17+
# rm 'units' from /usr/local/bin
18+
uninstall:
19+
rm /usr/local/bin/unit
20+
21+
# Add swiftformat to git/pre-commit
22+
dev_setup:
23+
echo "./Scripts/git_commit_hook.sh" > .git/hooks/pre-commit
24+
25+
# Open Documentation
26+
docs:
27+
open https://swiftpackageindex.com/NeedleInAJayStack/Units/v1.0.0/documentation/units
28+
29+
git_origin := `git remote get-url origin`
30+
31+
# repo/origin
32+
repo:
33+
@echo {{git_origin}}
34+
35+
# open repo/origin
36+
open-repo:
37+
open {{git_origin}}
38+

Sources/CLI/Convert.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ struct Convert: ParsableCommand {
3131
@Argument(help: """
3232
The unit to convert to. This can either be a unit name, a unit symbol, or an equation of \
3333
unit symbols.
34+
Example: unit convert 1_ft meter -> 0.3048 m
3435
""")
3536
var to: Units.Unit
3637

Sources/CLI/List.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ struct List: ParsableCommand {
66
abstract: "Print a table of the available units, their symbols, and their dimensionality."
77
)
88

9+
@Option(name: .shortAndLong,
10+
help: "Substring to filter on dimensions and symbols")
11+
var filter: String? = nil
12+
913
func run() throws {
1014
let units = registry.allUnits().sorted { u1, u2 in
1115
u1.name <= u2.name
@@ -17,7 +21,9 @@ struct List: ParsableCommand {
1721
"dimension",
1822
]
1923

20-
let rows = units.map { unit in
24+
let rows = units
25+
.filter({ $0.contains(filter)})
26+
.map { unit in
2127
[
2228
unit.name,
2329
unit.symbol,
@@ -59,3 +65,11 @@ struct List: ParsableCommand {
5965
}
6066
}
6167
}
68+
69+
extension Units.Unit {
70+
func contains(_ substring: String?) -> Bool {
71+
guard let substring else { return true }
72+
return self.symbol.contains(substring)
73+
|| self.dimensionDescription().contains(substring)
74+
}
75+
}
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
//
2+
// Percent.swift
3+
// Units
4+
//
5+
// Created by Jason Jobe on 9/5/25.
6+
//
7+
8+
import Foundation
9+
/*
10+
NOTE: Should consider introducing `protocol Scalar`
11+
based on `VectorArithmetic`
12+
*/
13+
14+
/**
15+
Math operators with percentages treat the percent as its decimal equivalent (e.g., 25% = 0.25)
16+
but in the case of `+` and `-` the calculation is less direct.
17+
18+
Here’s how math operators work with percentages in typical calculations:
19+
20+
Multiplication (100 * 25%)
21+
22+
When you multiply a number by a percentage, you’re finding that percent of the number.
23+
• 25% is the same as 0.25.
24+
• So, 100 * 25% = 100 * 0.25 = 25.
25+
26+
Division (100 / 30%)
27+
28+
Dividing by a percentage means dividing by its decimal form.
29+
• 30% is 0.3.
30+
• So, 100 / 30% = 100 / 0.3 ≈ 333.33.
31+
32+
Addition (100 + 10%)
33+
34+
Adding a percentage to a number is less direct, but usually means increasing the number by that percent.
35+
• 10% of 100 is 10.
36+
• So, 100 + 10% = 100 + (100 * 0.10) = 110.
37+
38+
General Rule
39+
• Percent means “per hundred,” so 25% = 25/100 = 0.25.
40+
• Replace the percent with its decimal equivalent before performing the operation.
41+
42+
Example Table
43+
===========
44+
Expression Decimal Form Result
45+
------------------------------
46+
100 * 25% 100 * 0.25 25
47+
100 / 30% 100 / 0.3 333.33
48+
100 + 10% 100 + (100*0.10) 110
49+
50+
51+
If you see a percent sign in a calculation, just convert it to a decimal and proceed as usual. If you want to know how subtraction works with percentages, or how to handle more complex expressions, let me know!
52+
*/
53+
public struct Percent: Numeric, Equatable, Codable {
54+
55+
public private(set) var magnitude: Double
56+
57+
/// Create a new Percent
58+
/// - Parameters:
59+
/// - value: The magnitude of the percent
60+
public init(
61+
magnitude: Double
62+
) {
63+
self.magnitude = magnitude
64+
}
65+
66+
func percent(of measure: Measurement) -> Measurement {
67+
.init(value: magnitude * measure.value, unit: measure.unit)
68+
}
69+
70+
func percent(of other: Double) -> Double {
71+
magnitude * other
72+
}
73+
}
74+
75+
// MARK: Percent as Unit
76+
extension Percent {
77+
public var unit: Unit { Self.unit }
78+
79+
public static let unit = Unit(
80+
definedBy: try! DefinedUnit(
81+
name: "percent",
82+
symbol: "%",
83+
dimension: [:],
84+
coefficient: 0.01
85+
))
86+
}
87+
88+
// MARK: Numeric Conformance
89+
public extension Percent {
90+
init(integerLiteral value: Double) {
91+
magnitude = value
92+
}
93+
94+
static func *= (lhs: inout Percent, rhs: Percent) {
95+
lhs.magnitude *= rhs.magnitude
96+
}
97+
98+
static func - (lhs: Percent, rhs: Percent) -> Percent {
99+
Percent(magnitude: lhs.magnitude - rhs.magnitude)
100+
}
101+
102+
init?<T>(exactly source: T) where T : BinaryInteger {
103+
magnitude = Double(source)
104+
}
105+
106+
static func * (lhs: Percent, rhs: Percent) -> Percent {
107+
Percent(magnitude: lhs.magnitude * rhs.magnitude)
108+
}
109+
110+
static func + (lhs: Percent, rhs: Percent) -> Percent {
111+
Percent(magnitude: lhs.magnitude + rhs.magnitude)
112+
}
113+
}
114+
115+
postfix operator %
116+
117+
extension BinaryInteger {
118+
public static postfix func % (value: Self) -> Percent {
119+
Percent(magnitude: Double(value)/100)
120+
}
121+
}
122+
123+
extension BinaryFloatingPoint {
124+
public static postfix func % (value: Self) -> Percent {
125+
Percent(magnitude: Double(value)/100)
126+
}
127+
}
128+
129+
// AdditiveArithmetic operations `*` and `/`
130+
131+
public extension Measurement {
132+
/// Calculate the percentage of the Measurement
133+
/// - Parameters:
134+
/// - lhs: The left-hand-side measurement
135+
/// - rhs: The right-hand-side measurement
136+
/// - Returns: A new measurement with the summed scalar values and the same unit of measure
137+
@_disfavoredOverload
138+
static func + (lhs: Measurement, rhs: Percent) -> Measurement {
139+
return Measurement(
140+
value: lhs.value + rhs.percent(of: lhs.value),
141+
unit: lhs.unit
142+
)
143+
}
144+
145+
@_disfavoredOverload
146+
static func - (lhs: Measurement, rhs: Percent) -> Measurement {
147+
return Measurement(
148+
value: lhs.value - rhs.percent(of: lhs.value),
149+
unit: lhs.unit
150+
)
151+
}
152+
153+
@_disfavoredOverload
154+
static func += (lhs: inout Measurement, rhs: Percent) {
155+
lhs = lhs + rhs
156+
}
157+
158+
@_disfavoredOverload
159+
static func -= (lhs: inout Measurement, rhs: Percent) {
160+
lhs = lhs - rhs
161+
}
162+
163+
}
164+
165+
// Scalar operations `*` and `/`
166+
public extension Measurement {
167+
168+
@_disfavoredOverload
169+
static func * (lhs: Measurement, rhs: Percent) -> Measurement {
170+
return Measurement(
171+
value: lhs.value * rhs.magnitude,
172+
unit: lhs.unit
173+
)
174+
}
175+
176+
@_disfavoredOverload
177+
static func / (lhs: Measurement, rhs: Percent) -> Measurement {
178+
return Measurement(
179+
value: lhs.value / rhs.magnitude,
180+
unit: lhs.unit
181+
)
182+
}
183+
184+
}

Tests/UnitsTests/UnitTests.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,20 @@ final class UnitTests: XCTestCase {
8080
)
8181
}
8282

83+
func testPercent() throws {
84+
let value1 = 100.measured(in: .none)
85+
86+
XCTAssertEqual(Percent.unit.symbol, "%")
87+
88+
XCTAssertEqual(1%, 1%)
89+
90+
XCTAssertEqual(110.measured(in: .none), value1 + 10%)
91+
XCTAssertEqual(90.measured(in: .none), value1 - 10%)
92+
93+
XCTAssertEqual(10.measured(in: .none), value1 * 10%)
94+
XCTAssertEqual(1_000.measured(in: .none), value1 / 10%)
95+
}
96+
8397
func testSymbol() throws {
8498
XCTAssertEqual(
8599
Unit.meter.symbol,

0 commit comments

Comments
 (0)