Coin Flip

I have recently been developing a coin flip program, and I feel it is developed enough to release to the public.

--Coin Flip was originally made by Adam9812. It is Public Domain.

--Coin Flip 0.7.2
 
function setup()
    
    flipNumber = math.random(-6,6)
    flipAmount = 0
    headsAmount = 0
    tailsAmount = 0
    
    parameter.action("Flip Coin",flip)
    parameter.number("flipPoint",-6.000001,6.000001,0)
    
    parameter.watch("flipNumber")
    parameter.watch("flipAmount")
    parameter.watch("headsAmount")
    parameter.watch("tailsAmount")

end

function draw()

    background(40, 40, 50)
    stroke(0, 0, 0, 255)
    fill(95, 50, 31, 255)
 
    ellipse(WIDTH/2,HEIGHT/2,141)
 
    fill(0, 0, 0, 255)
 
    if flipNumber <= flipPoint then
        sprite("Dropbox:Head",WIDTH/2,HEIGHT/2 + 13)
    else
        sprite("Dropbox:Tail",WIDTH/2,HEIGHT/2 + 6)
     
    end   
 
    fontSize(11)
    font("Copperplate-Light")
    text("In Codea we trust",WIDTH/2,HEIGHT/2 - 38)

end

function flip()
    
     flipNumber = math.random(-6,6)
     flipAmount = flipAmount + 1
    
    if flipNumber <= flipPoint then
        headsAmount = headsAmount + 1
        
    else
            
        tailsAmount = tailsAmount + 1
        
    end
end

Oh, and there are sprites I’ve used that are only on my Dropbox. Here are the Links:

https://www.dropbox.com/s/u2ic767pvhofgr7/Head.png?m=
https://www.dropbox.com/s/qpe9kh6k3r3mtey/Tail.png

@Adam9812 - why do you flip the coin with a random number between -6 and +6? And why let the user choose the probability with flipPoint?

I think there is a minor flaw with the default value of 0 for flipPoint. The random number can take 13 different integer values from -6 to +6, including 0. If it is zero or less (which is 7 of these numbers) then it is heads, or if it is greater (6 of these numbers) then it is tails. So your coin is biased toward heads.

I would have simply used math.random() which gives a fractional number between 0 and 1, and tested if it was greater or less than 0.5 to choose between heads and tails.

@Ignatz if it was a true british penny it would be biased towards tails!

@Ignatz And if it was EXACTLY 0.5? Sorry, can’t help it.

How about just math.random(1, 2) == 1? Then it wouldn’t be biased at all, except for the pseudo-random nature of math.random.

Ok. Thanks for the advice. I won’t have access to this device for a while so I will fix it later today.

@SkyTheCoder If it is exactly 0.5, round up.

From PIL:

When we call it without arguments, it returns a pseudo-random real number with uniform distribution in the interval [0,1).

To split the interval [0,1) in two halves of exactly the same size, we split it as [0,.5) and [.5,1). So .5 rounds up and there’s no bias.

In any case, I’m pretty sure that if math.random() is biased in this way then math.random(1,2) will also be biased since the implementation will be pretty much exactly what Ignatz says.

@Andrew_Stacey If math.random() returns a non-integer value, then that means there’s an exact middle, 0.5? So then you have to decide whether that exact middle should count as 1 or 0 when rounding. So that’d have just the tiniest bias that it would be 1.

math.random(1, 2) returns only 2 possible results, either 1 or 2. No middle to decide whether it would be 1 or 2.

Example of the bias in math.random, imagining the decimals are clipped to only one:

0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0

As you can see, there are 5 values before and after the 0.5, meaning 0.5 is the exact middle. So if there is a middle, and you have to decide whether the middle counts 0 or 1, it’s biased. If the exact middle counts as 1, it’s biased toward 1. There’d be a 6 in 11 chance it was 1, and 5 in 11 it was 0. Vice versa for 0.5 being rounded to 0.

Since math.random(1, 2) only has 1 or 2, there is no middle, it’s an exact 50-50 chance.

But of course, remember that any of this could be biased beyond our control, what with pseudo-random instead of true random results.

Just run this a few times to see any bias.


