Как изменить двойной порядок байтов?

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

Будет ли это работать корректно? или есть какой-то лучший подход, который я не смог найти?

double get_double(uint8_t * buff){
    double value;
    memcpy(&value,buff,sizeof(double));
    uint64_t tmp;
    tmp = le64toh(*(uint64_t*)&value);
    value = *(double*) &tmp;
    return value;
}

p.s. Я считаю с double 8 байтами, так что не беспокойтесь об этом, пожалуйста. Я знаю, что могут быть проблемы с этим

РЕДАКТИРОВАТЬ: После предложения использовать союз, я сделал это:

union double_int{
    double d;
    uint64_t i;
};


double get_double(uint8_t * buff){
    union double_int value;
    memcpy(&value,buff,sizeof(double));
    value.i = le64toh(value.i);
    return value.d;
}

лучше? (хотя особой разницы не вижу)

РЕДАКТИРОВАТЬ 2: попытка № 3, что вы думаете сейчас?

double get_double(uint8_t * buff){
    double value;
    uint64_t tmp;
    memcpy(&tmp,buff,sizeof(double));
    tmp = le64toh(tmp);
    memcpy(&value,&tmp,sizeof(double));
    return value;
}

Edit3: я компилирую его с помощью gcc -std=gnu99 -lpthread -Wall -pedantic

Edit4: после следующего предложения я добавил условие для проверки порядка байтов. Честно говоря, я понятия не имею, что я сейчас делаю (разве не должно быть что-то вроде __DOUBLE_WORD_ORDER__ ?)

double get_double(uint8_t * buff){
    double value;
    if (__FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__){
        uint64_t tmp;
        memcpy(&tmp,buff,sizeof(double));
        tmp = le64toh(tmp);
        memcpy(&value,&tmp,sizeof(double));
    }
    else {
        memcpy(&value,buff,sizeof(double));
    }
    return value;
}

person Charlestone    schedule 08.04.2016    source источник
comment
Вы должны использовать союз   -  person Antti Haapala    schedule 08.04.2016
comment
Я сделал это, как вы предложили... лучше? выглядит лучше, но я не вижу существенной разницы   -  person Charlestone    schedule 08.04.2016
comment
Существуют правила псевдонимов, и требования к выравниванию uint64_t могут отличаться от double (маловероятно, да), и так далее.   -  person Antti Haapala    schedule 08.04.2016
comment
хорошо, так что теперь, когда я сделал это с профсоюзом, это хорошая работа?   -  person Charlestone    schedule 08.04.2016
comment
Лучше использовать memcpy или что-то еще. При использовании у вас могут возникнуть проблемы с некоторыми версиями GCC и включенной оптимизацией -O3.   -  person Defter    schedule 08.04.2016
comment
memcpy, а не что? Сорри, я вас не понял.   -  person Charlestone    schedule 08.04.2016
comment
Скопируйте double в uint64_t, затем преобразуйте с помощью le64toh uint64_t и скопируйте обратно в double. memcpy, а не что? союз   -  person Defter    schedule 08.04.2016
comment
хорошо, так что теперь лучше?   -  person Charlestone    schedule 08.04.2016
comment
Ah: вам нужно принудительно указать как минимум -std=c99, так как в C89 это имеет неопределенное поведение.   -  person Antti Haapala    schedule 08.04.2016
comment
Почему профсоюз беспокоится? Можно привести любой указатель к указателю char (а затем делать с памятью то, что вы хотите, например, memcpy); это явно разрешено как исключение из обычных ограничений на псевдонимы, и C не будет работать без него, и ни одна программа, написанная на C, не будет работать. Cf. n1570, 6,5/7.   -  person Peter - Reinstate Monica    schedule 08.04.2016
comment
Вызов le64toh() корректирует порядок байтов целого числа в зависимости от порядка байтов платформы и сети. Порядок следования байтов double независим от целого числа. le64toh() может быть полезно для выполнения замены байтов, но не для определения, требуется ли это для double.   -  person chux - Reinstate Monica    schedule 08.04.2016
comment
Проблемы union полезны для того, чтобы избежать шага memcpy(&tmp,buff,sizeof(double));, но не шага с порядком байтов.   -  person chux - Reinstate Monica    schedule 08.04.2016
comment
Я только что отредактировал вопрос, теперь он правильный?   -  person Charlestone    schedule 08.04.2016
comment
Редактировать 3 было полезно, так как добавляла недостающую информацию. Правки 1,2,4 неоднократно меняли вопрос. Движущиеся цели не одобряются в SO.   -  person chux - Reinstate Monica    schedule 08.04.2016
comment
Я имел в виду код в 4-м редактировании...   -  person Charlestone    schedule 08.04.2016
comment
@Charlestone Если у вас есть ответ на свой (исходный) вопрос, опубликуйте его в разделе ответ. Таким образом, все могут независимо голосовать за ваши вопросы и ответы. Редактирование собственного сообщения с постоянно улучшающимся ответом - это не модель SO.   -  person chux - Reinstate Monica    schedule 08.04.2016


