99

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.

TylerH
  • 20,799
  • 66
  • 75
  • 101
George WS
  • 3,903
  • 5
  • 27
  • 39

10 Answers10

153

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
Ondrej Stocek
  • 2,102
  • 1
  • 18
  • 13
  • 2
    Your example works with just the Comparable extension - the Strideable extension is redundant here unless there is another use case not demonstrated? – stef Jul 06 '18 at 21:38
  • 1
    Strideable extension is used for clamping to CountableClosedRange. In case you are using range literal compiler can create proper range type, but if you use variable as a param, Strideable extension is needed. I'll extend the example. – Ondrej Stocek Jul 18 '18 at 11:23
  • 2
    @OndrejStocek It seems that the `Strideable` extension is no longer required with Swift 5.1. – Imanou Petit Dec 08 '19 at 15:30
  • I truly advise against the "min/max" idiom. It is **trick programming**. The one and only reason to do it, is, because it looks clever. Just one problem: Note that - in fact - here and now glancing at that code I literally **don't know** if the ends are inclusive/exclusive, and/or whether or not it deals w/ float equality issues. Again: literally the ONLY reason to do that is because it "looks clever". It achieves utterly nothing and leads to confusion and questions. It is **NOT a case of "elegant short code"**. I strongly advise against it. – Fattie Oct 19 '20 at 13:40
  • 2
    @Fattie That's the point of this extension - to reduce min/max usage to minimum. In all other cases you can use `.clamped(to: ...)` instead of `min(max(...)...)` – Ondrej Stocek Oct 20 '20 at 09:59
  • 1
    @Fattie So what is the proper way to do it? And actually, you do know that the range includes the bounds because of the `...` closed range operator. – Peter Schorn Dec 01 '20 at 03:14
  • Simple and consice, this is perfect! – thecoolwinter Mar 02 '21 at 23:24
62

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
    }
}
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • Fabulous! This is exactly the kind of information I was looking for. Would love to see this implemented in future versions of Swift. – George WS Mar 20 '16 at 08:31
  • 1
    As a native Swede, (alphabet: `..., x, y, z, å, ä, ö`), `"ä" < "z" // true` is a bit confusing ;) on another note, the `clamp` examples listed will need to be used with external parameter name `value` for the Swift 3 example, possibly worth mentioning explicitly (or Xcode will fix that prompt!). – dfrib Oct 27 '16 at 16:12
  • 1
    @dfri: String and character comparison is *not* locale sensitive, compare http://stackoverflow.com/questions/25713975/what-does-it-mean-that-string-and-character-comparisons-in-swift-are-not-locale. Added `_` external parameter name to make the examples work again. Thanks for the feedback! – Martin R Oct 27 '16 at 16:36
29

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.

RodolfoAntonici
  • 1,555
  • 21
  • 34
17

2020. The extremely simple way.

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).)

An opinion on end-naming ...

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 :)

Fattie
  • 27,874
  • 70
  • 431
  • 719
  • 1
    You make a good point about ranges not necessarily being relevant to clamping, and about the order of the two clamp values. A shorter version of your implementation might be: `func clamped(_ a: Self, _ b: Self) -> Self { max(min(self, a), b) } ` – markiv May 29 '20 at 09:05
  • it is not simple because you create extra variable but you could return the value directly – Vyachaslav Gerchicov May 20 '21 at 10:21
  • hi @VyachaslavGerchicov - that's very much the point! do it the simplest, clearest, most explicit way. *not* the shortest or most concise way – Fattie May 20 '21 at 10:53
  • 2
    This answer doesn't utilises anything new that was added in 2020. And also doesn't contribute to existing answer. – kelin Oct 22 '21 at 05:37
9

With Swift 5.1, the idiomatic way to achieve the desired clamping would be with property wrappers. A touched-up example from NSHipster:

@propertyWrapper
struct Clamping<Value: Comparable> {
  var value: Value
  let range: ClosedRange<Value>

  init(wrappedValue: Value, _ range: ClosedRange<Value>) {
    precondition(range.contains(wrappedValue))
    self.value = wrappedValue
    self.range = range
  }

  var wrappedValue: Value {
    get { value }
    set { value = min(max(range.lowerBound, newValue), range.upperBound) }
  }
}

Usage:

@Clamping(0...5) var a: Float = 4.2
@Clamping(0...5) var b: Float = -1.3
@Clamping(0...5) var c: Float = 6.4
Blaz
  • 3,548
  • 5
  • 26
  • 40
  • 11
    This seems so much verbose and unintuitive to me! `let x = 1.42.clamped(to: 0...1)` or `let x = clamp(1.42, to: 0...1)` is cleaner than `@Clamping(0...1) let x = 1.42`. – Louis Lac Dec 03 '19 at 22:27
  • Very interesting solution. Thanks. – Vyacheslav Aug 15 '20 at 21:28
  • 1
    @LouisLac it's more verbose but the increased verbosity comes with the benefit of communicating to the reader that they may have their values clamped. If you are a newcomer to the codebase this is incredibly valuable. – MH175 Jun 27 '22 at 00:06
