Есть какие-нибудь идеи, как этого добиться без использования чисел с плавающей запятой двойной точности?
Не вдаваясь в подробности о float
:
Shift u
до тех пор, пока не будет установлен старший значащий бит, значение преобразования float
уменьшается вдвое.
«Сохраняя равномерное распределение»
50% значений uint32_t
будут находиться в [0,5... 1,0)
25% от значений uint32_t
будет в [0,25 ... 0,5)
12,5% от значений uint32_t
будет в [0,125 ... 0,25)
6,25% от значений uint32_t
будут находиться в [0,0625 ... 0,125)
...
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
float ui32to0to1(uint32_t u) {
if (u) {
float band = 1.0f/(1llu<<32);
while ((u & 0x80000000) == 0) {
u <<= 1;
band *= 0.5f;
}
return (float)u * band;
}
return 0.0f;
}
Некотором тестовом коде, чтобы показать функциональную эквивалентность double
.
int test(uint32_t u) {
volatile float f0 = (float) ((double)u / (1llu<<32));
volatile float f1 = ui32to0to1(u);
if (f0 != f1) {
printf("%8lX %.7e %.7e\n", (unsigned long) u, f0, f1);
return 1;
}
return 0;
}
int main(void) {
for (int i=0; i<100000000; i++) {
test(rand()*65535u ^ rand());
}
return 0;
}
Возможны различные оптимизации, особенно с учетом свойств float
.Тем не менее, для первоначального ответа я придерживаюсь общего подхода.
Для повышения эффективности цикл должен только повторяться с 32 до FLT_MANT_DIG
, что обычно составляет 24 *. 1033 *
float ui32to0to1(uint32_t u) {
float band = 1.0f/(1llu<<32);
for (int i = 32; (i>FLT_MANT_DIG && ((u & 0x80000000) == 0)); i--) {
u <<= 1;
band *= 0.5f;
}
return (float)u * band;
}
Этот ответ сопоставляет [от 0 до 2 32 -1] с [0,0 до 1,0)
Для сопоставления с [от 0 до 2 32 -1] до (-1,0-1,0).Может составлять -0,0.
if (u >= 0x80000000) {
return ui32to0to1((u - 0x80000000)*2);
} else
return -ui32to0to1((0x7FFFFFFF - u)*2);
}