Преобразование значений с плавающей запятой из прямого порядка байтов в прямой

Можно ли преобразовать floats из большого в маленький? У меня есть значение с прямым порядком байтов от платформы PowerPC, которое я отправляю через TCP в процесс Windows (с прямым порядком байтов). Это значение равно float, но когда я memcpy преобразую значение в число с плавающей запятой Win32, а затем вызываю _byteswap_ulong для этого значения, я всегда получаю 0,0000?

Что я делаю неправильно?


person Blade3    schedule 06.05.2010    source источник
comment
взгляните на этот вопрос: stackoverflow.com/questions/1786137/   -  person Drakosha    schedule 06.05.2010
comment
Я думаю, что они оба имеют формат IEEE, но вы должны перепроверить.   -  person Mark Ransom    schedule 06.05.2010
comment
Что произойдет, если вы не позвоните _byteswap_ulong ?   -  person Maciej Hehl    schedule 02.11.2010
comment
‹забастовка›Не применяйте такие вещи. Оформить заказ boost::endian по адресу: boost. org/doc/libs/1_64_0/libs/endian/doc/index.html‹/strike  -  person morteza    schedule 18.05.2017
comment
Ответ @ morteza, который он пытался, но не смог вычеркнуть, не работает - поддержка float была удалена из boost::endian, потому что это не сработало.   -  person AnotherParker    schedule 25.08.2018


Ответы (9)


просто поменять местами четыре байта работает

float ReverseFloat( const float inFloat )
{
   float retVal;
   char *floatToConvert = ( char* ) & inFloat;
   char *returnFloat = ( char* ) & retVal;

   // swap the bytes into a temporary buffer
   returnFloat[0] = floatToConvert[3];
   returnFloat[1] = floatToConvert[2];
   returnFloat[2] = floatToConvert[1];
   returnFloat[3] = floatToConvert[0];

   return retVal;
}
person Gregor Brandt    schedule 06.05.2010
comment
Это сломается на gcc с оптимизацией из-за псевдонима указателя. Не делайте этого, используйте union для такого приведения. Я был там, я ударил его. - person Tomek; 07.05.2010
comment
Это совершенно законный код C, и ни один компилятор не должен его нарушать. Я тестировал его с VC6, Visual Studio 2008, Visual Studio 2010 и c++ Builder 2010. Ни один из этих компиляторов не нарушает этот код. - person Gregor Brandt; 07.05.2010
comment
@gbrand код является законным, но это не значит, что он работает. посмотрите здесь: xania.org/200712/cpp-strict-aliasing. Вы также можете увидеть, как приводить союзы. - person Tomek; 07.05.2010
comment
@Tomek: это не нарушает строгое правило псевдонимов. И C, и C++ явно разрешают доступ к объекту любого типа как к массиву символов (и, следовательно, через char*). Приведение через взлом союза по ссылке, которую вы разместили, приводит к поведению undefined (чтение из члена союза, отличного от последнего записанного, приводит к поведению undefined). - person James McNellis; 31.05.2010
comment
@Tomek: Разве использование союза не нарушит какое-то другое правило? IIRC только один член профсоюза может быть действительным в любой момент времени. - person dalle; 31.05.2010
comment
Я понимаю, что доступ к объединению через элемент, который не был последним для записи, - это плохо (AFAIR, это ужасный UB), но, похоже, это работает. Вот почему я вызываю шаблон для приведения таким образом evil_cast ;). @James: доступ в виде массива char и через указатель на char НЕ одно и то же. Для псевдонимов я думаю, что пройти через char * безопасно (я помню, что где-то видел это), но для меня это так же плохо, как пройти через union. И, честно говоря, если вы хотите возиться с битами на таком низком уровне, вы рано или поздно наткнетесь на UB или IDB. Это означает, что вы находитесь во власти компилятора. - person Tomek; 31.05.2010
comment
@Tomek: неважно, плохо ли это для тебя; важно, хорошо ли определено его поведение. Реинтерпретация через char* четко определена (см. C++03 §3.10/15). Union hack - нет (см. C++03 §9.5/1). Если вы знакомы с языком, то избежать неопределенного или определяемого реализацией поведения не составит особого труда. - person James McNellis; 01.06.2010
comment
У меня есть еще одна небольшая проблема с этим кодом. Несмотря на название функции, она ведет себя совсем не так, как, скажем, ntohl(). Если порядок байтов уже правильный, он ничего не должен делать. Здесь он все равно меняет местами байты. - person kriss; 05.12.2010
comment
@Tomek: То есть вы предпочитаете решение, которое вызывает UB, тому, которое совершенно четко определено? Это... вообще не имеет смысла. Вы говорите, что код является законным, но это не значит, что он работает., а затем, говоря об использовании союза... (AFAIR это страшный UB), но, похоже, он работает.. Я не думаю, что я никогда не слышал более непоследовательного аргумента. И как вы можете сказать, что если вы хотите возиться с битами на таком низком уровне, вы рано или поздно попадете в UB или IDB? О чем ты говоришь? Это совершенно неправда, вам просто нужно знать, что вы делаете. - person Ed S.; 15.09.2012
comment
Идиома объединения настолько широко распространена, что любой компилятор, нарушающий ее, не сможет скомпилировать большую часть программного обеспечения. - person Joseph Garvin; 05.12.2018

