Почему этот повторяемый случайный алгоритм не работает?
Мне нужно было получить случайный генератор, который взял бы две координаты в качестве семени и вывел бы случайное число, всегда одинаковое для тех же координат. Итак, вдохновленный реализацией Jon Skeet GetHashCode, вот что я сделал:
public class SimpleRandom2D
{
public SimpleRandom2D(int _Seed)
{
Seed = _Seed;
}
public SimpleRandom2D()
{
Seed = new Random().Next();
}
readonly public int Seed;
public float Sample(float _X, float _Y)
{
int thisSeed;
unchecked
{
thisSeed = (int) 2166136261;
thisSeed = thisSeed * 16777619 ^ _X.GetHashCode();
thisSeed = thisSeed * 16777619 ^ _Y.GetHashCode();
thisSeed = thisSeed * 16777619 ^ Seed;
}
return 2f * (float) new Random(thisSeed).NextDouble() - 1f;
}
}
Я знаю, что создание нового Random очень далек от оптимального, но я хотел, чтобы это было правильно, прежде чем я быстро его освою. Однако оказалось, что это было не совсем правильно: иногда они возвращали те же значения для координат, которые имеют одинаковые x и у которых ± 1. Этот тест последовательно терпит неудачу:
[Test]
[Repeat (20)]
public void Sample_IntegerFloats_WithSameXNeighbourY_NotSame()
{
var random = new Random();
var simpleRandom2d = new SimpleRandom2D();
float y = random.Next(); // .Next() returns an integer, which is implicitly converted to a float — hence IntegerFloats in the test title
float x = random.Next();
float value1 = simpleRandom2d.Sample(x, y);
float value2 = simpleRandom2d.Sample(x, y + 1);
Assert.That(value1, Is.Not.EqualTo(value2));
}
Напротив, этот тест не прерывается:
[Test]
[Repeat (20)]
public void Sample_IntegerFloats_WithSameX_NotSame()
{
var random = new Random();
var simpleRandom2d = new SimpleRandom2D();
float x = random.Next();
float value1 = simpleRandom2d.Sample(x, random.Next());
float value2 = simpleRandom2d.Sample(x, random.Next());
Assert.That(value1, Is.Not.EqualTo(value2));
}
Почему это происходит и как я могу его исправить?
Ответы
Ответ 1
Оказывается, моя ошибка не имела никакого отношения к случайности. Я сгенерировал случайный целочисленный float с методом .Next()
метода Random. Тем не менее, я забыл, что в то время как .Next()
приведет к любому целому числу от 0 до 2 147 483 647, тип float имел ограниченную точность, поэтому, когда я сделал + 1
для получения "соседа", мой поплавок проводился большую часть времени, поэтому большой, что на самом деле это не имело значения. Другими словами, когда вы это делаете:
float x = new Random().Next();
y = x + 1;
x обычно все равно равен y.