Skip to content

Commit 086701c

Browse files
Improve math utility functions
* Use generics * Use ranges * Improve random number generation
1 parent 19dbdcc commit 086701c

File tree

3 files changed

+69
-27
lines changed

3 files changed

+69
-27
lines changed

C4/Core/Math.swift

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -55,16 +55,36 @@ public func clamp<T: Comparable>(_ val: T, min: T, max: T) -> T {
5555
/// - parameter param: parameter between 0 and 1 for interpolation
5656
///
5757
/// - returns: The interpolated value
58-
public func lerp(_ a: Double, _ b: Double, at: Double) -> Double {
58+
public func lerp<T: FloatingPoint>(_ a: T, _ b: T, at: T) -> T {
5959
return a + (b - a) * at
6060
}
6161

62+
/// Linear mapping. Maps a value in the source range [min, max) to a value in the target range [toMin, toMax) using linear interpolation.
63+
///
64+
/// ````
65+
/// map(10, 0..<20, 0..<200) = 100
66+
/// map(10, 0..<100, 200..<300) = 210
67+
/// map(10, 0..<20, 200..<300) = 250
68+
/// ````
69+
///
70+
/// - parameter val: Source value
71+
/// - parameter min: Source range lower bound
72+
/// - parameter max: Source range upper bound
73+
/// - parameter toMin: Target range lower bound
74+
/// - parameter toMax: Target range upper bound
75+
///
76+
/// - returns: The mapped value.
77+
public func map<T: FloatingPoint>(_ val: T, from: Range<T>, to: Range<T>) -> T {
78+
let param = (val - from.lowerBound)/(from.upperBound - from.lowerBound)
79+
return lerp(to.lowerBound, to.upperBound, at: param)
80+
}
81+
6282
/// Linear mapping. Maps a value in the source range [min, max] to a value in the target range [toMin, toMax] using linear interpolation.
6383
///
6484
/// ````
65-
/// map(10, 0, 20, 0, 200) = 100
66-
/// map(10, 0, 100, 200, 300) = 210
67-
/// map(10, 0, 20, 200, 300) = 250
85+
/// map(10, 0...20, 0...200) = 100
86+
/// map(10, 0...100, 200...300) = 210
87+
/// map(10, 0...20, 200...300) = 250
6888
/// ````
6989
///
7090
/// - parameter val: Source value
@@ -74,9 +94,24 @@ public func lerp(_ a: Double, _ b: Double, at: Double) -> Double {
7494
/// - parameter toMax: Target range upper bound
7595
///
7696
/// - returns: The mapped value.
77-
public func map(_ val: Double, min: Double, max: Double, toMin: Double, toMax: Double) -> Double {
78-
let param = (val - min)/(max - min)
79-
return lerp(toMin, toMax, at: param)
97+
public func map<T: FloatingPoint>(_ val: T, from: ClosedRange<T>, to: ClosedRange<T>) -> T {
98+
let param = (val - from.lowerBound) / (from.upperBound - from.lowerBound)
99+
return lerp(to.lowerBound, to.upperBound, at: param)
100+
}
101+
102+
/// Returns a random `Int`.
103+
///
104+
/// ````
105+
/// let x = random()
106+
/// ````
107+
///
108+
/// - returns: Random `Int`.
109+
public func random() -> Int {
110+
var r = 0
111+
withUnsafeMutableBytes(of: &r) { bufferPointer in
112+
arc4random_buf(bufferPointer.baseAddress, MemoryLayout<Int>.size)
113+
}
114+
return r
80115
}
81116

82117
/// Return a random integer below `below`
@@ -87,35 +122,36 @@ public func map(_ val: Double, min: Double, max: Double, toMin: Double, toMax: D
87122
///
88123
/// - parameter below: The upper bound
89124
///
90-
/// - returns: A random value smaller than `below`
125+
/// - returns: A random value in the range `0 ..< below`
91126
public func random(below: Int) -> Int {
92-
return Int(arc4random_uniform(UInt32(below)))
127+
return abs(random()) % below
93128
}
94129

95-
/// Return a random integer greater than or equal to min and less than max.
130+
/// Return a random integer in the given range.
96131
///
97132
/// ````
98-
/// let x = random(10,20)
133+
/// let x = random(in: 10..<20)
99134
/// ````
100135
///
101-
/// - parameter min: The lower bound
102-
/// - parameter max: The upper bound
136+
/// - parameter range: range of values
103137
///
104138
/// - returns: A random value greater than or equal to min and less than max.
105-
public func random(min: Int, max: Int) -> Int {
106-
assert(min < max, "min must be less than max")
107-
return min + random(below: max - min)
139+
public func random(in range: Range<Int>) -> Int {
140+
return range.lowerBound + random(below: range.upperBound - range.lowerBound)
108141
}
109142

110-
/// Return a random Double in the interval [0, 1)
143+
/// Return a random Double in the given range.
111144
///
112145
/// ````
113-
/// let x = random01()
146+
/// let x = random(in: 0..<1)
114147
/// ````
115148
///
149+
/// - parameter range: range of values
116150
/// - returns: A random Double uniformly distributed between 0 and 1
117-
public func random01() -> Double {
118-
return Double(arc4random()) / Double(UInt32.max)
151+
public func random(in range: Range<Double>) -> Double {
152+
let intRange: Range<Double> = Double(-Int.max) ..< Double(Int.max) + 1
153+
let r = Double(random())
154+
return map(r, from: intRange, to: range)
119155
}
120156

121157
/// Converts radian values to degrees.

Tests/MathTests.swift

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,26 @@ class MathTests: XCTestCase {
4444
XCTAssertEqual(testValue, correctValue, "Value should be clamped to upper bound")
4545
}
4646

47-
func testMap() {
48-
let testValue = map(5, min: 0, max: 10, toMin: 0, toMax: 20)
47+
func testMapOpen() {
48+
let testValue = map(5, from: 0..<10, to: 0..<20)
49+
let correctValue = 10.0
50+
XCTAssertEqual(testValue, correctValue, "Value should be mapped to the target range")
51+
}
52+
53+
func testMapClosed() {
54+
let testValue = map(5, from: 0...10, to: 0...20)
4955
let correctValue = 10.0
5056
XCTAssertEqual(testValue, correctValue, "Value should be mapped to the target range")
5157
}
5258

5359
func testLerpDouble() {
54-
let testValue = map(5.0, min: 0.0, max: 10.0, toMin: 0.0, toMax: 20.0)
60+
let testValue = map(5.0, from: 0.0..<10.0, to: 0.0..<20.0)
5561
let correctValue = 10.0
5662
XCTAssertEqual(testValue, correctValue, "Double value should be mapped to the target range")
5763
}
5864

5965
func testLerpInt() {
60-
let testValue = map(6, min: 0, max: 10, toMin: 0, toMax: 20)
66+
let testValue = map(6, from: 0..<10, to: 0..<20)
6167
let correctValue = 12.0
6268
XCTAssertEqual(testValue, correctValue, "Double value should be mapped to the target range")
6369
}
@@ -89,7 +95,7 @@ class MathTests: XCTestCase {
8995
var min = Int.max
9096
var max = Int.min
9197
for _ in 0..<samples {
92-
let value = random(min: lowerBound, max: upperBound)
98+
let value = random(in: lowerBound..<upperBound)
9399
if value < min { min = value }
94100
if value > max { max = value }
95101
}
@@ -103,7 +109,7 @@ class MathTests: XCTestCase {
103109
var min = 1.0
104110
var max = 0.0
105111
for _ in 0..<samples {
106-
let value = random01()
112+
let value = random(in: 0.0..<1.0)
107113
if value < min { min = value }
108114
if value > max { max = value }
109115
}

Tests/TransformTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class TransformTests: XCTestCase {
2626
var transform = Transform()
2727
for col in 0...3 {
2828
for row in 0...3 {
29-
transform[row, col] = random01()
29+
transform[row, col] = random(in: 0.0..<1.0)
3030
}
3131
}
3232
XCTAssertEqual(identity * transform, transform, "Multiplying by the identity transform should not change the other transform")

0 commit comments

Comments
 (0)