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;
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.
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.
procdump -ma -e 1 Project1.exe
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With