NextFloat() has an inclusive maximum value
The current implementation was copied from a solution for double precision numbers according to the code comment
But this results in different behaviour for floating point values, which will include the maximum of 1.0f: Here a simple test that calculates the maximum value in bit representation to make sure it is not caused by rounding when printing:
public static void Main(string[] args)
{
float a = 4.6566128752458E-10f;
float b = (Int32.MaxValue - 1) * 4.6566128752458E-10f;
float c = Int32.MaxValue * 4.6566128752458E-10f;
float d = 1.0f;
string bitsA = Convert.ToString(BitConverter.ToInt32(BitConverter.GetBytes(a), 0), 2).PadLeft(32, '0');
string bitsB = Convert.ToString(BitConverter.ToInt32(BitConverter.GetBytes(b), 0), 2).PadLeft(32, '0');
string bitsC = Convert.ToString(BitConverter.ToInt32(BitConverter.GetBytes(c), 0), 2).PadLeft(32, '0');
string bitsD = Convert.ToString(BitConverter.ToInt32(BitConverter.GetBytes(d), 0), 2).PadLeft(32, '0');
Console.WriteLine ($"{a}");
Console.WriteLine ($"{bitsA}");
Console.WriteLine ($"{b}");
Console.WriteLine ($"{bitsB}");
Console.WriteLine ($"{c}");
Console.WriteLine ($"{bitsC}");
Console.WriteLine ($"{d}");
Console.WriteLine ($"{bitsD}");
}
}
which gives the output
4.656613E-10
00110000000000000000000000000000
1
00111111100000000000000000000000
1
00111111100000000000000000000000
1
00111111100000000000000000000000
The same problem does not occurr for double precision:
public static void Main(string[] args)
{
double a = 4.6566128752458E-10f;
double b = (Int32.MaxValue - 1) * 4.6566128752458E-10;
double c = Int32.MaxValue * 4.6566128752458E-10;
double d = 1.0;
string bitsA = Convert.ToString(BitConverter.ToInt64(BitConverter.GetBytes(a), 0), 2).PadLeft(64, '0');
string bitsB = Convert.ToString(BitConverter.ToInt64(BitConverter.GetBytes(b), 0), 2).PadLeft(64, '0');
string bitsC = Convert.ToString(BitConverter.ToInt64(BitConverter.GetBytes(c), 0), 2).PadLeft(64, '0');
string bitsD = Convert.ToString(BitConverter.ToInt64(BitConverter.GetBytes(d), 0), 2).PadLeft(64, '0');
Console.WriteLine ($"{a}");
Console.WriteLine ($"{bitsA}");
Console.WriteLine ($"{b}");
Console.WriteLine ($"{bitsB}");
Console.WriteLine ($"{c}");
Console.WriteLine ($"{bitsC}");
Console.WriteLine ($"{d}");
Console.WriteLine ($"{bitsD}");
}
4.6566128752458E-10
0011111000000000000000000000000000000000001000000000000000000011
0.999999999534339
0011111111101111111111111111111111111111110000000000000000000110
1
0011111111110000000000000000000000000000000000000000000000000011
1
0011111111110000000000000000000000000000000000000000000000000000
Usually random number generators exclude the maximum value, so this may cause numerical errors in rare cases where the user was not aware of this. The documentation says that the maximum should be excluded:
@Fildrance
It looks to me that its better to move onto random.NextSingle - microbenchmark provided minimal perf difference... yet it exists so i am a bit hesitant.