Подтвердить что ты не робот

Переносимость двоичной сериализации типа double/float в С++

Стандарт С++ не обсуждает базовый макет float и double типов, а только диапазон значений, которые они должны представлять. (Это также верно для подписанных типов, это два комплимента или что-то еще)

Мой вопрос: каковы методы, используемые для сериализации/десериализации типов POD, таких как double и float, переносимым образом? На данный момент кажется, что единственный способ сделать это - иметь значение, представленное буквально (как в "123.456" ). Макет ieee754 для double не является стандартным для всех архитектур.

4b9b3361

Ответ 1

Брайан "Beej Jorgensen" Hall дает в своем "Руководство по сетевому программированию" код для упаковки float (соответственно double) на uint32_t (соответственно uint64_t), чтобы иметь возможность безопасно передавать его по сети между двумя машинами, которые не могут согласиться с их представлением. Он имеет некоторое ограничение, в основном он не поддерживает NaN и бесконечность.

Вот его функция упаковки:

#define pack754_32(f) (pack754((f), 32, 8))
#define pack754_64(f) (pack754((f), 64, 11))

uint64_t pack754(long double f, unsigned bits, unsigned expbits)
{
    long double fnorm;
    int shift;
    long long sign, exp, significand;
    unsigned significandbits = bits - expbits - 1; // -1 for sign bit

    if (f == 0.0) return 0; // get this special case out of the way

    // check sign and begin normalization
    if (f < 0) { sign = 1; fnorm = -f; }
    else { sign = 0; fnorm = f; }

    // get the normalized form of f and track the exponent
    shift = 0;
    while(fnorm >= 2.0) { fnorm /= 2.0; shift++; }
    while(fnorm < 1.0) { fnorm *= 2.0; shift--; }
    fnorm = fnorm - 1.0;

    // calculate the binary form (non-float) of the significand data
    significand = fnorm * ((1LL<<significandbits) + 0.5f);

    // get the biased exponent
    exp = shift + ((1<<(expbits-1)) - 1); // shift + bias

    // return the final answer
    return (sign<<(bits-1)) | (exp<<(bits-expbits-1)) | significand;
}

Ответ 2

Что не так с человеческим читаемым форматом.

Он имеет несколько преимуществ перед двоичным:

  • Он читается
  • Он переносится
  • Это упрощает поддержку (так как вы можете попросить пользователя посмотреть на него в своем любимом редакторе даже на слово)
  • Легко исправить (или настроить файлы вручную в ситуациях с ошибками)

Неудобство:

  • Это не компактно Если это реальная проблема, вы всегда можете закрепить ее.
  • Это может быть немного медленнее для извлечения/генерации Обратите внимание, что двоичный формат, вероятно, также необходимо нормализовать (см. htonl())

Чтобы вывести двойной с полной точностью:

double v = 2.20;
std::cout << std::setprecision(std::numeric_limits<double>::digits) << v;

OK. Я не уверен, что это точно. Он может потерять точность.

Ответ 3

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

Ответ 4

Взгляните на реализацию старого файла gtypes.h в glib 2 - он включает в себя следующее:

#if G_BYTE_ORDER == G_LITTLE_ENDIAN
union _GFloatIEEE754
{
  gfloat v_float;
  struct {
    guint mantissa : 23;
    guint biased_exponent : 8;
    guint sign : 1;
  } mpn;
};
union _GDoubleIEEE754
{
  gdouble v_double;
  struct {
    guint mantissa_low : 32;
    guint mantissa_high : 20;
    guint biased_exponent : 11;
    guint sign : 1;
  } mpn;
};
#elif G_BYTE_ORDER == G_BIG_ENDIAN
union _GFloatIEEE754
{
  gfloat v_float;
  struct {
    guint sign : 1;
    guint biased_exponent : 8;
    guint mantissa : 23;
  } mpn;
};
union _GDoubleIEEE754
{
  gdouble v_double;
  struct {
    guint sign : 1;
    guint biased_exponent : 11;
    guint mantissa_high : 20;
    guint mantissa_low : 32;
  } mpn;
};
#else /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */
#error unknown ENDIAN type
#endif /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */

