2016-12-21 14 views
6

Я разрабатываю игру, которая работает на Windows и Android, но у нее есть проблема, которую я не могу решить. В основном у меня есть сетка 4x5 с некоторыми кнопками, и эти кнопки заполняются каждую секунду случайным числом, которое должно быть 2, 4 или 8. Если вы нажмете на две кнопки с одинаковым номером, сумма будет рассчитана. Это проект firemonkey.Странные значения для математического выражения

Игра прекрасно работает, но вы можете увидеть эту проблему на рисунках ниже. Когда я запускаю игру в моем окне машины он генерирует 2, 4 или 8. В соответствии с андроидом Он генерирует 2, 4, 7 и 8. Случайные числа созданы таким образом:

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

Этих переменный держит номер, который будет отображаться на кнопке. Почему у меня разные результаты в Windows и Android? Эти две картинки

Я уверен, что функция является правильным, потому что изобразили его (ехр (п (2) * (1 + x)) = http://prnt.sc/dmdc3u), а когда x равно 0,1 или 2 (= когда случайное число равно 0,1 или 2). Может ли это быть проблемой с компилятором?

Примечание: Я уже решил эту проблему, используя обходное решение, которое вы можете видеть ниже, но сначала я использовал код, который вы можете увидеть в проблеме, и я хотел бы понять, что происходит.

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

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

Как насчет просто 'valueToOutput: = 2 shl Random (3)'? Затем вы можете пропустить все мусор с плавающей запятой. –

+0

Да, конечно, это очень хорошее решение (также быстрее, чем умножение), но я хотел знать об этой формуле. Я хотел знать, была ли потеря точности только в android. –

+0

Что такое 'sizeof (Extended)' на Android? На Win32 это 10, но на Win64 это 8. Может быть, аналогичная потеря точности на мобильном? Он не документирован в DocWiki от Embarcadero. –

ответ

6

вычислений с плавающей точкой являются повторяющимися, для того же входа, для того же состо ние с плавающей контрольной точкой.

С тремя различными входами ваше выражение должно иметь три различных возможных выхода. Поэтому единственным объяснением является то, что что-то изменяет состояние управления с плавающей запятой, например. режим округления, точность и т. д. во время выполнения программы.

Если арифметика с плавающей запятой может выполнить вычисление точно, вам не нужно округлять до целого числа. Но если вы должны округлить, по крайней мере, округлите до ближайшего, используя Round, а не Trunc.

Тем не менее, это категорически неправильный способ выполнить дискретную задачу выбора случайным образом один из 2, 4 и 8. Сделайте это, как так:

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

Другой способ поместить возможные выходы в массив, а затем выберете их следующим образом:

Result := arr[Random(3)]; 

Это становится более привлекательным, когда есть больше значений на выбор.

Золотое правило заключается в том, что если вы можете избежать использования плавающей запятой, сделайте это. Плавающая точка медленнее и сложнее рассуждать, чем целочисленная арифметика. Используйте его только при необходимости.

+0

любых указателей на то, как отлаживать и проверять это? Я предполагаю, что можно было бы сломать, когда состояние управления изменится, или состояние управления можно извлечь из дампа для проверки. –

+0

Я не знаком с компиляторами рук, поэтому я не знаю деталей. –

2

Проводка это как ответ, потому что он не соответствует комментарии

Я ничего не знаю о AnDrOID, но я хотел бы использовать следующий подход в Windows, чтобы сузить его. Вероятно, аналогичный подход существует для Androïd.

  1. Сохранять временные расчеты в глобальных переменных. Это облегчает извлечение значений из crashdump, которые мы собираемся предпринять. Поднимите исключение при попадании в неправильного значения.
  2. Приложите procdump к работающему процессу. Procdump из Sysinternals позволяет создавать файл дампа для каждого обнаруженного исключения. Командная строка будет что-то вроде procdump -ma -e 1 Project1.exe
  3. Запустите расчет и дождитесь его появления исключения.
  4. Анализ свалки. Значения переменных tmp можно извлечь из памяти.

код

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

0

Мы не видим все это. Например, вы говорите, что окна генерируют 2, 4 или 8, но мы видим 16 в сетке. Вы говорите, что Android генерирует 7, но мы видим 14 в сетке. Так ясно, что вы умножаетесь на 2 места. Мой анализ был бы, так как 2 * 4 - 8, что только версия Android генерирует 2, 4 и 7 (не 8), так как, как говорит Дэвид, у вас есть только 3 стартовых состояния, поэтому должно быть только три заканчивающиеся состояния. Использование Trunc в этой ситуации требует неприятностей. Я думаю, что все будет облегчено, используя Round вместо Trunc, вот так.

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

Это говорит, что, как говорит Дэвид, это неправильный способ сделать это.

+1

Не знаю, почему вы получаете downvotes. Ничто в вопросе не опровергает вашу гипотезу, и иногда разработчики пропускают очевидное. (Хотя вопрос объясняет, почему некоторые значения удваиваются.) –

+0

@CraigYoung. Спасибо. В то время, когда я писал, ответ не был принят, и я чувствовал, что добавляю потенциальное решение того, что должно было быть теоретически невозможно. Я также подумал (и все еще думаю), что я определял причину его проблемы, которую он не понимал в то время, хотя был лучший подход, – Dsm

 Смежные вопросы

  • Нет связанных вопросов^_^