0

I want to design a function that would return true most of the time but theoretically could return false.

So far, all I've come up with is (with comments added, due to some confusion):

function true(seed) {
  // Poop, you can't `seed` Math.random()!
  return Math.random() !== Math.random();
}
// but if I had that seed, and Math.random() was seedable, 
// I could make this function return false.

However, this runs into a few limitations.

  1. Math.random() by implementation is not seedable therefore calling a random number generator twice in a row (with no other entropy) will never return the same number twice.
  2. Math.random() will return a value between 0.0000000000000000 and 0.9999999999999999, which is sixteen digits of precision. So according to Binomial Distribution the probability of true not being true is (1/9999999999999999)^2. Or 1.0 e-32.

What I am trying to build is something that would only return false in the probability of 1/some integer that grows larger and larger. This is purely a thought experiment. There is no constraint on space and time, although if your answer has considered that as well then that's a bonus.

EDIT: I guess, here is another way to ask this question.

Take a look at this Plunker. https://plnkr.co/edit/C8lTSy1fWrbXRCR9i1zY?p=preview

<script src="//cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.0/seedrandom.min.js"></script>

function f(seed) {
  Math.seedrandom(seed);
  return 0.7781282080210712 === Math.random();
}

console.log(f());              // Behaves as expected
console.log(f(Math.random())); // Pretty much everything returns false

function t(seed) {
  Math.seedrandom(seed);
  return 0.7781282080210712 !== Math.random();
}

console.log(t());              // Returns true.
console.log(t(Math.random())); // All is well with the world.


// But, if you have the right seed!
console.log(f('Udia'));        // Holy shit, this returned true!
console.log(t('Udia'));        // Holy shit, this returned false!

What is the most interesting way to write a function that returns true? It can run forever, take up as much space as possible, etc. But it must return true. (and have the smallest probability of returning false.)

Alexander
  • 841
  • 1
  • 9
  • 23
  • https://github.com/udia-software/udia-boolean – Alexander Aug 24 '16 at 04:53
  • I think your calculation is wrong. The odds of `true` should be just 1/9999999999999999, not squared. – user94559 Aug 24 '16 at 04:56
  • Also, I think you want `!==`... otherwise this will return `false` almost all of the time rather than `true` almost all of the time. – user94559 Aug 24 '16 at 04:57
  • I guess what I meant was, if I had a unit test, it would fail when `true() === true()`, which would be 1/9999999999999999 squared. Sorry for the confusion! – Alexander Aug 24 '16 at 04:57
  • I think the way Math.random is implemented will make it impossible to get the same value twice in a row. – Thilo Aug 24 '16 at 04:58
  • @Alexander I don't think that's right either. `true() === true()` is true when both return true or both return false. The odds of them *both returning false* are 1/9999999999999999^2, but the odds of them both returning true are way higher. – user94559 Aug 24 '16 at 05:00
  • @smarx I edited that comment. Thanks! – Alexander Aug 24 '16 at 05:02
  • @Brad: Normally, a non-cryptographic random number generator has a simple seed number which represents *all its state*. The next number is derived from the internal state only. If you got the same number again, you will get the same number forever. https://en.wikipedia.org/wiki/Linear_congruential_generator – Thilo Aug 24 '16 at 05:02
  • "*return a value between 0.0000000000000000 and 0.9999999999999999, which is sixteen digits of precision*" - no, that's not how `Math.random()` works at all. You should learn about [floating point numbers](http://floating-point-gui.de/formats/fp/) – Bergi Aug 24 '16 at 06:24
  • 1
    @Thilo: No, there's some hidden state, not all of it is exposed in the result. Also have a look [here](http://stackoverflow.com/a/31257982/1048572) – Bergi Aug 24 '16 at 06:27

5 Answers5

2

Fill buffers of whatever size you want with random data, and compare them.

Untested, but try something like this:

const length = 32768;
let values = [
  new Uint8Array(length),
  new Uint8Array(length)
];
window.crypto.getRandomValues(values[0]);
window.crypto.getRandomValues(values[1]);

let i;
for (i=0; i<length; i++) {
  if (values[0][i] === values[1][i]) {
    break;
  }
}
if (i === length-1) {
  console.log('The (nearly) impossible has occurred!');
}
Brad
  • 159,648
  • 54
  • 349
  • 530
  • Okay, that's interesting! You are comparing random bits in an array. I guess what I'm trying to answer is the case where `function(seed){return true; }() === function(seed){return false;}();`. EDIT: It should behave predictably? If I seed a random number generator with the right seed, I can make it equal anything it want. Even make a function that only returns true, return false. – Alexander Aug 24 '16 at 05:23
  • This is good because it allows to tune the probability to be really tiny (more than a float number could express). OTOH, this is pretty much impossible to ever happen, so this whole question is a bit weird. – Thilo Aug 24 '16 at 05:25
  • 1
    @Alexander I don't understand what you're asking. – Brad Aug 24 '16 at 05:25
  • 1
    @Thilo Perhaps if we gave an infinite number of monkeys typewriters, one of them would clarify the question by chance. :-) – Brad Aug 24 '16 at 05:26
