Standard way to "clamp" a number between two values in Swift
Swift 4/5
Extension of Comparable/Strideable
similar to ClosedRange.clamped(to:_) -> ClosedRange
from standard Swift library.
extension Comparable {
func clamped(to limits: ClosedRange<Self>) -> Self {
return min(max(self, limits.lowerBound), limits.upperBound)
}
}
#if swift(<5.1)
extension Strideable where Stride: SignedInteger {
func clamped(to limits: CountableClosedRange<Self>) -> Self {
return min(max(self, limits.lowerBound), limits.upperBound)
}
}
#endif
Usage:
15.clamped(to: 0...10) // returns 10
3.0.clamped(to: 0.0...10.0) // returns 3.0
"a".clamped(to: "g"..."y") // returns "g"
// this also works (thanks to Strideable extension)
let range: CountableClosedRange<Int> = 0...10
15.clamped(to: range) // returns 10
The ClosedInterval type already has a
func clamp(_ intervalToClamp: ClosedInterval<Bound>) -> ClosedInterval<Bound>
method which takes another interval as an argument. There is a proposal on the Swift evolution mailing list
- Add clamp(value: Bound) -> Bound to ClosedInterval
to add another method which clamps a single value to the given interval:
/// Returns `value` clamped to `self`.
func clamp(value: Bound) -> Bound
and that is exactly what you need.
Using the implementation of the existing clamp()
method at
- https://github.com/apple/swift/blob/master/stdlib/public/core/Interval.swift.gyb
as an example, this additional clamp()
method can be implemented as
extension ClosedInterval {
func clamp(value : Bound) -> Bound {
return self.start > value ? self.start
: self.end < value ? self.end
: value
}
}
Example:
(0.0 ... 5.0).clamp(4.2) // 4.2
(0.0 ... 5.0).clamp(-1.3) // 0.0
(0.0 ... 5.0).clamp(6.4) // 5.0
ClosedInterval
is a generic type
public struct ClosedInterval<Bound : Comparable> { ... }
therefore this works not only for Double
but for all
types which are Comparable
(like Int
, CGFloat
, String
, ...):
(1 ... 3).clamp(10) // 3
("a" ... "z").clamp("ä") // "ä"
Update for Swift 3 (Xcode 8): ClosedInterval
has been renamed
to ClosedRange
, and its properties are lower/upperBound
now:
extension ClosedRange {
func clamp(_ value : Bound) -> Bound {
return self.lowerBound > value ? self.lowerBound
: self.upperBound < value ? self.upperBound
: value
}
}
Using the same syntax as Apple to do the min and max operator:
public func clamp<T>(_ value: T, minValue: T, maxValue: T) -> T where T : Comparable {
return min(max(value, minValue), maxValue)
}
You can use as that:
let clamped = clamp(newValue, minValue: 0, maxValue: 1)
The cool thing about this approach is that any value defines the necessary type to do the operation, so the compiler handles that itself.