Another attempt, still not entirely sure if everything works correctly:
extension FixedWidthInteger {
func roundedTowardZero(toMultipleOf m: Self) -> Self {
return self - (self % m)
}
func roundedAwayFromZero(toMultipleOf m: Self) -> Self {
let x = self.roundedTowardZero(toMultipleOf: m)
if x == self { return x }
return (m.signum() == self.signum()) ? (x + m) : (x - m)
}
func roundedDown(toMultipleOf m: Self) -> Self {
return self < 0
? self.roundedAwayFromZero(toMultipleOf: m)
: self.roundedTowardZero(toMultipleOf: m)
}
func roundedUp(toMultipleOf m: Self) -> Self {
return self >= 0
? self.roundedAwayFromZero(toMultipleOf: m)
: self.roundedTowardZero(toMultipleOf: m)
}
func roundedUpToPowerOfTwo() -> Self {
precondition(self > 0)
let shifts = bitWidth &- leadingZeroBitCount
return nonzeroBitCount == 1 ? self : 1 &<< shifts
}
}
extension BinaryFloatingPoint {
func roundedToNearest(multipleOf m: Self) -> Self {
return self - self.remainder(dividingBy: m)
}
func roundedTowardZero(toMultipleOf m: Self) -> Self {
return self - self.truncatingRemainder(dividingBy: m)
}
func roundedAwayFromZero(toMultipleOf m: Self) -> Self {
let x = self.roundedTowardZero(toMultipleOf: m)
if self == x {
return self
} else {
return self >= 0 ? x + m : x - m
}
}
func roundedDown(toMultipleOf m: Self) -> Self {
return self < 0
? self.roundedAwayFromZero(toMultipleOf: m)
: self.roundedTowardZero(toMultipleOf: m)
}
func roundedUp(toMultipleOf m: Self) -> Self {
return self >= 0
? self.roundedAwayFromZero(toMultipleOf: m)
: self.roundedTowardZero(toMultipleOf: m)
}
}
Note for example that:
print(Float(1.5).roundedToNearest(multipleOf: 1.0)) // 2.0
print(Float(2.5).roundedToNearest(multipleOf: 1.0)) // 2.0 (we might have expected 3.0)
This is because roundedToNearest(multipleOf:)
uses remainder(dividingBy:)
which has the following property:
For two finite values x and y, the remainder r of dividing x by y satisfies x == y * q + r, where q is the integer nearest to x / y. If x / y is exactly halfway between two integers, q is chosen to be even.
So:
print(Float(1.5).remainder(dividingBy: 1.0)) // -0.5
print(Float(2.5).remainder(dividingBy: 1.0)) // 0.5
An alternative implementation of roundedToNearest(multipleOf:)
could perhaps be:
func roundedToNearest(multipleOf m: Self) -> Self {
let x = self >= 0 ? self + m/2 : self - m/2
return x - x.truncatingRemainder(dividingBy: m)
}
And since these two implementations corresponds to the two rounding rules .toNearestOrEven
and .toNearestOrAwayFromZero
we can implement all variants as this single method:
extension BinaryFloatingPoint {
func rounded(_ roundingRule: FloatingPointRoundingRule,
toMultipleOf m: Self) -> Self
{
switch roundingRule {
case .toNearestOrEven:
return self - self.remainder(dividingBy: m)
case .toNearestOrAwayFromZero:
let x = self >= 0 ? self + m/2 : self - m/2
return x - x.truncatingRemainder(dividingBy: m)
case .awayFromZero:
let x = self.rounded(.towardZero, toMultipleOf: m)
if self == x {
return self
} else {
return self >= 0 ? x + m : x - m
}
case .towardZero:
return self - self.truncatingRemainder(dividingBy: m)
case .down:
return self < 0
? self.rounded(.awayFromZero, toMultipleOf: m)
: self.rounded(.towardZero, toMultipleOf: m)
case .up:
return self >= 0
? self.rounded(.awayFromZero, toMultipleOf: m)
: self.rounded(.towardZero, toMultipleOf: m)
}
}
}
Though perhaps the "even" in .toNearestOrEven
doesn't quite make sense in the context of this method.