Given:
let a = 4.2
let b = -1.3
let c = 6.4
I want to know the simplest, Swiftiest way to clamp these values to a given range, say 0...5
, such that:
a -> 4.2
b -> 0
c -> 5
I know I can do the following:
let clamped = min(max(a, 0), 5)
Or something like:
let clamped = (a < 0) ? 0 : ((a > 5) ? 5 : a)
But I was wondering if there were any other ways to do this in Swift—in particular, I want to know (and document on SO, since there doesn't appear to be a question about clamping numbers in Swift) whether there is anything in the Swift standard library intended specifically for this purpose.
There may not be, and if so, that's also an answer I'll happily accept.
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
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
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.
extension Comparable {
func clamped(_ f: Self, _ t: Self) -> Self {
var r = self
if r < f { r = f }
if r > t { r = t }
// (use SIMPLE, EXPLICIT code here to make it utterly clear
// whether we are inclusive, what form of equality, etc etc)
return r
}
While I truly love ranges in Swift, I really think the absolutely standard syntax for a clamp function ("for 50 years now in every computer language") is just simpler and better:
x = x.clamped(0.5, 5.0)
Until it is built-in to Swift, really I think that's best.
Philosophical corner:
IMO the two values in a clamp function are not really a 'range' - they're just "two values".
(Just for example: it's completely common in game code to have the two dynamic values sometimes be in the "wrong order" (i..e, the desired result is something outside) or the same (the result is just that value).)
On everything we do, we insist on explicitly stating whether inclusive or exclusive. For example if there's a call
randomIntUpTo( 13 )
in fact we will name it
randomIntUpToExclusive( 13 )
or indeed "inclusive" if that is the case. Or depending on the language something like
randomInt(fromInclusive: upToExclusive: )
or whatever the case may be. In this way there is absolutely never ever ever a unity error, and nothing needs to be discussed. All code names should be self-documenting. So indeed, for us, the function above would be named
func clamped(fromExclusive: Self, toExclusive: Self)
or whatever describes it.
But that's just us. But it's the right thing to do :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With