function setup()
    tab={0,0}
    for z=1,1000000 do
        r=math.random(1,2)
        tab[r]=tab[r]+1
    end
    print("1 = "..tab[1],"  2 = "..tab[2])
    print(tab[1]-tab[2])    
end

@SkyTheCoder Your mistake is in assuming that 1.0 is a possible output of math.random(). It isn’t, according to PIL. So in your thought experiment (which is a good idea, by the way) then the possible values are actually:

0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9

and now it is, I hope, clear that .5 rounds up.

@SkyTheCoder, [0,1) which is what PIL says according to Andrew_Stacey, is interval notation meaning it will return anything from and including 0 up to but not including 1

@JakAttak math.random(0,1) will only return 0 or 1.

EDIT: math.random() will return a fractional value between 0 and 1 but not including 0 or 1.

@SkyTheCoder, @Andrew_Stacey, etc - since math.random() calculates to 14 decimal places, any error around 0.5 (if there was one) would be vanishingly small. The errors (non random faults) in the random number function are likely to be far greater, so it would be pointless worrying about 0.5 - unless of course you are rounding heavily as suggested above.

(Of course, I know that SkyTheCoder was only pulling my leg when he started all this).

I did math.random() in a 10,000,000 loop and never hit a .5 . So it’s probably safe saying less than .5 or greater than .5 and not have to worry about hitting .5 .

Everything’s a learning opportunity, as they say.

I know that @SkyTheCoder’s original post was just trolling, but if you’re going to troll pedantically, you should at least be right! There are a few subtle but important errors in what everyone (including me) has said so far and I think it’s worth taking a moment to go through them in detail.

  1. (@SkyTheCoder) “Since math.random(1, 2) only has 1 or 2, there is no middle, it’s an exact 50-50 chance.”

    Well, no. Let’s play a game. We’ll roll a die; if the result is a perfect number[1], I’ll pay you $10, if not then you pay me $10. That’s a two state system so according to your logic it’s an exact 50-50 chance and the game is fair.

    math.random(1,2) is only better than if math.random() < .5 then return 0 else return 1 done if math.random(1,2) uses a better method of figuring out which number to return. It doesn’t (but we’ll come to that in a minute).

  2. Then there’s all the stuff about the difference between [0,1) and [0,1] which I think has now been adequately covered in the above. Though I’d just add that theoretically there is no difference between the two as if we can take any value in [0,1] then the probability of getting exactly 1 is exactly 0. However, as we can’t take any value but only those in the precision of the device, we get a positive probability of getting 1 if the interval is [0,1]. But it isn’t, according to PIL.

  3. Or is it. Here’s where it starts getting interesting. I looked up the code for math.random. This is what I found.

