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
}
}