Existe um printf
especificador de largura que pode ser aplicado a um especificador de ponto flutuante que formata automaticamente a saída para o número necessário de dígitos significativos de forma que, ao digitalizar a string de volta, o valor do ponto flutuante original seja adquirido?
Por exemplo, suponha que eu imprima um float
com uma precisão de 2
casas decimais:
float foobar = 0.9375;
printf("%.2f", foobar); // prints out 0.94
Ao digitalizar a saída 0.94
, não tenho garantia de conformidade com os padrões de que receberei o 0.9375
valor de ponto flutuante original (neste exemplo, provavelmente não o farei).
Eu gostaria de uma maneira printf
de imprimir automaticamente o valor de ponto flutuante para o número necessário de dígitos significativos para garantir que ele possa ser digitalizado de volta ao valor original passado printf
.
Eu poderia usar algumas das macros em float.h
para derivar a largura máxima a ser passada printf
, mas já existe um especificador para imprimir automaticamente o número necessário de dígitos significativos - ou pelo menos a largura máxima?
fonte
printf( "%f", val );
que já é portátil, eficiente e o padrão.double
. Como o seudouble
fica extremamente grande (muito longe de 1,0), ele na verdade se torna menos preciso na parte decimal (parte do valor menor que 1,0). Portanto, você não pode realmente ter uma resposta satisfatória aqui, porque sua pergunta contém uma suposição falsa (ou seja, que todos osfloat
s /double
s são criados iguais)Respostas:
Eu recomendo a solução hexadecimal @Jens Gustedt: use% a.
OP quer “imprimir com precisão máxima (ou pelo menos até a casa decimal mais significativa)”.
Um exemplo simples seria imprimir um sétimo como em:
Mas vamos cavar mais fundo ...
Matematicamente, a resposta é "0,142857 142857 142857 ...", mas estamos usando números de ponto flutuante de precisão finita. Vamos supor que o binário de dupla precisão IEEE 754 . Portanto, os
OneSeventh = 1.0/7.0
resultados no valor abaixo. Também são mostrados osdouble
números de ponto flutuante representáveis anteriores e seguintes .Imprimir a representação decimal exata de um
double
tem usos limitados.C tem 2 famílias de macros
<float.h>
para nos ajudar.O primeiro conjunto é o número de dígitos significativos a serem impressos em uma string em decimal, então, ao digitalizar a string de volta, obtemos o ponto flutuante original. Eles são mostrados com o valor mínimo da especificação C e um compilador C11 de amostra .
O segundo conjunto é o número de dígitos significativos que uma string pode ser digitalizada em um ponto flutuante e então o FP impresso, ainda mantendo a mesma apresentação da string. Eles são mostrados com o valor mínimo da especificação C e um compilador C11 de amostra . Acredito que disponível pré-C99.
O primeiro conjunto de macros parece cumprir a meta do OP de dígitos significativos . Mas essa macro nem sempre está disponível.
O "+ 3" foi o ponto crucial da minha resposta anterior. É centrado em saber se a conversão de ida e volta string-FP-string (conjunto # 2 macros disponíveis C89), como alguém determinaria os dígitos para FP-string-FP (conjunto # 1 macros disponíveis após C89)? Em geral, some 3 foi o resultado.
Agora, quantos dígitos significativos imprimir são conhecidos e direcionados
<float.h>
.Para imprimir N dígitos decimais significativos, pode-se usar vários formatos.
Com
"%e"
, o campo de precisão é o número de dígitos após o dígito inicial e o ponto decimal. Então- 1
está em ordem. Nota: Isso-1
não está na inicialint Digs = DECIMAL_DIG;
Com
"%f"
, o campo de precisão é o número de dígitos após o ponto decimal. Para um número comoOneSeventh/1000000.0
, seria necessárioOP_DBL_Digs + 6
ver todos os dígitos significativos .Nota: muitos são usados para
"%f"
. Isso exibe 6 dígitos após o ponto decimal; 6 é o padrão de exibição, não a precisão do número.fonte
%f
é 6."%f"
em primeiro lugar. Usar"%e"
como você mostrou é, obviamente, uma abordagem melhor em geral e efetivamente uma resposta decente (embora talvez não seja tão bom quanto usar"%a"
se estiver disponível, e é claro que"%a"
deveria estar disponível se `DBL_DECIMAL_DIG estiver). Sempre desejei um especificador de formato que sempre arredondasse exatamente para a precisão máxima (em vez das 6 casas decimais embutidas em código).A resposta curta para imprimir números de ponto flutuante sem perdas (de modo que eles possam ser lidos de volta exatamente para o mesmo número, exceto NaN e Infinity):
printf("%.9g", number)
.printf("%.17g", number)
.NÃO use
%f
, pois isso especifica apenas quantos dígitos significativos após o decimal e truncará pequenos números. Para referência, os números mágicos 9 e 17 podem ser encontrados nosfloat.h
quais defineFLT_DECIMAL_DIG
eDBL_DECIMAL_DIG
.fonte
%g
especificador?double
valores imediatamente acima0.1
:1.000_0000_0000_0000_2e-01
,1.000_0000_0000_0000_3e-01
necessidade de distinguir 17 dígitos."%.16g"
é insuficiente e"%.17g"
e"%.16e"
são suficientes. Os detalhes de%g
, foram mal lembrados por mim.Se você estiver interessado apenas no bit (padrão hexadecimal resp), poderá usar o
%a
formato. Isso garante a você:Eu teria que acrescentar que isso só está disponível desde C99.
fonte
Não, não existe tal especificador de largura de printf para imprimir ponto flutuante com precisão máxima . Deixe-me explicar o porquê.
A precisão máxima de
float
edouble
é variável e depende do valor real defloat
oudouble
.Lembre-se
float
edouble
são armazenados em sign.exponent.mantissa formato. Isso significa que há muito mais bits usados para o componente fracionário para números pequenos do que para números grandes.Por exemplo,
float
pode distinguir facilmente entre 0,0 e 0,1.Mas
float
não tem ideia da diferença entre1e27
e1e27 + 0.1
.Isso ocorre porque toda a precisão (que é limitada pelo número de bits da mantissa) é usada para a grande parte do número, à esquerda do decimal.
O
%.f
modificador apenas diz quantos valores decimais você deseja imprimir a partir do número flutuante no que diz respeito à formatação . O fato de que a precisão disponível depende do tamanho do número depende de você, como programador .printf
não posso / não lida com isso para você.fonte
float
fornece, e você afirma que não existe tal coisa (ou seja, que não existeFLT_DIG
), o que está errado.FLT_DIG
não significa nada. Essa resposta afirma que o número de casas decimais disponíveis depende do valor dentro do float .Basta usar as macros de
<float.h>
e o especificador de conversão de largura variável (".*"
):fonte
printf("%." FLT_DIG "f\n", f);
%e
, não tão bem para%f
: somente se souber que o valor a ser impresso está próximo de1.0
.%e
imprime dígitos significativos para números muito pequenos e%f
não. por exemplox = 1e-100
.%.5f
impressões0.00000
(uma perda total de precessão).%.5e
impressões1.00000e-100
.FLT_DIG
é definido para o valor para o qual é definido por um motivo. Se for 6, é porquefloat
não é capaz de conter mais de 6 dígitos de precisão. Se você imprimir usando%.7f
, o último dígito não terá significado. Pense antes de votar negativamente.%.6f
não é equivalente, porqueFLT_DIG
nem sempre é 6. E quem liga pra eficiência? E / S já é caro pra caramba, um dígito a mais ou menos precisão não causará gargalo.Eu executo um pequeno experimento para verificar se a impressão com
DBL_DECIMAL_DIG
realmente preserva exatamente a representação binária do número. Descobri que, para os compiladores e bibliotecas C que experimentei,DBL_DECIMAL_DIG
é de fato o número de dígitos necessário, e imprimir com um dígito a menos cria um problema significativo.Eu executo isso com o compilador C 19.00.24215.1 da Microsoft e gcc versão 7.4.0 20170516 (Debian 6.3.0-18 + deb9u1). Usar um dígito decimal a menos divide pela metade o número de números comparáveis exatamente iguais. (Eu também verifiquei que,
rand()
como usado, de fato produz cerca de um milhão de números diferentes.) Aqui estão os resultados detalhados.Microsoft C
GCC
fonte
RAND_MAX == 32767
. Considereu.s[j] = (rand() << 8) ^ rand();
ou algo parecido para garantir que todos os bits tenham uma chance de ser 0 ou 1.Em um de meus comentários a uma resposta, lamentei que há muito desejo alguma maneira de imprimir todos os dígitos significativos em um valor de ponto flutuante na forma decimal, da mesma forma que a pergunta pede. Bem, eu finalmente sentei e escrevi. Não é totalmente perfeito e este é um código de demonstração que imprime informações adicionais, mas funciona principalmente para meus testes. Por favor, deixe-me saber se você (ou seja, alguém) gostaria de uma cópia de todo o programa wrapper que o conduz para teste.
fonte
Pelo que sei, existe um algoritmo bem difundido que permite a saída para o número necessário de dígitos significativos de modo que, ao digitalizar a string de volta, o valor do ponto flutuante original é adquirido por
dtoa.c
escrito por Daniel Gay, que está disponível aqui no Netlib (ver também o papel associado ). Este código é usado, por exemplo, em Python, MySQL, Scilab e muitos outros.fonte