sprintf(buffer, "%.20g", x) // quão grande deve buf ser?
-
20-09-2019 - |
Pergunta
Eu estou convertendo duplo valores de cadeia de caracteres como esta:
std::string conv(double x) {
char buf[30];
sprintf(buf, "%.20g", x);
return buf;
}
Eu codifiquei o tamanho do buffer de 30, mas não tenho certeza se isso é grande o suficiente para todos os casos.
- Como posso descobrir o tamanho máximo da memória intermédia que eu preciso?
- A precisão obter mais alta (e, portanto, buffer de necessidades para aumentar) quando a troca de 32 bits para 64?
PS:Eu não posso usar ostringstream
ou boost::lexical_cast
para o desempenho razão (ver este)
Solução
Eu codifiquei o tamanho do buffer de 30, mas não tenho certeza se isso é grande o suficiente para todos os casos.
É.%.20g especifica 20 dígitos na mantissa.adicione 1 para o ponto decimal.1 para o (possível) assinar, 5 para "e+308" ou "e-308", o pior caso de expoente.e 1 para o valor nulo.
20 + 1 + 1 + 5 + 1 = 28.
A precisão obter mais alta (e, portanto, buffer de necessidades para aumentar) quando a troca de 32 bits para 64?
Não.
Um casal é o mesmo tamanho em ambas as arquiteturas.Se você declarar variáveis como long double, então você possivelmente tem 1 dígito a mais no expoente "e+4092", que ainda se encaixa em uma de 30 caracteres do buffer.Mas só em X86, e apenas em processadores mais antigos.
O long double é um obsoletos 80 bits forma de valor de ponto flutuante que foi o formato nativo do 486 FPU.Que FPU arquitetura não escala bem e como já foi descartado em favor do estilo de instruções SSE, onde o maior valor de ponto flutuante de 64 bits duplo.
Que é um longo caminho de dizer um buffer de 30 caracteres sempre vai ser suficiente, enquanto você manter limitar a mantissa em sua impressão a 20 dígitos.
Outras dicas
printf("%.20g", 1.79769e+308);
é 1.7976900000000000632e+308
, 27 bytes, incluindo o à direita 0. Eu escolheria 64 ou 128 apenas para ter certeza.
(Como está na pilha e lançado logo depois que você também pode usar grandes buffers, até 2048 bytes, sem encontrar problemas para aplicativos não incorporados)
Além disso, tem certeza de que o gargalo do seu programa é lexical_cast
..? Fazer o que você está fazendo parece muito bobo para mim
Parece que me lembro que se você ligar sprintf
com um NULL
destino, não faz nada. No entanto, retorna o número de chars que "escreveu". Se estou certo (e não consigo encontrar a fonte para isso), você pode fazer:
// 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,...);
Outra opção é usar snprintf
que permite limitar o comprimento da saída:
#define MAX 20 /* or whatever length you want */
char output[MAX];
snprintf(output, MAX, fmt, var1, var2,...);
snprintf
pega o tamanho do buffer como argumento e não permite que a sequência de saída exceda esse tamanho.
Aqui está um programa para imprimir o número de dígitos necessários para valores máximos e mínimos double
pode levar para qualquer sistema:
#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;
}
Para mim, imprime:
1.7976931348623157081e+308
27
2.2250738585072013831e-308
27
Desde 27, inclui uma nova linha, mas não inclui o término 0
Para as cordas, eu diria que neste sistema, 27 devem ser suficientes. Por long double
, a resposta parece ser 27 e 28 para LDBL_MAX
e LDBL_MIN
respectivamente no meu sistema.
A página do homem (no meu para sprintf
diz isso sobre %g
:
O argumento duplo é convertido em estilo para e (ou f ou e para conversões G). A precisão especifica o número de dígitos significativos. Se falta a precisão, 6 dígitos serão dados; Se a precisão for zero, ela será tratada como 1. O estilo E é usado se o expoente de sua conversão for menor que -4 ou maior ou igual à precisão. Os zeros à direita são removidos da parte fracionária do resultado; Um ponto decimal aparece apenas se for seguido por pelo menos um dígito.
A redação semelhante está no padrão C.
Então, acho que você estará seguro se usou a saída do programa acima como tamanho da sua matriz.
Se você estiver em uma plataforma que suporta POSIX ou C99, poderá usar snprintf
Para calcular o tamanho do buffer de que você precisará. snprintf
pega um parâmetro indicando o tamanho do buffer que você está passando; Se o tamanho da string exceder o tamanho desse buffer, ela truncaria a saída para caber no buffer e retornará a quantidade de espaço necessária para ajustar a saída inteira. Você pode usar a saída disso para alocar um buffer do tamanho exato. Se você deseja apenas calcular o tamanho do buffer que precisa, pode passar nulo como o buffer e um tamanho de 0 para calcular quanto espaço você precisa.
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);
Lembrar de free(buf)
Depois de usá -lo para evitar vazamentos de memória.
O problema é que ele não funcionará no Visual Studio, que ainda não suporta o C99. Enquanto eles têm algo como snprintf
, se o buffer passado for muito pequeno, não retornará o tamanho necessário, mas retorna -1
Em vez disso, o que é completamente inútil (e não aceita NULL
como um buffer, mesmo com um 0
comprimento).
Se você não se importa de truncar, você pode simplesmente usar snprintf
com um buffer de tamanho fixo e tenha certeza de que você não vai transbordar:
char buf[30];
snprintf(buf, sizeof(buf), "%.20g", x);
Verifique sua plataforma documentos em snprintf
; Em particular, algumas plataformas não podem adicionar um nulo de término no final da string se a string for truncada; portanto, você pode precisar fazer isso você mesmo.