Переносимость двоичной сериализации типа double / float в C ++ - PullRequest
44 голосов
/ 19 января 2011

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

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

Ответы [ 9 ]

27 голосов
/ 19 января 2011

Брайан "Бидж Йоргенсен" Холл дает в своем Руководстве по сетевому программированию код для упаковки 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;
}
5 голосов
/ 19 января 2011

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

У него есть несколько преимуществ по сравнению с двоичным:

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

Недостаток:

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

Для вывода двойного значения с полной точностью:

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

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

4 голосов
/ 07 ноября 2011

Взгляните на (старую) реализацию файла 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

4 голосов
/ 19 января 2011

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

2 голосов
/ 19 января 2011

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

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

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

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

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

1 голос
/ 19 января 2011

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

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

0 голосов
/ 05 декабря 2018

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

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

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

0 голосов
/ 13 ноября 2015

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

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

Источники:

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

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

0 голосов
/ 19 января 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...