A Random Decimal Fraction Block

Situation: Snap! provides an operator block that returns random integers. What if I would prefer a random decimal fraction for some reason?

Now, I agree with BH that IEEE floating-point cannot represent every possible value of a decimal fraction. Humor me, please; sometimes a fraction comes in handy, and may be suitable for simulations where close enough is good enough, as in tossing horseshoes.

Solution: combine the random-integer block with several other Snap! operator blocks into a custom block that returns a decimal fraction. Here is one way to approach it.

Planning: start with a 32-bit integer, i where 0 <= i < 2^32. Combine these Snap! blocks...


... into this:


All that remains is to divide by 2^32. Combine the result, above, with a few more Snap! operator blocks:


Finally, let's wrap it up into a custom operator block for convenience and the benefit of self-documenting code.


Now I have a block that says right on its face that it returns a random decimal fraction:



Note to Purists: I have learned to be humble when discussing the random-number topic in the presence of mathematicians and computer scientists. I make no claim regarding the distribution of the values coming out of a function like the one proposed here. Please do not veer this thread off into critiques of the JavaScript random number generator or into philosophies about randomness.

A canoe is not an aircraft carrier, but we can land a fish in a canoe even if we'd never use it to land an airplane. Snap! upholds playfulness in the pursuit of learning, and the JavaScript algorithm behind Snap's random-number tools may well serve the purpose.

One thing: "randomDecimalFraction" would be more Snap!-like if it were named "random decimal" or something.

Here is another way:

Well, sure, let a thousand flowers bloom. To quote Juliet,

"What's in a name? That which we call a rose
By any other name would smell as sweet."

Thanks for proposing alternative approaches. That block sure looks clever. Alas, my poor old brain can't tell what arithmetic it is performing, by just looking at it. When one reaches my age one craves clarity over cleverness.

I will cautiously say that ranging the function over the integers from 0 to ( 2^32 - 1 = ) 4,294,967,295 may result in a greater number of possible fractional values compared to ranging it over only the integers less than 16 or 17. Perhaps one of our mathematical friends might contribute a thought?

I can explain. It generates a list with 17 items (numbers from (0) to (16)). It uses pick random (0) to (9) in map to get a list of 17 random digits. It puts it in join to get a string. 1 * ("0." + (the random digits)) converts it to a number.

It picks 17 random 1-digit numbers, joins them into a 17 decimal digit number, i.e. 0.35841678165299237, then actually makes it a number and not a string.

Here's my contribution!

With this one, you can control the number of decimals
if you want 3 decimals, multiply the min by 1000 and the max by 1000, find a random number in min and max and divide it by 1000. Now you have 3 digits after the number...

With this one, a real number is return
The pick random function can return a real, try it with

You can also do this with the original function:

All roads lead to Rome...

About the mathematical validity: I believe all these approaches are fine, producing uniformly distributed pseudorandom numbers.

The finite number of bits in floating point format, of course, implies that all floats are rational; you can't exactly represent any irrational number. But that's true regardless of what algorithm for pseudorandomness you choose.

(If I were commenting on an algorithm rather than on the general mathematics involved I'd loosely say "random" like everyone else, but in this context it's worth keeping in mind that all these algorithms are algorithmic -- from the same starting point they'll always generate the same sequence of values. To get true randomness you need special hardware that depends on quantum effects. This doesn't mean a quantum computer; a precise measurement of the voltage drop across a diode will do the job.)

Even then I have a feeling that nothing is truly random.

I'm not a physicist, but from what I read, quantum events really are random. Quantum is weird.

Very weird. Particles splitting, going through solid objects, teleporting, etc.

Thanks @loucheman for showing me how to return non-integer results from the Snap! built-in random number block. The Geezer learns again!

I'm going to adopt the solution by @loucheman because it's better than mine. My proposed solution, above, generates only a few billion possible values. By contrast that by @loucheman accesses the full range of the built-in function and is self-documenting. Now, there's a refinement I want to propose...

Doze Alert! I shall indulge in explanation. Readers who just want the food without the cooking should skip to the end now.

One concern to consider when passing a value greater than one into the "to" parameter of the function. "... to 1.000000000000001" allows the function to return a value of 1.0 or more. However unlikely, the possibility should be -- and can be -- avoided when the desired result is a decimal fraction less than one. Happily, polyhedral dice light the way forward.

We can use dodecahedral (20-sided) dice to roll for numbers in the range, say, 1 to 16, as I do when I want (truly?) random hexadecimal byte values from a non-algorithmic source. Simply roll again when 17 through 20 come up. A dice is not a lamda function; it never remembers what it did the last time. It doesn't mind when we reject a number and roll again.

I accept that the function returns pseudorandom numbers such that every value it can possibly return has a statistically equal likelihood to occur, like a zillion-sided dice. Which means we need only to reject values of 1 or greater. The following reporter block incorporates the refinement:



set [a v] to (pick random (0) to (1.000000000000001))
repeat until <(a) < [1]>
set [a v] to (pick random (0) to (1.000000000000001))
can be simplified with the Iteration, Composition library to

repeat {
set [a v] to (pick random (0) to (1.000000000000001))
} until <(a) < [1]> :: control loop

Or even simpler:
(pick random (0.5) to (1.5)) - (0.5)

Responding to @bubgamer07_bungamer0 and @warped_wart_wars. A couple of challenge face those two solutions.

Challenge at the upper bound: the subtraction from the maximum parameter retains the possibility of returning 1.0. A function designed to limit the range to only values less than zero has to deal with that possibility.

Challenge of floating point representation, maybe? This concern is a little more hypothetical only because my knowledge is limited. I do not understand IEEE floating point at the binary level as well as I would like. I do understand that decimal powers of 0.1 do not convert exactly. There might be rounding errors, up or down, in the conversion. Which might result in subtracting something slightly more than, say, 0.000000000000001 from the minimum parameter. Which would return a negative value. Again, that's a possibility to be guarded against.

I'd feel more confident after testing the return value explicitly and rejecting any that fall outside the interval of the positive decimal fractions f where 0 <= f < 1.

You don't need quantum for that. Even classically, a "solid" object is mostly vacuum. Small enough particles, especially ones with zero mass and zero charge, can slip right through.

This is essentially @snapenilk's technique that you rejected, except that since these "dice" are in software you can have a 10-sided one or a 16-sided one or any base you like. To make a 30-digit random fraction you just roll the die 30 times.

Dodecahedrons are 12-sided. What you're thinking of is "icosahedron".

Sincere apologies to @snapenilk. I did not mean to reject that perfectly useful algorithm.

Embracing the @loucheman solution, modified for interval enforcement, reflects an admiration for its simplicity, but no disapproval of the other. Sorry I failed to say it as clearly as I wish I had done. Please forgive me.

PS apologies also to Plato, may he rest in peace, for misnaming one of his Solids. Icosahedron, I surely ought to have said.

The Geezer needs a nap now...