There's Math.random(), and then there's Math.random() (2015)
v8.dev
v8.dev
I have a little puzzle I've been trying to solve in my spare time; maybe someone here can point me in the right direction.
The puzzle is: given a float from Math.random(), suppose you know only whether it is greater than it is then 0.5 (i.e you only see the result of a coin flip which depends on Math.random()). What is a practical method to reverse out the state of the xorshift+ generator, given multiple such successive observations of the output?
Any input appreciated!
There are a series of challenges called "fastrology" from PlaidCTF 2023, which is about predicting Math.random() given some partial output (e.g. Math.floor(Math.random() * k)) with several variants. You can try to find writeups for that challenges, those solvers should be easy to adapt to predicting Math.random()>0.5 .
tl;dr For a "Math.random() > 0.5" function, you need ~128 successive outputs, each of which leaks exactly 1 bit of the state at that particular iteration, and then you "just" solve a system of linear equations over gf(2) to recover the full state for any given iteration.
You can use z3 to solve it automagically, or you can write your own gaussian elimination solver (which works out much faster than z3 in practice).
Math isn't exactly my strong suit, so it took me quite a bit of bashing my head against the wall to make sense of things and turn it into code, so I've been planning on writing a blog post that explains this all in-depth (or at least, as much depth as I understand it).
For Chrome, there's an added catch in that it generates random numbers in batches, and returns those batches in reverse-order, so you'd need to do a small brute-force to identify where the batch boundaries are in your output sequence.
For Firefox and Safari, there's an added catch in that they add together the two halves of the state before returning it. This can also be worked around with a small brute force.
It's sort of sad it isn't all AES-CTR or ChaCha12. Fast enough not to be the bottleneck in your webpage, and if you find any hint of a pattern in the output, good news, you get to publish a paper on it!
What I'm saying is that outside rare, extreme situations--maybe some HPC Monte Carlo simulation where you need gobs of randomness--you might as well use strong primitives for all your randomness, because they work well and are now plenty cheap for the use case with hardware support, etc.
The games to find a fast non-cryptographic function that passes enough statistical tests, etc., don't make as much sense to me when cryptographic randomness costs a few cycles a byte. There is not much upside to every standard library exposing a bad random generator that we have to warn people not to use. The handful of people that really need xorshift128+ can figure something out.
Maybe still a position you disagree with, but at least clearer without the sarcastic gloss.
The goal for Math.random is to have much lower memory and speed impact. You can certainly disagree with their priorities but given those priorities, it makes sense to go with a much simpler algorithm that can be reversed easily.
I'm saying the cost of running actual cryptographic primitives has dropped a ton over time: on computers from decades ago a cheaper flavor of pseudorandomness was clearly necessary, now hardware AES is very cheap. And webpages aren't typically massive doing HPC simulations or other things that will be bound by the PRNG taking a few cycles per byte.
So the memory/CPU benefit of keeping the bad PRNG around is not obviously still worth it to me. In your words, I think I disagree with their priorities, particularly because the cost savings are not what they used to be.
it's a bit of a nitpick, but i believe there are 1023×2⁵² such numbers, which is quite a bit more. there are 2⁵² double precision floats in just [0.5;1)!
But it's not, and will never be. It can only ever return values that have been rounded down to some floating point number. Even if you realize this, you still might expect it to be able to return _any_ floating point number x∈[0, 1) with `eps(x)` probability. But that's not typically the case, either. Typical implementations round down to the previous multiple of `eps(1.0)` or `eps(0.5)`.
It changes the fundamental property of the distribution — the cdf — without stating it.
With floating point realizations of [0, 1) intervals:
cdf(p) = P(random() < p) := p
With (0, 1] intervals: cdf(p) = P(random() <= p) := pFollowing your post, I've found the following fast and straightforward and SIMD-friendly implementation that uses all 64 bits for a [0, 1) distribution:
```julia
function random_float(rng)
r = rand(rng, UInt64)
last_bit = r & -r
exponent = UInt64(2045)<<52 - reinterpret(UInt64, Float64(last_bit))
exponent *= !iszero(r)
fraction = ((r ⊻ last_bit)>>(8*sizeof(UInt64) - 52)) % UInt64
return exponent | fraction
end
``` double random_double(rng_t *rng) {
return((double)(random_uint64(rng) >> 11) * 0x1.0p-53);
}
It has the advantage of not needing bit_cast (which C lacks) and has 53 instead of 52 bits of randomness.Java's random number generator returns exceptionally non-random values, but Sun, er, Oracle, won't fix it because of the most insane of reasons: unlike in any reasonable language, Java's PRNG essentially has a contract to be deterministic. There's seemingly a worry that someone, somewhere, is actually relying on java.util.Random to always produce the same random number sequence for a given seed from Java version to version.
The lesson I learned, don't use random() when you want hash(). random is for non-deterministic output, hash is for deterministic output. random(seed) is an artifact of implementation and should never be used for deterministic output.
The real wtf was that this was perl on win98, probably due to when it was implemented, they wanted dvd burning capability and someone sort of knew perl.
If you need determinism, then the proper thing to do is use your own RNG. There's been a good Mersenne Twister implementation on Java since 1999. But a system-wide generator should never be beholden to determinism.
I also don't think you've justified why determinism from a seed is a bad thing.
As to bugs. Let's start with the famous one:
This is due to boneheaded mistakes in Sun's choice of constants for its LCG and errors in its bit-handling. These are massive mistakes, which it can no longer fix.
Next, there's an outstanding bug in nextBytes(), which generates ints and then cuts them into bytes (a big no-no for this particular LCG).
Some unfortunate omissions: nextChar(), nextShort(), nextByte() are missing, and there is no save-state procedure. There is no nextDouble() method that is inclusive for one or exclusive for zero.
There was a notorious bug in nextGaussian() which would take the log of 0 and then divide it by 0, but that has long been fixed. :-) [And some other bugs which were fixed early on despite Sun's claim that it couldn't fix bugs due to its stupid nondeterminism promise. For an RNG!]
1. Python: The `random` module can be seeded to produce deterministic random sequences. 2. Java: Java's `Random` class can be seeded to produce deterministic random sequences. 3. C++: The C++ Standard Library provides functions like `srand` and `rand` that can be used for deterministic random number generation when seeded. 4. C#: C# offers the `System.Random` class, which can be seeded for deterministic randomness. 5. JavaScript: In browsers and Node.js, the `Math.random` function can be overridden to make it deterministic. 6. MATLAB: MATLAB's `rand` function can be made deterministic by setting the seed with `rng`. 7. Ruby: Ruby's `Kernel.rand` method can be seeded for deterministic random numbers. 8. Julia: Julia provides a `Random.seed!` function to set the seed for deterministic randomness. 9. R: R has functions like `set.seed` to control the randomness and make it deterministic. 10. Swift: In Swift, you can use the `arc4random_uniform` function with a fixed seed for deterministic random numbers.
These are just a few examples, and many other programming languages and libraries offer similar functionality for deterministic random number generation when a specific seed value is used.
And since the exact algorithm has been documented for java.util.Random you can't just change it.
That being said, .NET had a similar problem, with System.Random having some drawbacks and bugs over the years. They chose to keep the algorithm the same when a seed is used (again, it's an important property you don't just break), but otherwise switch to something completely different. It gets more complicated even, as you can derive from Random there and some of those changes may be observable, so they also check for that: https://source.dot.net/#System.Private.CoreLib/src/libraries...
The issue is Java historically has guaranteed that, for all future versions and implementations of Java, the RNG will produce the exact same sequence given the same seed. It is deterministic across language versions and implementations. This is extremely unusual for a programming language, and a very bad idea. Java's RNG has grievous errors: but these bugs cannot be fixed because it would change the sequence! Its bugs are fixed in stone.
pretty well all of them, surely? eg. https://learn.microsoft.com/en-us/sql/t-sql/functions/rand-t... "For a specified seed value, the result returned is always the same."
Pretty well every language I'm aware of does it this way. Are we even talking about the same thing?
As for the others you point out, I'm afraid I can't speak for that. I'll just have to accept what you say.
They don't want code to break like that, which means they need to preserve this sort of thing.
It just seems like a terrible idea (as opposed to int)
For instance they are needed for testing the implementation of various functions of FP numbers, or for the so-called Monte-Carlo methods used for numerical integration, especially in multiple dimensions or for the solution of certain systems of equations with partial derivatives, or for the simulation of how some industrial product, e.g. an electronic integrated circuit, behaves when its components have random values of their characteristic parameters, within their accepted tolerances, as it always happens in any industrial production process, or the random numbers may be used in an optimization problem, to search through the possible solution space.
For random floating-point numbers it is frequent to need various other probability density distributions than uniform.
In most cases the best way to generate pseudo-random FP numbers is to derive them from uniform pseudo-random integers, using an appropriate method that will ensure the desired probability density distribution.
If the 256 bit private key of the encryption is derived from a large character set ( A-Z, 1-9, etc.), it does not matter if the RNG is not perfect. I am assuming it's not an online channel.
This is explained in more detail here https://www.reddit.com/r/cryptography/comments/fw2cdu/can_yo...