Вот функция, которая может изменить порядок байтов любого типа.

template <typename T>
T bswap(T val) {
    T retVal;
    char *pVal = (char*) &val;
    char *pRetVal = (char*)&retVal;
    int size = sizeof(T);
    for(int i=0; i<size; i++) {
        pRetVal[size-1-i] = pVal[i];
    }

    return retVal;
}
person Benjamin    schedule 15.07.2014

Я нашел что-то примерно такое давным-давно. Это было хорошо для смеха, но глотать на свой страх и риск. Я его даже не компилировал:

void * endian_swap(void * arg)
{
    unsigned int n = *((int*)arg);
    n = ((n >>  8) & 0x00ff00ff) | ((n <<  8) & 0xff00ff00);
    n = ((n >> 16) & 0x0000ffff) | ((n << 16) & 0xffff0000);
    *arg = n;   

    return arg;
}
person Sniggerfardimungus    schedule 06.05.2010
comment
Хе-хе. Знал, что за это проголосуют. Выложил только ради смеха. Когда я брал интервью у EA много-много лет назад, у них была версия, которая делала сдвиги на 1, 2, 4, 8, а затем на 16-битную ширину, и меня спросили, что она делает. - person Sniggerfardimungus; 07.05.2010
comment
Во-первых, я думаю, было довольно ясно, что это было не более чем небольшое развлечение. Во-вторых, я не уверен, что применяются правила псевдонимов. Хотя существует вероятность того, что arg может указывать на память, которой будет владеть n, оптимизатор не сочтет это релевантным, так как он никогда больше не использует n после присвоения *arg. Правила присвоения псевдонимов не вступят в силу, пока вы не сделаете что-то вроде: a=5;*b=7;c=a+(*b);, где значение c не может быть вычислено из кэшированного значения a, поскольку присваивание *b могло повлиять на это. Хотя я должен был сказать *((int)arg) = n; знак равно - person Sniggerfardimungus; 08.05.2010
comment
Что ж, если бы производительность была важна, эта версия была бы предпочтительнее той, которая выполняет перетасовку байтов. Перетасовка байтов покажет частичную остановку, как только вы прочитаете значение обратно как двойное слово. В этом случае вы всегда остаетесь в размере dword, и это может быть хорошо конвейерно, без задержек. Я не удивлен, что EA показала это, поскольку разработчики игр используют подобные функции для критичного ко времени кода. - person Suma; 14.05.2010
comment
Тем не менее, я, вероятно, предпочел бы более удобочитаемую версию с четырьмя сменами, объединенными вместе. Однако есть некоторые платформы, где производительность смены хуже при большем количестве смен (PPC), поэтому та, которая у них есть, скорее всего, будет работать лучше. - person Suma; 14.05.2010
comment
*arg = n;? Будет ли присваивание разыменованному указателю void работать без приведения? Я ожидаю, что компилятор будет жаловаться. - person blubberdiblub; 25.08.2019
comment
Если ваш компилятор жалуется на это (@blubberdiblub), вы можете отключить предупреждение/ошибку. Меня не волнует тип того, на что он указывает - на самом деле, я явно работаю над этим - только на что он на самом деле указывает. - person Sniggerfardimungus; 06.09.2019