Ответы (3)


Я бы просто пошел и скопировал байты вручную во временный двойник, а затем вернул бы его. В C (и я думаю, что в C++) можно привести любой указатель к char *; это одно из явных разрешений для псевдонимов (в стандартном черновике n1570, пар. 6.5/7), как я упоминал в одном из своих комментариев. Исключение абсолютно необходимо, чтобы сделать что-то близкое к аппаратному; включая реверсирование байтов, полученных по сети :-).

Не существует стандартного способа определить время компиляции, нужно ли это, а жаль; если вы хотите избежать ветвей, что, вероятно, является хорошей идеей, если вы имеете дело с большим количеством данных, вам следует искать в документации вашего компилятора проприетарные определения, чтобы вы могли выбрать правильную ветвь кода во время компиляции. gcc, например, имеет __FLOAT_WORD_ORDER__ значение __ORDER_LITTLE_ENDIAN__ или __ORDER_BIG_ENDIAN__ .

(Из-за вашего вопроса в комментариях: __FLOAT_WORD_ORDER__ в целом означает плавающие точки. Было бы очень больно, если бы проектировал FPU с разным порядком байтов для разных размеров данных :-). На самом деле существует не так много основных архитектур, которые имеют разные порядки байтов для типов с плавающей запятой и целых чисел. Как говорит Википедия, небольшие системы могут отличаться.)

Бэзил указал на ntohd, функцию преобразования, которая существует в Windows, но, по-видимому, отсутствует в Linux.

Моя наивная примерная реализация была бы похожа на

/** copy the bytes at data into a double, reversing the
    byte order, and return that.
*/
double reverseValue(const char *data)
{
    double result;

    char *dest = (char *)&result;

    for(int i=0; i<sizeof(double); i++) 
    {
        dest[i] = data[sizeof(double)-i-1];
    }
    return result;
}

/** Adjust the byte order from network to host.
    On a big endian machine this is a NOP.
*/
double ntohd(double src)
{
#   if      !defined(__FLOAT_WORD_ORDER__) \
        ||  !defined(__ORDER_LITTLE_ENDIAN__)
#       error "oops: unknown byte order"
#   endif

#   if __FLOAT_WORD_ORDER__ == __ORDER_LITTLE_ENDIAN__
        return reverseValue((char *)&src);
#   else
        return src;
#   endif
}

Здесь есть рабочий пример: https://ideone.com/aV9mj4.

Улучшенная версия будет обслуживать данный ЦП - она ​​может иметь 8-байтовую команду подкачки.

person Peter - Reinstate Monica    schedule 08.04.2016
comment
Вместо #error я бы предпочел вернуться к тесту во время выполнения. - person edmz; 08.04.2016
comment
@черный Правда. Я был счастлив, что в этом примере вообще есть обработка ошибок :-) - person Peter - Reinstate Monica; 08.04.2016
comment
Примечание. Использование "%le" вместо "%lf" более наглядно для связанного примера. - person chux - Reinstate Monica; 08.04.2016

Если вы можете адаптировать программное обеспечение с обеих сторон (излучатель и приемник), вы можете использовать некоторую библиотеку сериализации и формат. Это может быть использование старых процедур XDR, таких как xdr_double в вашем случае (см. xdr(3)...). Вы также можете использовать ASN1 или использовать текстовые форматы, такие как JSON.

XDR имеет старший порядок байтов. Вы можете попытаться найти какую-нибудь реализацию NDR, это прямой порядок байтов.

См. также этот связанный вопрос и STFW для htond

person Basile Starynkevitch    schedule 08.04.2016
comment
к сожалению не могу. Emmiter — GPS-приемник. - person Charlestone; 08.04.2016

Итак, наконец, благодаря всем вам, я нашел лучшее решение. Теперь мой код выглядит так:

double reverseDouble(const char *data){
    double result;
    char *dest = (char *)&result;
    for(int i=0; i<sizeof(double); i++) 
        dest[i] = data[sizeof(double)-i-1];
    return result;
}

double get_double(uint8_t * buff){
    double value;
    memcpy(&value,buff,sizeof(double));

    if (__FLOAT_WORD_ORDER__ == __ORDER_BIG_ENDIAN__)
        return reverseDouble((char *)&value);
    else
        return value;
}

p.s. (проверка определений и т. д. находится где-то еще)

person Charlestone    schedule 08.04.2016