Сообщаемое поведение согласуется с объявлением percent
float
и использованием базовых 32-разрядных и 64-разрядных двоичных чисел с плавающей точкой IEEE-754 для float
и double
.
uint16_t reInt1 = (uint16_t)(100.0 * percent);
Поскольку 100.0
является константой double
, это преобразует percent
в double
, выполняет умножение в double
и преобразует результат в uint16_t
. Умножение может иметь очень небольшую ошибку округления, вплоть до ½ ULP двойного формата, относительную ошибку около 2 -53 .
double stagedDouble = 100 * percent;
uint16_t reInt2 = (uint16_t)stagedDouble;
Поскольку 100
является константой int
, это преобразует 100
в float
, выполняет умножение в float
и преобразует результат в uint16_t
. Ошибка округления при умножении может составлять до 1/2 ULP формата float
, относительная ошибка около 2 -24 .
Поскольку все значения составляют около сотых целого числа, соотношение ошибок 50:50 вверх: вниз будет составлять примерно половину результатов, чуть меньше того, что необходимо для целочисленного порога. В умножениях все те, чьи значения равны 0, 25, 50 или 100 сотых, будут точными (потому что 25/100 равно ¼, что точно представлено в двоичной переменной с плавающей запятой), поэтому 96/100 будет иметь округление ошибки. Если направления ошибок округления float
и double
ведут себя как независимые, однородные случайные величины, примерно половина будет округляться в разных направлениях, приводя к разным результатам, давая примерно 48% несовпадений, что согласуется с 47%, сообщенными в вопрос.
(Однако, когда я измеряю фактические результаты, я получаю 42% -ную разницу между методами float
и double
. Я подозреваю, что это имеет какое-то отношение к конечным битам в умножении float
перед округлением - распределение может не действовать как равномерное распределение двух возможностей. Это может быть то, что код OP готовит значения percent
не так, как деление целочисленного значения на 100).