Search code examples
androiddelphifiremonkey

Weird values for math expression


I am developing a game that works on Windows and Android but it has an issue that I cannot solve. Basically I have a 4x5 grid with some buttons and these buttons are filled each second with a random number that must be 2, 4 or 8. If you tap on two buttons with the same number, the sum is calculated. This is a firemonkey project.

The game works fine but you can see the problem in the pictures below. When I run the game in my windows machine it generates 2, 4 or 8. Under android It generates 2, 4, 7 and 8. Random numbers are created in this way:

valueToOutput := Trunc(Exp(Ln(2) * (1+Random(3))));

That variable holds the number to be displayed in the button. Why do I get different results in windows and android? These are two screenshots

I am sure that the function is correct because I have plotted it ( exp(ln(2)*(1+x)) = http://prnt.sc/dmdc3u) and when x is 0,1 or 2 (= when the random number is 0,1 or 2). Could this be an issue with the compiler?

Note: I have already solved this problem using the workaround you can see below, but at first I used the code that you can see in the problem and I'd like to understand what's going on.

valueToOutput := Trunc(Exp(Ln(2) * (1+Random(3))));

//this will always give 2, 4 or 8
if valueToOutput = 7 then
 valueToOutput := valueToOutput + 1;

Solution

  • Floating point calculations are repeatable, for the same input, for the same floating point control state.

    With three distinct inputs your expression should therefore have three distinct possible outputs. The only explanation therefore is that something changes the floating point control state, e.g. the rounding mode, precision, etc. during program execution.

    If floating point arithmetic could perform the calculation exactly you would not need to round to an integer. But if you must round, at least round to the nearest using Round rather than Trunc.

    That said, this is categorically the wrong way to perform a discrete task of picking at random one of 2, 4 and 8. Do that like so:

    case Random(3) of
    0:
      Result := 2;
    1:
      Result := 4;
    2:
      Result := 8;
    end;
    

    Another way is to place the possible outputs into an array and then choose them like this:

    Result := arr[Random(3)];
    

    This becomes more attractive when there are more values to choose from.

    A golden rule is that if you can avoid using floating point do so. Floating point is slower, and harder to reason about than integer arithmetic. Use it only when necessary.