glib link

Ответ 5

Создайте соответствующий интерфейс serializer/de-serializer для записи/чтения.

Интерфейс может иметь несколько реализаций, и вы можете проверить свои параметры.

Как было сказано ранее, очевидными параметрами были бы:

  • IEEE754, который записывает/считывает двоичный фрагмент, если он напрямую поддерживается архитектурой или анализирует его, если не поддерживается архитектурой
  • Текст: всегда нужно разбираться.
  • Что бы вы еще не подумали.

Просто помните - если у вас есть этот слой, вы всегда можете начинать с IEEE754, если вы поддерживаете только платформы, которые используют этот формат внутри. Таким образом, вы будете иметь дополнительные усилия только тогда, когда вам нужно поддерживать другую платформу! Не делайте работу, которую вам не нужно.

Ответ 6

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

Это может использовать строковое представление или, если вам нужно что-то, что занимает меньше места, укажите ваш номер в ieee754 (или любой другой формат, который вы выберете), а затем проанализируйте его так же, как и со строкой.

Ответ 7

Я думаю, что ответ "зависит" от того, что ваше конкретное приложение и его профиль производительности.

Предположим, что у вас есть среда с низкой задержкой в ​​рыночных данных, тогда использование строк является откровенно глупым. Если информация, которую вы передаете, - это цены, то двойные (и двоичное их представление) действительно сложны в работе. Где, как, если вы действительно не заботитесь о производительности, и что вам нужно - это видимость (хранение, передача), тогда строки являются идеальным кандидатом.

Я бы выбрал интегральное представление мантиссы/экспоненциального представления поплавков/удвоений - то есть при первой возможности конвертировал float/double в пару целых чисел, а затем передал это. Вам тогда нужно только беспокоиться о переносимости целых чисел и ну, различных подпрограмм (таких как подпрограммы hton() для обработки конверсий для вас). Также сохраните все в своей наиболее распространенной платформе endianess (например, если вы используете только linux, то какой смысл хранить вещи в большом endian?)

Ответ 8

SQLite4 использует новый формат для хранения парных и плавающих

  • Он работает надежно и последовательно даже на платформах, в которых отсутствует поддержка чисел с плавающей запятой IEEE 754.
  • Курсы валют обычно выполняются точно и без округления.
  • Любое подписанное или неподписанное 64-разрядное целое может быть представлено точно.
  • Диапазон с плавающей запятой и точность превышают диапазон чисел с плавающей запятой IEEE 754.
  • Положительная и отрицательная бесконечность и NaN (Not-a-Number) имеют четко определенные представления.

Источники:

https://sqlite.org/src4/doc/trunk/www/design.wiki

https://sqlite.org/src4/doc/trunk/www/decimal.wiki

Ответ 9

Нашел эту старую нить. Отсутствует одно решение, разрешающее справедливое количество дел - использование фиксированной точки, передача целых чисел с известным коэффициентом масштабирования с использованием встроенных бросков в обоих концах. Таким образом, вам не нужно беспокоиться о базовом представлении с плавающей запятой.

Конечно, есть недостатки. Это решение предполагает, что вы можете иметь фиксированный коэффициент масштабирования и по-прежнему получать как диапазон, так и разрешение, необходимые для конкретного приложения. Кроме того, вы конвертируете с плавающей точкой в неподвижную точку на конец сериализации и конвертируете обратно при десериализации, представляя две ошибки округления. Однако на протяжении многих лет я обнаружил, что фиксированная точка достаточна для моих нужд почти во всех случаях, и она также достаточно быстро.

Типичным примером для фиксированной точки являются протоколы связи для встроенных систем или других устройств.