Categories
iOS

Random float and integer in Swift

Getting a random number within a range is a very common operation. There are multiple ways of extending Swift language to add support for getting random values within a specified range. After experimenting with different implementations like utility functions, integer and float extensions I have found that the most natural way of doing it is to extend ClosedRange. This will allow to write a very readable code:

let randomDouble: Double = (-0.3...0.9).random
let randomCGFloat: CGFloat = (-0.9...0.9).random
let randomInt: Int = (-9...3).random
let randomUInt: UInt = (3...9).random

Random floating point values

extension ClosedRange where Bound: BinaryFloatingPoint {
var random: Bound {
let ratio = Bound(arc4random_uniform(UInt32.max)) / Bound(UInt32.max - 1)
let offset = (upperBound - lowerBound) * ratio
return lowerBound + offset
}
}

1. Get a random value using a max possible range (note that arc4random_uniform excludes the upper bound).
2. Convert the value to percentage.
3. Multiply the range with the percentage.
4. Add offset to the range’s minimum value.

Random integer values

extension ClosedRange where Bound: BinaryInteger {
var random: Bound {
let offset = arc4random_uniform(UInt32(upperBound - lowerBound) + 1)
return lowerBound + Bound(offset)
}
}

1. Get a random value from the allowed range.
2. Add the offset to the range’s minimum value.

Categories
iOS

Clamping numbers in Swift

Clamping a value is an operation of moving the value to a range of allowed values. It can be achieved by comparing the value with allowed minimum and maximum values.

For example I was deforming SKWarpGeometryGrid from the direction of a point outside the grid and needed to constrain angles between grid points and the contact point. Maximum and minimum allowed angles were related to the angle between the contact point and the grid’s center point.

The solution I propose extends FloatingPoint and BinaryInteger protocols and gives a very readable form to this example problem:

let angleToCenter: CGFloat = .pi / 5
let angleToGridPoint: CGFloat = .pi / 3
// 1.0471975511966
let allowedRange = (angleToCenter - .pi / 8)...(angleToCenter + .pi / 8)
let angle = angleToGridPoint.clamped(to: allowedRange)
// 1.02101761241668
view raw Clamp.swift hosted with ❤ by GitHub

Extending FloatingPoint protocol

extension FloatingPoint {
func clamped(to range: ClosedRange<Self>) -> Self {
return max(min(self, range.upperBound), range.lowerBound)
}
}
let clamped = 5.4.clamped(to: 5.6...6.1)
let clamped = 10.5.clamped(to: 5...7)

Extending BinaryInteger protocol

extension BinaryInteger {
func clamped(to range: ClosedRange<Self>) -> Self {
return max(min(self, range.upperBound), range.lowerBound)
}
}
let clamped = 10.clamped(to: 5...7)