1

Since Math.random() will not yield the same number twice in a row, do this:

var improbabilityDrive = Math.random();
var discard = Math.random();

function true() {
  return Math.random() !== improbabilityDrive; 
}

Or, if you don't want global variables, just discard the next few results:

function true() {
  var improbabilityDrive = Math.random();
  var discard = Math.random();
  discard = Math.random();
  discard = Math.random();
  //... more discards, if necessary
  return Math.random() !== improbabilityDrive; 
}

Edit: Drop Probability each time it's called

OP asked if it's possible to make it less and less likely to return (false, I think is what you meant?)

var hitsRequired = 0.0;
var improbabilityDrive = Math.random();

//Increasingly Lower Chance of 'false' With Each Call
function superTrue() {
  hitsRequired += 0.1; //Set Growth Factor here (algebraic: +=, geometric: *=)

  for (int i = 0; i < hitsRequired; i++) {
    if (trueish()) return true;
  }

  return false;
}

//Same Theoretically Low Chance of 'false' Each Call
function trueish() {
  var discard = Math.random();
  discard = Math.random();
  discard = Math.random();
  //... more discards, if necessary
  return Math.random() !== improbabilityDrive; 
}

Edit 2: Insanely Low Probability

After re-reading your question, I think you're after the most-low probability you can get. This is far, far, below reason:

//Increasingly Lower Chance of 'false' With Each Call
function superDuperTrue() {

  for (int i = 0; i <= 9007199254740992; i++) {
    if (trueish()) return true;
  }

  return false;
}

The probability that this produced a false is:

(1/4503599627370496) ^ 9007199254740992 = 10 ^ ( - 10 ^ 17.15)

Wolfram Alpha Calculation

That would, by almost any reasonable measure, be such an absurdly low probability that it could just be assumed to never happen. I'd be surprised if it would return a single false if tried a trillion times per second until the heat death of the universe - putting that into wolfram alpha didn't even drop the number of the probability (1 trillion * 10^100 years until heat death of the universe * 3,156,000 seconds / year * that probability = that probability, subject to 14 decimal places of accuracy).

Basically, it would never happen, but it theoretically possible.

At 1,000,000,000,000 tries per second:

  • For n=0, 38 minutes would yeild a 50% chance of a single false.

  • For n=1, 325 billion years would yeild a 50% chance of a single false.

  • For n=2, 1500000000000000000000000000 years (1.5 * 10^17), or 110000000000000000 times the age of the universe would yeild a 50% chance of a single false.

  • ... Increase n up to the 9007199254740992, above, to make it as implausible as you desire.

Ehryk
  • 1,930
  • 2
  • 27
  • 47
  • That's cool! This definitely makes it **possible** for the function to return `false`. Is there a way to make that probability grow smaller and smaller? Ideally in a way that accepts a seed value, grows infinite disk space wise, and runs instantly? So if I had passed a seed to this function, it would behave predictably. Which would mean returning `true` a bunch of times. (but if I had the right seed, I could make it return `false`) – Alexander Aug 24 '16 at 05:14
  • That second version is not good. `Math.random()` won't repeat after only two steps either (for the same reason). You can just pick any fixed number for your `improbabilityDrive`. – Thilo Aug 24 '16 at 05:17
  • I think we're crossing streams here; I doubt every PRNG has been proven to not have any fix points, or had their entire domains exhaustively searched for one. Perhaps there are some that have been proven for all seed and input values; but until such a deterministic proof is presented it is _theoretically_ possible, which meet's OP's initial ask. – Ehryk Aug 24 '16 at 05:24
  • Edited to discard more than one result, though @Thilo – Ehryk Aug 24 '16 at 05:26
  • @Alexander Do you mean you want it to get increasingly less likely to be false? Or increasingly less likely to return true? Do you care how fast it grows? – Ehryk Aug 24 '16 at 05:27
0

One way to tune this yourself would be to repeat the process. E.g.:

function true() {
  var n = 1000;

  var first = Math.random();
  for (var i = 0; i < n; i++) {
    if (Math.random() !== first) {
      return true;
    }
  }
  return false;
}

Now your code in the original question is just the special case of n = 2, and the odds of returning false are 1/9999999999999999^(n-1).

user94559
  • 59,196
  • 6
  • 103
  • 103
  • A standard, non-cryptographic, simple PRNG will not return the same number twice in a row. – Thilo Aug 24 '16 at 05:04
  • @Thilo is that proven, or just highly improbable? How do you know a given PRNG doesn't have a fix point? – Ehryk Aug 24 '16 at 05:12
  • A PRNG is highly deterministic. Usually a simple multiplication / modulo operation. Here is the one from Chrome http://stackoverflow.com/a/20109332/14955. Fix points should have been avoided in the design of the parameters. – Thilo Aug 24 '16 at 05:14
  • @Thilo The V8 algorithm you point to doesn't seem to guarantee it won't produce the same number multiple times in a row. (It looks like `hi` and/or `lo` can get regenerated at any time.) That said, I accept that the odds of a `false` are not what they would be if a true source of random numbers were used. – user94559 Aug 24 '16 at 05:19