Элегантным способом обмена байтами является использование объединения:

float big2little (float f)
{
    union
    {
        float f;
        char b[4];
    } src, dst;

    src.f = f;
    dst.b[3] = src.b[0];
    dst.b[2] = src.b[1];
    dst.b[1] = src.b[2];
    dst.b[0] = src.b[3];
    return dst.f;
}

Следуя рекомендации jjmerelo написать цикл, более общим решением может быть:

typedef float number_t;
#define NUMBER_SIZE sizeof(number_t)

number_t big2little (number_t n)
{
    union
    {
        number_t n;
        char b[NUMBER_SIZE];
    } src, dst;

    src.n = n;
    for (size_t i=0; i<NUMBER_SIZE; i++)
        dst.b[i] = src.b[NUMBER_SIZE-1 - i];

    return dst.n;
}
person Antonio Cañas Vargas    schedule 29.10.2019
comment
Не следует ли зациклить присваивание dst.b ‹-› src.b? - person jjmerelo; 29.10.2019
comment
Действительно, это возможное решение - написать цикл. Сначала я так думал, чтобы сделать код более расширяемым на другие размеры (например, для преобразования двойников). А компилятор при оптимизации сериализует цикл, генерируя тот же код, что и без цикла. Причина, по которой четыре присваивания были помещены без цикла, заключалась в простоте, потому что, возможно, это лучше понимается. Решение с циклом будет таким: for (unsigned i=0; i‹sizeof(ffoat); i++) dst.b[i] = src.b[sizeof(ffoat)-1 - i]; - person Antonio Cañas Vargas; 29.10.2019
comment
Это неопределенное поведение в C++. Недопустимо чтение переменной, которая не была назначена в объединении. Это может хорошо работать в большинстве компиляторов, но тогда то же самое происходит и при переходе дороги не глядя. - person UKMonkey; 13.02.2020
comment
Большое спасибо за ваш комментарий. После 30 лет программирования на C я узнал этот аспект объединения. Читая ваш комментарий, я думал, что это будет функция, представленная в C++, но я искал первое издание Kernighan-Ritchie, и оно уже было там: безопасно читать член союза, если он последний, в котором вы написали , но в противном случае поведение остается неопределенным. - person Antonio Cañas Vargas; 26.02.2020

Не копируйте данные напрямую в тип с плавающей запятой. Сохраните его как данные char, поменяйте местами байты и затем обработайте его как число с плавающей запятой.

person jscharf    schedule 06.05.2010

Возможно, было бы проще использовать ntoa и связанные с ним функции для преобразования из сети в хост и из хоста в сеть. Преимущество в том, что это будет переносимым. Вот ссылка на статью, объясняющую как это сделать.

person t0mm13b    schedule 06.05.2010

Из SDL_endian.h с небольшими изменениями:

std::uint32_t Swap32(std::uint32_t x)
{
    return static_cast<std::uint32_t>((x << 24) | ((x << 8) & 0x00FF0000) |
                                      ((x >> 8) & 0x0000FF00) | (x >> 24));
}

float SwapFloat(float x)
{
    union
    {
        float f;
        std::uint32_t ui32;
    } swapper;
    swapper.f = x;
    swapper.ui32 = Swap32(swapper.ui32);
    return swapper.f;
}
person infval    schedule 11.11.2018

Это значение является числом с плавающей запятой, но когда я "memcpy" значение преобразую в тип числа с плавающей запятой win32, а затем вызываю _byteswap_ulong для этого значения, я всегда получаю 0,0000?

Это должно работать. Можете ли вы опубликовать код, который у вас есть?

Однако, если вы заботитесь о производительности (возможно, нет, в этом случае вы можете игнорировать остальное), должна быть возможность избежать memcpy, либо напрямую загрузив его в целевое местоположение и подкачивая там байты, либо используя своп который выполняет подкачку при копировании.

person Suma    schedule 14.05.2010

в некоторых случаях, особенно в Modbus: сетевой порядок байтов для числа с плавающей запятой:

nfloat[0] = float[1]
nfloat[1] = float[0]
nfloat[2] = float[3]
nfloat[3] = float[2]
person NPULSENET    schedule 20.06.2020