Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

  • Windows 32-bit: http://prnt.sc/dmd1ry
  • Android tablet: http://prnt.sc/dmd5uj

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;
like image 441
Alberto Miola Avatar asked Dec 21 '16 22:12

Alberto Miola


2 Answers

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.

like image 167
David Heffernan Avatar answered Nov 04 '22 20:11

David Heffernan


Posting this as an answer because it doesn't fit the comments

I know next to nothing about androïd but I would use following approach on Windows to narrow it down. Likely, a similar approach exists for Androïd.

  1. Keep temporary calculations in global variables. This makes it easier to extract the values from the crashdump we are going to take. Raise an exception when hitting a wrong value.
  2. Attach procdump to the running process. Procdump from Sysinternals allows you to create a dumpfile for each exception encountered. The commandline would be something like procdump -ma -e 1 Project1.exe
  3. Run the calculation and wait for it to raise the exception.
  4. Analyze the dump. The values of the tmp variables can be extracted from memory.

Code

var
  tmpRandom: Integer;
  tmpLn: Extended;
  tmpLnRandom: Extended;
  tmpExp: Extended;
  tmpTrunc: Int64;

procedure TForm1.btn1Click(Sender: TObject);
var
  I: Int64;
begin
  while true do
  begin
      tmpRandom := Random(3);
      tmpLn := Ln(2);
      tmpLnRandom := tmpLn * (1+tmpRandom);
      tmpExp := Exp(tmpLnRandom);
      tmpTrunc := Trunc(tmpExp);
      I := tmpTrunc;
      if (I and 1 = 1) then
        raise Exception.CreateFmt('I = %0:d', [I]);
  end;
end;

Example layout of variables

Example layout of variables

like image 21
Lieven Keersmaekers Avatar answered Nov 04 '22 20:11

Lieven Keersmaekers