Вы действительно нарушаете строгие правила псевдонимов (раздел 3.10 стандарта C ++) с этими переинтерпретациями. Это, вероятно, взорвется, когда вы включите оптимизацию компилятора.
Стандарт C ++, раздел 3.10, параграф 15 гласит:
Если программа пытается получить доступ к сохраненному значению объекта через значение lvalue, отличное от одного из следующих типов, поведение не определено
- динамический тип объекта,
- cv-квалифицированная версия динамического типа объекта,
- тип, аналогичный динамическому типу объекта,
- тип, который является типом со знаком или без знака, соответствующим динамическому типу объекта,
- тип, который является типом со знаком или без знака, соответствующим cv-квалифицированной версии динамического типа объекта,
- агрегатный или объединенный тип, который включает один из вышеупомянутых типов среди своих членов (включая, рекурсивно, член субагрегата или автономного объединения),
- тип, который является (возможно, cv-квалифицированным) типом базового класса динамического типа объекта,
- тип char или unsigned char.
В частности, 3.10 / 15 не позволяет нам получить доступ к объекту с плавающей точкой через lvalue типа unsigned int. Я действительно укусила себя этим. Написанная мною программа перестала работать после включения оптимизаций. По-видимому, GCC не ожидал, что lvalue типа float будет псевдонимом lvalue типа int, что является верным предположением 3.10 / 15. Инструкции были перемешаны оптимизатором в соответствии с правилом «как будто», использующим 3.10 / 15, и он перестал работать.
При следующих допущениях
- float действительно соответствует 32-битному IEEE-float,
- SizeOf (флоат) == SizeOf (INT)
- unsigned int не имеет битов заполнения или представлений прерываний
Вы должны быть в состоянии сделать это так:
/// returns a 30 bit number
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return r >> 2;
}
float unpack_float(unsigned int x) {
x <<= 2;
float r;
std::memcpy(&r,&x,sizeof r);
return r;
}
Это не страдает от "нарушения 3.10" и, как правило, очень быстро. По крайней мере, GCC рассматривает memcpy как встроенную функцию. Если вам не нужны функции для работы с NaN, бесконечностями или числами с чрезвычайно высокой величиной, вы даже можете повысить точность, заменив «r >> 2» на «(r + 1) >> 2»:
unsigned int pack_float(float x) {
unsigned r;
std::memcpy(&r,&x,sizeof r);
return (r+1) >> 2;
}
Это работает, даже если оно изменяет показатель степени из-за переполнения мантиссы, поскольку кодирование IEEE-754 отображает последовательные значения с плавающей запятой в последовательные целые числа (игнорируя +/- ноль). Это отображение фактически очень хорошо аппроксимирует логарифм.