Компактное представление без потерь констант с плавающей точкой в ​​C/C++

У меня есть программа, написанная на C++, которая генерирует исходный код C для математических расчетов. Я заметил, что константы занимают очень много места в сгенерированном коде и ищут более компактное представление.

Для генерации констант я сейчас использую:

double v = ...
cfile << std::scientific << std::setprecision(std::numeric_limits<double>::digits10 + 1) << v;

Я уверен, что это представление без потерь, но оно также очень раздутое. Например, ноль и единица будут представлены как что-то вроде 0,0000000000000000e+00 и 1.0000000000000000e+00. И "0" или "1" несет столько же информации.

Есть ли способ напечатать константы в файл более компактным, но все же без потерь способом? Он не должен хорошо выглядеть для читателя-человека, просто компилировать, когда он представлен в простом C-коде (если C99, я бы предпочел, чтобы это был также допустимый C++). Шестнадцатеричный может быть в порядке, если он переносим.

РЕДАКТИРОВАТЬ: Удалено std::fixed во фрагменте кода.

4 ответа

Решение

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

В гипотетическом put_constant(double value) Вы можете проверить, какое значение вы должны написать:

  • Это целое число? Не раздутый код с std::fixed а также set_precision, просто приведите к целому числу и добавьте точку.
  • Попробуйте преобразовать его в строку с настройками по умолчанию, а затем преобразовать обратно в doubleЕсли ничего не изменилось, то представление по умолчанию (короткое) достаточно хорошо.
  • Преобразуйте его в строку с вашей фактической реализацией и проверьте его длину. Если это больше чем N (см. Позже), используйте другое представление, иначе просто напишите это.

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

#define USE_L2D __int64 ___tmp = 0;
#define L2D(x) (double&)(___tmp=x)

int main(int argc, char* argv[])
{
    // 2.2 = in memory it is 0x400199999999999A

    USE_L2D
    double f1 = L2D(0x400199999999999A);
    double f2 = 123456.1234567891234567;

    return 0;
}

Вы можете использовать шестнадцатеричное число с плавающей точкой ( спецификатор формата%a для printf() в C); он определен, чтобы сохранить все биты точности (C11, 7.21.6.1p8, a,A спецификаторы).

cfile << std::hexfloat << v;

Если ваш компилятор / стандартная библиотека не поддерживает hexfloat, вы можете использовать C99 %a спецификатор printf (это эквивалентно, как указано в таблице 88 C++ 11 в разделе 22.4.2.2.2):

printf("%a", v);

Например, следующая программа действительна C99:

#include <stdio.h>
int main() {
   double v = 0x1.8p+1;
   printf("%a\n", v);
}

Ваш сгенерированный исходный файл не будет действительным C++ 11, так как довольно нелепо C++ 11 не поддерживает шестнадцатеричные литералы с плавающей точкой. Однако многие компиляторы C++ 11 поддерживают шестнадцатеричные литералы с плавающей точкой C99 в качестве расширения.

Во-первых, вы противоречите себе, когда вы впервые говоритеstd::scientific, а потом std::fixed, А во-вторых, вы, вероятно, тоже не хотите. Формат по умолчанию, как правило, предназначен для этого лучше всего. Формат по умолчанию не имеет ни имени, ни манипулятора, но это то, что вы получаете, если не указан другой формат, и его можно установить (если другой код установил другой формат), используя:

cfile.setf( std::ios_base::fmtflags(), std::ios_base::floatfield );

Я бы рекомендовал использовать это. (Вам все еще нужна точность, конечно.)

Я не уверен, что вы можете передавать плавающие точки без потерь, как это. Плавающие точки обязательно с потерями. Хотя они могут точно представлять подмножество значений, вы не можете включать ВСЕ значимые цифры - разное оборудование может иметь разные представления, поэтому вы не можете гарантировать отсутствие потери информации. Даже если вы могли бы передать все это, поскольку значение не может быть представлено принимающим оборудованием.

Обычный ofstream::operator<< будет печатать столько цифр, сколько потребуется, поэтому нет необходимости усложнять ситуацию.

Другие вопросы по тегам