Упаковка 32-битного плавающего в 30 бит (с ++) - PullRequest
4 голосов
/ 02 октября 2010

Вот цели, которых я пытаюсь достичь:

  • Мне нужно упаковать 32-битные значения IEEE в 30 бит.
  • Я хочу сделать это, уменьшив размер мантиссы на 2 бита.
  • Сама операция должна быть максимально быстрой.
  • Я знаю, что некоторая точность будет потеряна, и это приемлемо.
  • Было бы преимуществом, если бы эта операция не разрушила особые случаи, такие как SNaN, QNaN, бесконечности и т. Д. Но я готов пожертвовать этим ради скорости.

Я думаю, этот вопрос состоит из двух частей:

1) Могу ли я просто очистить наименее значимые биты мантиссы? Я пробовал это, и пока это работает, но, возможно, я прошу о неприятностях ... Что-то вроде:

float f;
int packed = (*(int*)&f) & ~3;
// later
f = *(float*)&packed;

2) Если есть случаи, когда 1) не удастся, то какой самый быстрый способ достичь этого?

Заранее спасибо

Ответы [ 5 ]

10 голосов
/ 02 октября 2010

Вы действительно нарушаете строгие правила псевдонимов (раздел 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 отображает последовательные значения с плавающей запятой в последовательные целые числа (игнорируя +/- ноль). Это отображение фактически очень хорошо аппроксимирует логарифм.

8 голосов
/ 02 октября 2010

Слепое отбрасывание 2 младших битов поплавка может дать сбой при небольшом количестве необычных кодировок NaN.

NaN кодируется как экспонента = 255, мантисса! = 0, но IEEE-754 ничего не говорито том, какие значения мантиасса следует использовать.Если значение мантиссы <= 3, вы можете превратить NaN в бесконечность! </p>

2 голосов
/ 02 октября 2010

Вы должны инкапсулировать его в структуру, чтобы случайно не смешивать использование тега с плавающей точкой с обычным "unsigned int":

#include <iostream>
using namespace std;

struct TypedFloat {
    private:
        union {
            unsigned int raw : 32;
            struct {
                unsigned int num  : 30;  
                unsigned int type : 2;  
            };
        };
    public:

        TypedFloat(unsigned int type=0) : num(0), type(type) {}

        operator float() const {
            unsigned int tmp = num << 2;
            return reinterpret_cast<float&>(tmp);
        }
        void operator=(float newnum) {
            num = reinterpret_cast<int&>(newnum) >> 2;
        }
        unsigned int getType() const {
            return type;
        }
        void setType(unsigned int type) {
            this->type = type;
        }
};

int main() { 
    const unsigned int TYPE_A = 1;
    TypedFloat a(TYPE_A);

    a = 3.4;
    cout << a + 5.4 << endl;
    float b = a;
    cout << a << endl;
    cout << b << endl;
    cout << a.getType() << endl;
    return 0;
}

Хотя я не могу гарантировать его мобильность.

1 голос
/ 03 ноября 2011

Сколько точности вам нужно?Если 16-разрядного числа с плавающей запятой достаточно (достаточно для некоторых типов графики), то 16-разрядное число с плавающей запятой ILM («половина»), часть OpenEXR, великолепна, подчиняется всем видам правил (http://www.openexr.com/),, и у вас будет многопространство, оставшееся после упаковки в структуру.

С другой стороны, если вы знаете приблизительный диапазон значений, которые они собираются принять, вы должны рассмотреть фиксированную точку. Они более полезны, чем большинстволюди понимают.

1 голос
/ 11 октября 2010

Я не могу выбрать ни один из ответов как определенный, потому что большинство из них имеют достоверную информацию, но не совсем то, что я искал. Поэтому я просто подведу итоги.

Метод преобразования, который я опубликовал в части 1 моего вопроса, явно неверен по стандарту C ++, поэтому следует использовать другие методы для извлечения битов с плавающей точкой.

И самое главное ... насколько я понимаю, прочитав ответы и другие источники о плавающих элементах IEEE754, можно отбросить наименее значимые биты из мантиссы. В основном это повлияет только на точность, за одним исключением: sNaN. Так как sNaN представлен показателем степени, установленным в 255, и мантисса! = 0, может быть ситуация, когда мантисса будет <= 3, и отбрасывание последних двух битов преобразует sNaN в +/- бесконечность. Но поскольку sNaN не генерируются во время операций с плавающей запятой на процессоре, он безопасен в контролируемой среде. </p>

...