static int math_random (lua_State L) {
/
the %' avoids the (rare) case of r==1, and is needed also because on some systems (SunOS!) rand()’ may return a value larger than RAND_MAX /
lua_Number r = (lua_Number)(rand()%RAND_MAX) / (lua_Number)RAND_MAX;
switch (lua_gettop(L)) { /
check number of arguments /
case 0: { /
no arguments /
lua_pushnumber(L, r); /
Number between 0 and 1 /
break;
}
case 1: { /
only upper limit /
int u = luaL_checkint(L, 1);
luaL_argcheck(L, 1<=u, 1, “interval is empty”);
lua_pushnumber(L, floor(r
u)+1); /* int between 1 and u' */ break; } case 2: { /* lower and upper limits */ int l = luaL_checkint(L, 1); int u = luaL_checkint(L, 2); luaL_argcheck(L, l<=u, 2, "interval is empty"); lua_pushnumber(L, floor(r*(u-l+1))+l); /* int between l’ and `u’ */
break;
}
default: return luaL_error(L, “wrong number of arguments”);
}
return 1;
}


    Let's take this in reverse.  The situation `math.random(1,2)` is handled by the final case.  Then we get our random number (in the interval `[0,1)`) and apply the function `floor(r*(u-l+1))+l` which generates an integer between (and including) `l` and `u`.  The key point here is that if the `math.random()` is biased then so is `math.random(1,2)`.

    But is it?  Well, it's defined as `(rand()%RAND_MAX) / RAND_MAX`.  The modulus function is there to ensure that we never get `1` as an answer.  But there is a serious problem here: it doesn't throw away `1` if it gets it, it reassigns it to `0`.  So in fact, `math.random()` returns `0` **twice as often as it should**.  If we had infinite precision, this would be twice zero so no difference (really, honestly, absolutely no difference).  However, as we don't have infinite precision then that really is skewed.  Moreover, the comment above this line indicates that on some systems `rand()` can return a value above `RAND_MAX` so we're not dealing with the case that `rand()` returns just `RAND_MAX` (which is approximately 1/precision) but that `rand()` returns values in some unspecified interval above `RAND_MAX` (and without knowing the interval, it's impossible to know how bad this is).

    To illustrate the point, imagine we're back rolling the die.  But we want to decide between 5 options, not 6.  So we want to simulate a 1-in-5 chance with a fair die.  Here's two possibilities, which would you choose?

    1. The values 1 through 5 mean what they say, if you get a 6 then roll again (and keep rolling until you don't get a 6).
    2. The values 1 through 5 mean what they say, if you get a 6 then count it as 1.

    To illustrate the issue with the `SunOS` implementation of `rand()` then imagine instead of 5 options, you have 4 and you replace "if you get a 6" by "if you get a 5 or 6".

Ultimately, it all depends on the behaviour of the inbuilt `rand()` function.  I've not been able to find out how iOS implements that (not that I've looked very hard) but I did come across a [very interesting article](http://www.azillionmonkeys.com/qed/random.html) about how bad `rand()` is in general and what can be done to fix it (without going all the way to Mersenne Twisters and the like).

And just to forestall one objection to this, the point is *not* the distinction between random and pseudo-random.  It's all about the *distribution* thereof, whether the sample is truly random or not.


[1] Did I say that *everything* was a learning opportunity?  I meant it.  Look it up.

Here’s a link to the Darwin source of rand(). It would indicate that the lua source ought to replace RAND_MAX by RAND_MAX+1 in both places.

finishReadingPost = math.random(1,2)

if finishReadingPost == 1 then
   print("You kept my interest Andrew - congrats!"
   Click PostComment
else
    ReadNextPost()
end

@Andrew_Stacey - I take the view that math.random(1,2) cannot be less biased than math.random(), and any bias in math.random() is in the 14th decimal place and of no consequence.

In which case, if I use math.random(), I just saved myself the trouble of looking up all those documents because there is no (practical) point in considering other options (unless you are a mathematician who just has to know :wink: )

Many years ago when the Excel RNG was really bad, I adapted Mersenne Twister code to VBA, and ran a library of tests, whose name escapes me now. Fortunately, Excel improved the RNG soon afterwards.

Here is a new version, with only 2 possible numbers. This should eliminate the bias.

--Coin Flip was originally made by Adam9812. It is Public Domain.

--Coin Flip 0.7.2
 
function setup()
    
    flipNumber = math.random(1,2)
    flipAmount = 0
    headsAmount = 0
    tailsAmount = 0
    
    parameter.action("Flip Coin",flip)
    
    parameter.watch("flipNumber")
    parameter.watch("flipAmount")
    parameter.watch("headsAmount")
    parameter.watch("tailsAmount")

end

function draw()

    background(40, 40, 50)
    stroke(0, 0, 0, 255)
    fill(95, 50, 31, 255)
 
    ellipse(WIDTH/2,HEIGHT/2,141)
 
    fill(0, 0, 0, 255)
 
    if flipNumber == 1 then
        sprite("Dropbox:Head",WIDTH/2,HEIGHT/2 + 13)
    else
        sprite("Dropbox:Tail",WIDTH/2,HEIGHT/2 + 6)
     
    end   
 
    fontSize(11)
    font("Copperplate-Light")
    text("In Codea we trust",WIDTH/2,HEIGHT/2 - 38)

end

function flip()
    
     flipNumber = math.random(1,2)
     flipAmount = flipAmount + 1
    
    if flipNumber == 1 then
        headsAmount = headsAmount + 1
        
    else
            
        tailsAmount = tailsAmount + 1
        
    end
end

@Andrew_Stacey Now that I know that math.random() will never return 1, then yes, rounding up from 0.5 is correct. This has gotten quite off-topic though, we should let Adam have it all to his program.