Rounding numbers (up/down) to nearest multiple and power

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.