2011-01-19 3 views
44

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

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

+2

Если вам нужна для хранения файлов, hdf5 или NetCDF очень поможет. – Anycorn

+5

Большой вопрос. –

ответ

1

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

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

+2

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

+0

Я думаю, что есть некоторые, но я ничего не знаю, извините. – peoro

5

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

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

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

Неудобство:

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

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

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

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

+5

Дополнительный недостаток: он не является точным. Важность этого может сильно различаться между приложениями. –

+0

@ Magnus Hoff: Как он не точен? Если вы выводите значение со всей точностью, а получатель использует ту же точность, то вы ничего не потеряете. Если пункт назначения не использует ту же точность, то все ставки отключены, но между этим и двоичным форматом нет потерь. –

+1

+1, даже если могут быть другие недостатки: дороже генерировать/анализировать - будет влиять только на производительность приложений, которые в основном считывают/записывают данные, но все же. Размер тоже влияет на него, и zip-ping сделает производительность еще хуже ... Тем не менее, хорошее решение в * почти всех случаях реального мира в 99,9% случаев. –

2

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

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

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

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

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

27

Brian «Beej Йоргенсен» Холл дает в своем Guide to Network Programming код, чтобы упаковать 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

, нетрудно включить NaN, бесконечность и денормализованные числа, если они вам понадобятся. Кроме того, этот код является общедоступным, что делает его отличным ответом. –

+3

Будет ли подход, основанный на «frexp», быть последовательно быстрее, чем повторное деление/умножение с плавающей запятой? 'frexp' дает вам' exp' и 'fnorm' за один вызов. Имейте в виду, что IEEE 754 double имеет показатель экспоненциальности в 11 бит, поэтому вы можете разделить/умножить на 2 несколько сотен раз. – jw013

+1

@ jw013 Каким будет подход, основанный на «frexp», в этой ситуации? Я сейчас борюсь с сериализацией с плавающей запятой, и, хотя подход 'frexp' кажется интересным, я не могу понять, как преобразовать мантиссу (которая находится между 0,5 и 1) в серию бит, представляющих значение в IEEE float или double. Есть ли эффективный и переносимый способ сделать это? –

4

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

0

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

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

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

+2

рыночные данные являются плохим примером: получение рыночных данных обычно дороже, чем анализ синтаксических строк. Это зависит от вашей технологии, но обычно такие вещи хранятся в базе данных. –

+0

@Alex, а?Я думаю, что вы, возможно, неправильно поняли меня, когда я говорю о средах с низкой задержкой, я не говорю об исторических данных, которые могут быть в БД, но в торговых средах, где каждый микросекунда считается - в них вы действительно хотите добавить дополнительную задержку в процедурах преобразования строк? 'atoi()', 'scanf()', 'sprintf()', независимо от того, что сравнительно медленное ... – Nim

+0

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

3

Посмотрите на (старую) реализацию файла gtypes.h в бойкий 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

0

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