8

In Swift 3 there are new CountableClosedRange, CountableRange, Range, ClosedRange protocols. They have the same upperBound and lowerBound properties. So you can extend all Range protocols at once with a clamp method by declaring a custom protocol:

protocol ClampableRange {

    associatedtype Bound : Comparable

    var upperBound: Bound { get }

    var lowerBound: Bound { get }

}

extension ClampableRange {

    func clamp(_ value: Bound) -> Bound {
        return min(max(lowerBound, value), upperBound)
    }  

}

extension Range : ClampableRange {}
extension ClosedRange : ClampableRange {}
extension CountableRange : ClampableRange {}
extension CountableClosedRange : ClampableRange {}

Usage:

(0...10).clamp(12) // 10
(0..<100).clamp(-2) // 0
("a"..."c").clamp("z") // c
chebur
  • 614
  • 8
  • 16
8

Following up on @Fattie's answer and my comment, here's my suggestion for clarity:

extension Comparable {
    func clamped(_ a: Self, _ b: Self) -> Self {
        min(max(self, a), b)
    }
}
Thomas Besnehard
  • 2,106
  • 3
  • 25
  • 47
markiv
  • 1,578
  • 16
  • 12
7

The shortest (but maybe not most efficient) way to clamp, is:

let clamped = [0, a, 5].sorted()[1]

Source: user tobr in a discussion on Hacker News

Bart van Kuik
  • 4,704
  • 1
  • 33
  • 57
2

Extending FixedWidthInteger and creating an instance generic method to accept a RangeExpression and taking care of the edge cases:

extension FixedWidthInteger {
    func clamped<R: RangeExpression>(with range: R) -> Self where R.Bound == Self {
        switch range {
        case let range as ClosedRange<Self>:
            return Swift.min(range.upperBound, Swift.max(range.lowerBound, self))
        case let range as PartialRangeFrom<Self>:
            return Swift.max(range.lowerBound, self)
        case let range as PartialRangeThrough<Self>:
            return Swift.min(range.upperBound, self)
        case let range as Range<Self>:
            return Swift.min(range.dropLast().upperBound, Swift.max(range.lowerBound, self))
        case let range as PartialRangeUpTo<Self>:
            return Swift.min(range.upperBound.advanced(by: -1), self)
        default: return self
        }
    }
}

Playground testing:

100.clamped(with: 1...)     // 100
100.clamped(with: ..<100)   // 99
100.clamped(with: ...100)   // 100
100.clamped(with: 1..<100)  // 99
100.clamped(with: 1...100)  // 100

0.clamped(with: 1...)       // 1
0.clamped(with: ..<100)     // 0
0.clamped(with: ...100)     // 0
0.clamped(with: 1..<100)    // 1
0.clamped(with: 1...100)    // 1

To achieve the same results with a FloatingPoint implementation you can use its nextDown property for the edge cases:

extension BinaryFloatingPoint {
    func clamped<R: RangeExpression>(with range: R) -> Self where R.Bound == Self {
        switch range {
        case let range as ClosedRange<Self>:
            return Swift.min(range.upperBound, Swift.max(range.lowerBound, self))
        case let range as PartialRangeFrom<Self>:
            return Swift.max(range.lowerBound, self)
        case let range as PartialRangeThrough<Self>:
            return Swift.min(range.upperBound, self)
        case let range as Range<Self>:
            return Swift.min(range.upperBound.nextDown, Swift.max(range.lowerBound, self))
        case let range as PartialRangeUpTo<Self>:
            return Swift.min(range.upperBound.nextDown, self)
        default: return self
        }
    }
}

Playground testing:

let value = 100.0

value.clamped(with: 1...)     // 100
value.clamped(with: ..<100)   // 99.99999999999999
value.clamped(with: ...100)   // 100
value.clamped(with: 1..<100)  // 99.99999999999999
value.clamped(with: 1...100)  // 100
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
1

I find it counter-intuitive thinking about how to clamp a value only from one side: min() clamps a value to an upper bound, and max() clamps a value to a lower bound. So adding to the other answers, I use an extension that allows to clamp a value to a range as well as a lower and an upper bound.

extension Comparable {
    
    func clamped(range: ClosedRange<Self>) -> Self {
        return max(range.lowerBound, min(self, range.upperBound))
    }
    
    func clamped(lowerBound: Self) -> Self {
        return max(lowerBound, self)
    }
    
    func clamped(upperBound: Self) -> Self {
        return min(self, upperBound)
    }
    
}
Nickkk
  • 2,261
  • 1
  • 25
  • 34