Improving Float.random(in:using:)

Cool, not using floating point * and + probably makes a lot of difference for subnormals, especially for Float80 which on top of those usually slow subnormal arithmetic operations also seems to be subject to a lot of missed optimization opportunities.

However, note that the stdlib implementation is around 15x (Float and Double) and 72x (Float80) slower for
0 ..< leastNormalMagnitude
than it is for eg
0 ..< 1 or 0 ..< 1024.

The implementation in my previous post is about 14x faster than the stdlib for the subnormal range for Float and Double and 120x faster for Float80. But it doesn't handle the sign bit etc of course.


EDIT:

BTW, I have a very simple sketch for a PseudorandomNumberGenerator protocol, something which I think should be added to the Random API of the stdlib, since it's needed for eg initializing or restarting a PRNG with some specfic state or seed in a generic context. I've needed it for writing tests for this stuff.

Here it is together with the WyRand generator
protocol PseudorandomNumberGenerator : RandomNumberGenerator {
    associatedtype State
    init(seed: UInt64)
    init?(state: State)
}
extension PseudorandomNumberGenerator {
    init(seed: UInt64? = nil) {
        self.init(seed: seed ?? UInt64.random(in: .min ... .max))
    }
}

/// The wyrand generator, translated from:
/// https://github.com/wangyi-fudan/wyhash
struct WyRand : PseudorandomNumberGenerator {

    typealias State = UInt64

    private (set) var state: State

    init(seed: UInt64) {
        self.state = seed
    }
    init(state: State) {
        // Every UInt64 value is a valid WeRand state, so no need for this
        // initializer to be failable, it will satisfy the requirement anyway.
        self.state = state
    }

    mutating func next() -> UInt64 {
        state &+= 0xa0761d6478bd642f
        let mul = state.multipliedFullWidth(by: state ^ 0xe7037ed1a0b428db)
        return mul.high ^ mul.low
    }
}