Extending Comparable to clamp values within a range

Daily Coding Tip 006

You may need to ensure a value is within a certain range, and conditional logic is in danger of human error. For instance, do we want to use the < operator or the <= operator? It helps to have an easy way to repeat this process in all cases, so that it works consistently as we expect and reduces the amount of code we need to write. There are many number types like Int, Float and Double, as well as versions of these that are different sizes. Unsurprisingly Int8 is 8 bits in size, while Float16 is 16-bit instead of its usual 32-bit size.

How do we add the functionality to all of these at once?

By extending a protocol that they all have in common!

import Foundation
extension Comparable {
func clampFrom(min minValue: Self, to maxValue: Self) -> Self {
let clampedToMin = [minValue, self].max() ?? minValue
let clampedToBoth = [clampedToMin, maxValue].min() ?? maxValue
return clampedToBoth
}
}
var float = Float(0.5)
print(float.clampFrom(min: 0.5, to: 1)) //No effect, value is within range
float += 0.6 //Value is increased beyond range
print(float.clampFrom(min: 0.5, to: 1)) //Would be 1.1, now clamped to 1.0
print((float + 0.6).clampFrom(min: 1, to: 1.5)) //Would be 1.7, now clamped to 1.5

When the items in an array conform to the Comparable protocol, the min() and max() methods are made available. This provides us with an optional value, depending on whether there are any values in the array. An empty array returns nil, but an array that repeats the same number will return that number as the minimum and as the maximum value. In my example I am hard-coding my arrays with two values, so the answer will never be nil. Nevertheless I am using the ?? nil-coalescing operator to set it in that situation.

It’s far better to use this operator in many cases than force unwrapping with !, the unsafe call operator.

In order to clamp to the minimum value, I find out whether the value is higher than the minimum. If it is, the clamped value remains the same. If not, it ends up as the minimum value. This clamped value is then compared to the maximum value, remaining the same if it is lower and changing to the maximum value if it is out of range.

NSHipster has a good example of how to use property wrappers to clamp a value, which would remove the need to manually request that a value is clamped.

This makes it easier to be sure that a value is never out of range.

However, clamping using a property wrapper would not allow the usage in my example, where I am actually clamping the value for the purpose of printing it, without affecting the value of the original variable.


Get more Daily Coding Tips in your inbox!