Ваш наивный скалярный алгоритм не обеспечивает правильно округленное преобразование - он будет страдать от двойного округления на некоторых входах. Например: если x
равно 0x88000081
, то правильно округленный результат преобразования в число с плавающей запятой равен 2281701632.0f
, но вместо этого ваш скалярный алгоритм вернет 2281701376.0f
.
Вне моей головы, вы можете сделать правильное преобразование следующим образом (как я уже сказал, это не в моей голове, поэтому, вероятно, можно где-то сохранить инструкцию):
movdqa xmm1, xmm0 // make a copy of x
psrld xmm0, 16 // high 16 bits of x
pand xmm1, [mask] // low 16 bits of x
orps xmm0, [onep39] // float(2^39 + high 16 bits of x)
cvtdq2ps xmm1, xmm1 // float(low 16 bits of x)
subps xmm0, [onep39] // float(high 16 bits of x)
addps xmm0, xmm1 // float(x)
где константы имеют следующие значения:
mask: 0000ffff 0000ffff 0000ffff 0000ffff
onep39: 53000000 53000000 53000000 53000000
Для этого нужно отдельно преобразовать верхнюю и нижнюю половины каждой полосы в число с плавающей запятой, а затем сложить эти преобразованные значения вместе. Поскольку каждая половина имеет ширину всего 16 бит, преобразование в число с плавающей запятой не требует округления. Округление происходит только при добавлении двух половинок; поскольку сложение является правильно округленной операцией, все преобразования правильно округляются.
Напротив, ваша наивная реализация сначала конвертирует младшие 31 бит в плавающее число, что вызывает округление, затем условно добавляет 2 ^ 31 к этому результату, что может вызвать второе округление. Каждый раз, когда у вас есть две отдельные точки округления в конверсии, если вы не слишком осторожны в отношении того, как они происходят, вы не должны ожидать, что результат будет правильно округлен.