sprintf (buf, "% .20g", x) // какой размер должен быть buf? - PullRequest
3 голосов
/ 19 декабря 2009

Я конвертирую двойные значения в строку следующим образом:

std::string conv(double x) {
    char buf[30];
    sprintf(buf, "%.20g", x);
    return buf;
}

Я жестко закодировал размер буфера до 30, но я не уверен, достаточно ли он велик для всех случаев.

  • Как узнать максимальный размер буфера, который мне нужен?
  • Повышается ли точность (и, следовательно, необходимо увеличить буфер) при переключении с 32-разрядного на 64-разрядный режим?

PS: я не могу использовать ostringstream или boost::lexical_cast по соображениям производительности (см. this )

Ответы [ 5 ]

3 голосов
/ 20 декабря 2009

Я жестко закодировал размер буфера до 30, но не уверен, достаточно ли он для всех случаев.

Это так. % .20g указывает 20 цифр в мантиссе. добавить 1 для десятичной точки. 1 для (возможного) знака, 5 для «е + 308» или «е-308», показатель в худшем случае. и 1 для завершения нуля.

20 + 1 + 1 + 5 + 1 = 28.

Повышается ли точность (и, следовательно, необходимо увеличить буфер) при переключении с 32-разрядного на 64?

Нет.

Двойной одинаковый размер в обеих архитектурах. Если вы объявите свои переменные как long double, то у вас, возможно, будет еще 1 цифра в показателе «e + 4092», которая все еще помещается в 30-символьный буфер. Но только на X86 и только на старых процессорах.

Long double - это устаревшая 80-битная форма значения с плавающей запятой, которая была собственным форматом 486 FPU. Эта архитектура FPU плохо масштабировалась и с тех пор была отброшена в пользу инструкций стиля SSE, где наибольшее возможное значение с плавающей запятой - 64-битный двойной.

Какой длинный способ сказать, что буфера из 30 символов всегда будет достаточно, если вы продолжаете ограничивать мантиссу в вашей распечатке 20 цифрами.

3 голосов
/ 19 декабря 2009

Кажется, я помню, что если вы позвоните sprintf с адресатом NULL, он ничего не сделает. Однако он возвращает количество символов, которые он «написал». Если я прав (и я не могу найти источник для этого), тогда вы можете сделать:

// find the length of the string
int len = sprintf(NULL, fmt, var1, var2,...);
// allocate the necessary memory.
char *output = malloc(sizeof(char) * (len + 1)); // yes I know that sizeof(char) is defined as 1 but this seems nicer.
// now sprintf it after checking for errors
sprintf(output, fmt, var1, var2,...);

Другой вариант - использовать snprintf, который позволяет ограничить длину вывода:

#define MAX 20 /* or whatever length you want */
char output[MAX];
snprintf(output, MAX, fmt, var1, var2,...);

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

3 голосов
/ 19 декабря 2009

printf("%.20g", 1.79769e+308); равно 1.7976900000000000632e+308, 27 байтов, включая завершающий \ 0. Я бы выбрал 64 или 128, просто чтобы быть уверенным.

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

Кроме того, вы уверены, что узким местом вашей программы является lexical_cast ..? Делать то, что ты делаешь, кажется мне очень глупым

0 голосов
/ 20 декабря 2009

Если вы работаете на платформе, которая поддерживает POSIX или C99, вы сможете использовать snprintf для вычисления размера буфера, который вам понадобится. snprintf принимает параметр, указывающий размер буфера, который вы передаете; если размер строки будет превышать размер этого буфера, он усекает вывод, чтобы поместиться в буфер, и возвращает количество места, которое ему потребовалось бы для размещения всего вывода. Вы можете использовать выходные данные этого, чтобы выделить буфер, который является точным правильным размером. Если вы просто хотите вычислить размер буфера, который вам нужен, вы можете передать NULL в качестве буфера и размер 0, чтобы вычислить, сколько места вам нужно.

int size = snprintf(NULL, 0, "%.20g", x);
char *buf = malloc(size + 1); // Need the + 1 for a terminating null character
snprintf(buf, size + 1, "%.20g", x);

Не забудьте free(buf) после того, как вы его использовали, чтобы избежать утечек памяти.

Проблема в том, что он не будет работать в Visual Studio, которая все еще не поддерживает C99. Хотя они имеют что-то вроде snprintf, если переданный буфер слишком мал, он не возвращает необходимый размер, а вместо этого возвращает -1, что совершенно бесполезно (и он не принимает NULL как буфер, даже с длиной 0.

Если вы не возражаете против усечения, вы можете просто использовать snprintf с буфером фиксированного размера и быть уверенным, что вы не переполните его:

char buf[30];
snprintf(buf, sizeof(buf), "%.20g", x);

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

0 голосов
/ 19 декабря 2009

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

#include <float.h>
#include <stdio.h>

int main(void)
{
    double m = DBL_MAX;
    double n = DBL_MIN;
    int i;
    i = printf("%.20g\n", m);
    printf("%d\n", i);
    i = printf("%.20g\n", n);
    printf("%d\n", i);
    return 0;
}

Для меня это печатает:

1.7976931348623157081e+308
27
2.2250738585072013831e-308
27

Так как 27 включает перевод строки, но не включает завершающий 0 для строк, я бы сказал, что в этой системе достаточно 27. Для long double в моей системе ответом будет 27 и 28 для LDBL_MAX и LDBL_MIN соответственно.

Справочная страница (на моем для sprintf говорится об %g:

Двойной аргумент преобразуется в стиль f или e (или F или E для G преобразования). Точность определяет количество значительное цифры. Если точность отсутствует, дается 6 цифр; если точность равна нулю, она рассматривается как 1. Стиль e используется, если показатель его конверсии меньше -4 или больше чем или равно точности. Замыкающие нули удаляются из дробная часть результата; появляется только десятичная точка если оно сопровождается хотя бы одной цифрой.

Аналогичная формулировка в стандарте C.

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

...