0

You can get an adjustable probability with

return Math.random() > 0.00000001; // play with that number 
Thilo
  • 257,207
  • 101
  • 511
  • 656
0

Numbers in js are IEEE 754 doubles. So Math.random() returns values between 0 and 0.999999999999999888977697537484.

To calculate the number of possible unique return values is simpler. IEEE 754 doubles have 52 bit mantissa. So the number of possible return values is: 2^52 or 4503599627370496.

Which means, the smallest ever possibility is 1/4503599627370496.

Now, if you REALLY want the smallest possible probability just compare the output of Math.random() to one of it's unique outputs. Note that since not all decimal numbers are representable as floats you should use a number that has an exact representation. For example 0.5.

So, the smallest possible probability is:

Math.random() === 0.5

Which has exactly 1 in 4503599627370496 chance of happening. The theoretical smallest probability.

So if you want your function to return true most of the time you should do:

function true() {
    return Math.random() !== 0.5;
}

Warning

Note that this may not be what you want. It is very, very rare for this smallest probability to happen. As a test I ran the following code:

for (i=0;i<1000000000;i++){ // loop to one billion
    foo=Math.random();
    if (foo === 0.5) {
        console.log('>>> ' + i)
    }
}

I ran the above loop five times and only observed the console.log() output once. In theory you should expect to see the event happen once every 4.5 quadrillion times you call that function. Which means that if you call the function once each second it will see the it return false roughly once every 143 million years.

slebetman
  • 109,858
  • 19
  • 140
  • 171
  • Well, you can obviously get a lower probability than this... e.g. `return Math.random() !== 1 || Math.random() !== 2;`. Presumably that would square the probability of a `false`. – user94559 Aug 24 '16 at 07:09
  • @smarx: Well, `0` is definitely smaller but that means never happening. This is the smallest probability that can happen. – slebetman Aug 24 '16 at 07:13
  • Are you saying two consecutive calls to `Math.random` can't return `1` and then `2`? – user94559 Aug 24 '16 at 07:15
  • @smarx. `Math.random()` cannot return either `1` or `2` since it ONLY returns numbers between 0 and 1 (including 0 but never 1) – slebetman Aug 24 '16 at 07:16
  • Oh, sorry, make that `0.1` and `0.2`. – user94559 Aug 24 '16 at 07:17
  • My basic point is that your method has the same probability as that in the original question, but he's asking for an improvement (smaller possibility of `false`), and you seemed to be claiming that's impossible. – user94559 Aug 24 '16 at 07:19
  • @smarx: Careful, not all decimal numbers are exactly representable by IEEE 754. The closest to your example would be `0.100000000000000005551115123126` and `0.200000000000000011102230246252` – slebetman Aug 24 '16 at 07:19
  • Pick whatever numbers you like. – user94559 Aug 24 '16 at 07:20
  • I think this also runs into the limitations of PRNGs... if an algorithm is chosen with no hidden state, testing twice is presumably equivalent to testing once. (If `n` comes up, `n'` will always come up next.) So it depends how "real-world" this code is expected to be and what implementation we're assuming. – user94559 Aug 24 '16 at 07:23
  • @smarx: OK. `a || b` will give you a higher probability of happening. Not lower. `a && b` will give you lower probability. And yes, that would square the probability of false. If the RNG is perfectly fair then it's easy to keep reducing the probability by just finding sequence of repeating numbers so `random == 0.5 && random == 0.5` will square 4.5 quadrillion and `random == 0.5 && random == 0.5 && random == 0.5` will cube it. – slebetman Aug 24 '16 at 07:26
  • Here we want a *higher* probability of true, so we would want `||`. Yes, your last sentence describes my answer. – user94559 Aug 24 '16 at 16:01
  • @smarx: Yes, a lower probability of the event happening represents a higher probability of true. Remember, we're generating false at the low probability, not true. – slebetman Aug 24 '16 at 23:36
  • I believe `||` is correct. Are we in agreement, or do you think `||` is wrong? – user94559 Aug 25 '16 at 02:14
  • @smarx `||` is wrong in this case (returning false). It is correct if we generate the opposite probability: `random != 0.5 || random != 0.5` but that's not how my examples are written. As such `&&` is correct – slebetman Aug 25 '16 at 02:34
  • That's exactly how your example is written. Today you have `return Math.random() !== 0.5`. I'm suggesting that `return Math.random() !== 0.5 || Math.random() !== 0.5;` would mean we return `false` less often, which I believe is the goal. (I actually suggested different numbers, but the reason `||` is correct is the same.) So I think we agree? – user94559 Aug 25 '16 at 02:41