I don't think the difference will show up for small N. This is an asymptotes thing. Try it for N = 100, that's what I did. For example:
>>> np.product(np.random.random(100))
5.469939152265385e-43
>>> 1 / np.e**100
3.7200759760208555e-44
>>> 1 / 2**100
7.888609052210118e-31
The underlying thing here is that random(random()) in this case is the same as random() * random(). So random(random(random(...))) is the same as random() * random() * random() and then the analysis goes on. And sure, random() * random() has a mean close to 1/4. But the dynamics change as N becomes large.Edit - and just in case you doubt whether random() * random() * ... is a valid way of doing this, I also just checked the laborious way of doing it:
>>> def foo(n):
... result = 1.0
... for _ in range(n):
... result = np.random.uniform(0, result)
... return result
...
>>> foo(100)
1.4267531652344414e-46
>>> foo(100)
7.852496730908136e-49
>>> foo(100)
1.3216070221780724e-41