Вопрос

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

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

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

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

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

Это было полезно?

Решение

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

Это.%.20g задает 20 цифр в мантиссе.добавьте 1 для десятичной точки.1 для (возможного) знака, 5 для «e+308» или «e-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 цифрами.

Другие советы

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

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

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

Кажется, я помню, что если ты позвонишь 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 принимает размер буфера в качестве аргумента и не позволяет выходной строке превышать этот размер.

Вот программа для печати количества цифр, необходимого для максимального и минимального значений 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:

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

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

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

Если вы используете платформу, поддерживающую 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;в частности, некоторые платформы могут не добавлять завершающий нуль в конце строки, если строка усечена, поэтому вам, возможно, придется сделать это самостоятельно.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top