Math.random () и точность потери любопытства - PullRequest
4 голосов
/ 06 декабря 2011

Следующее не компилируется:

int result = Math.random() + 1;

error: possible loss of precision
    int result = Math.random() + 1;
                               ^
    required: int
    found:    double

но следующее компилирует :

int result = 0;
result += Math.random() + 1;

Почему?

Помещая компилируемый код во вложенный цикл, можно ожидать, что результат будет увеличиваться на 1 с каждой итерацией, потому что Math.random () всегда возвращает значение типа double, значение которого меньше 1, а при добавлении к целому числу дробная часть будет потерял из-за потери точности. Запустите следующий код и увидите неожиданный результат:

public class MathRandomCuriosity
{
  public static void main(String[] args)
  {
    int result = 0;
    for (int i = 0; i < 10; i++)
    {
      // System.out.println(result);
      for (int j = 0; j < 20; j++)
      {
        // System.out.println(result);
        for (int k = 0; k < 300; k++)
        {
          // System.out.println(result);
          for (int m = 0; m < 7000; m++)
          {
            result += Math.random() + 1;
          }
        }
      }
    }
    System.out.println(result);
  }
}

При 10 * 20 * 300 * 7000 = 42 000 000 итераций результат должен составить 42 000 000. Но это не так! Результат варьируется, т. Е. 42 000 007 против 42 000 006 против 42 000 010 и т. Д.

Почему?

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

Ответы [ 3 ]

12 голосов
/ 06 декабря 2011

Назначенные операторы, такие как +=, выполняют неявное приведение.

Примечание: в этом случае Math.random() будет округляться до 0 каждый раз, что является значительной потерей точности. ;)

Однако Math.random() + 1 имеет очень небольшой шанс округления до 2. Например, 1.999999 будет округлено до 1, но 1.9999999999999999 будет округлено до 2 (но оператор double + вместо преобразования int).

long l = Double.doubleToLongBits(1.0);
double d0_999etc = Double.longBitsToDouble(l -1);
System.out.println("The value before 1 is " +d0_999etc+" cast to (int) is "+ (int) d0_999etc);
System.out.println("The value before 1, plus 1 is " +(1+d0_999etc)+" cast to (int) is "+(int)(1 +d0_999etc));

печать

The value before 1 is 0.9999999999999999 cast to (int) is 0
The value before 1, plus 1 is 2.0 cast to (int) is 2
1 голос
/ 06 декабря 2011

Детали математической реализации IEEE указывают на потерю точности и ненадежных результатов от двойного / с плавающей точкой к целочисленному преобразованию. Например, однажды я нашел код, который сравнивал числа с плавающей запятой:

int x = 0;
if (a <= b) 
{ 
    x = y; 
}
if (a > b) 
{ 
    x = z; 
}

Иногда результат был x == 0 например, число, которое было поймано ни одним из операторов if, мне пришлось переписать код как:

int x = 0; 
if (a <= b) 
{ 
    x = y; 
} 
else 
{ 
     x = z; 
}
0 голосов
/ 06 декабря 2011

По определению Math.random() возвращает double результат от 0,0 до 1,0.Операция Math.random() + 1 создает двойной результат, который затем присваивается переменной int, что дает целочисленный результат.На каждой итерации результат равен 1, если Math.random() не возвращает ровно 1,0.Вероятность того, что это произойдет, очень мала, но все еще существует.По статистике это что-то вроде 1/6000.Это причина того, что некоторые итерации цикла добавляют 2 к вашему результату.

Итак, здесь нет потери точности.Все происходит по спец.

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