Pergunta

Executando uma experiência rápida relacionado com é o dobro Multiplicação quebrado em .NET? e ler um par de artigos sobre C # string de formatação, eu pensei que isso:

{
    double i = 10 * 0.69;
    Console.WriteLine(i);
    Console.WriteLine(String.Format("  {0:F20}", i));
    Console.WriteLine(String.Format("+ {0:F20}", 6.9 - i));
    Console.WriteLine(String.Format("= {0:F20}", 6.9));
}

Seria o C # equivalente a este código C:

{
    double i = 10 * 0.69;

    printf ( "%f\n", i );
    printf ( "  %.20f\n", i );
    printf ( "+ %.20f\n", 6.9 - i );
    printf ( "= %.20f\n", 6.9 );
}

No entanto, o C # produz a saída:

6.9
  6.90000000000000000000
+ 0.00000000000000088818
= 6.90000000000000000000

apesar i mostrando-se igual ao valor 6,89999999999999946709 (em vez de 6,9) no depurador.

em comparação com C que mostra a precisão solicitada pelo formato:

6.900000                          
  6.89999999999999946709          
+ 0.00000000000000088818          
= 6.90000000000000035527          

O que está acontecendo?

(Microsoft .NET Framework versão 3.51 SP1 / Visual Studio C # 2008 Express Edition)


Eu tenho um fundo em computação numérica e experiência na implementação intervalo aritmética - uma técnica para estimar erros devido aos limites de precisão em sistemas numéricos complicados - em várias plataformas. Para obter a recompensa, não tente explicar sobre a precisão de armazenamento - neste caso, é uma diferença de um ULP de um 64 bit duplo.

Para obter a recompensa, eu quero saber como (ou se) .Net pode formatar uma dupla com a precisão requerida tão visível no código C.

Foi útil?

Solução

O problema é que o .NET será sempre em volta de uma double 15 dígitos decimais significativas antes aplicar a sua formatação, independentemente da precisão solicitado pelo seu formato e independentemente do valor decimal exata do número binário .

Eu acho que o depurador Visual Studio tem as suas próprias rotinas de formato / exibição que acessam diretamente o número binário interno, daí as discrepâncias entre seu código C #, o código C e o depurador.

Não há nada embutido que lhe permitirá acessar o valor decimal exato de um double, ou para permitir que você formate um double a um número específico de casas decimais, mas você pode fazer isso sozinho, escolhendo além do binário interno número e reconstruí-lo como uma representação string do valor decimal.

Como alternativa, você poderia usar de Jon Skeet DoubleConverter classe (ligada a partir de sua "ponto flutuante binário e .NET" artigo ). Isto tem um método ToExactString que retorna o valor decimal exato de um double. Você poderia facilmente modificar este para permitir o arredondamento da saída para uma precisão específica.

double i = 10 * 0.69;
Console.WriteLine(DoubleConverter.ToExactString(i));
Console.WriteLine(DoubleConverter.ToExactString(6.9 - i));
Console.WriteLine(DoubleConverter.ToExactString(6.9));

// 6.89999999999999946709294817992486059665679931640625
// 0.00000000000000088817841970012523233890533447265625
// 6.9000000000000003552713678800500929355621337890625

Outras dicas

Digits after decimal point
// just two decimal places
String.Format("{0:0.00}", 123.4567);      // "123.46"
String.Format("{0:0.00}", 123.4);         // "123.40"
String.Format("{0:0.00}", 123.0);         // "123.00"

// max. two decimal places
String.Format("{0:0.##}", 123.4567);      // "123.46"
String.Format("{0:0.##}", 123.4);         // "123.4"
String.Format("{0:0.##}", 123.0);         // "123"
// at least two digits before decimal point
String.Format("{0:00.0}", 123.4567);      // "123.5"
String.Format("{0:00.0}", 23.4567);       // "23.5"
String.Format("{0:00.0}", 3.4567);        // "03.5"
String.Format("{0:00.0}", -3.4567);       // "-03.5"

Thousands separator
String.Format("{0:0,0.0}", 12345.67);     // "12,345.7"
String.Format("{0:0,0}", 12345.67);       // "12,346"

Zero
Following code shows how can be formatted a zero (of double type).

String.Format("{0:0.0}", 0.0);            // "0.0"
String.Format("{0:0.#}", 0.0);            // "0"
String.Format("{0:#.0}", 0.0);            // ".0"
String.Format("{0:#.#}", 0.0);            // ""

Align numbers with spaces
String.Format("{0,10:0.0}", 123.4567);    // "     123.5"
String.Format("{0,-10:0.0}", 123.4567);   // "123.5     "
String.Format("{0,10:0.0}", -123.4567);   // "    -123.5"
String.Format("{0,-10:0.0}", -123.4567);  // "-123.5    "

Custom formatting for negative numbers and zero
String.Format("{0:0.00;minus 0.00;zero}", 123.4567);   // "123.46"
String.Format("{0:0.00;minus 0.00;zero}", -123.4567);  // "minus 123.46"
String.Format("{0:0.00;minus 0.00;zero}", 0.0);        // "zero"

Some funny examples
String.Format("{0:my number is 0.0}", 12.3);   // "my number is 12.3"
String.Format("{0:0aaa.bbb0}", 12.3);

Dê uma olhada neste MSDN referência . Nas notas afirma que os números são arredondados para o número de casas decimais solicitado.

Se em vez de utilizar "{0: R}" que irá produzir o que é referido como um valor "round-trip", dê uma olhada nesta referência MSDN para mais informações, aqui está o meu código e a saída:

double d = 10 * 0.69;
Console.WriteLine("  {0:R}", d);
Console.WriteLine("+ {0:F20}", 6.9 - d);
Console.WriteLine("= {0:F20}", 6.9);

saída

  6.8999999999999995
+ 0.00000000000000088818
= 6.90000000000000000000

Embora esta questão está, entretanto, fechou, eu acredito que vale a pena mencionar como esta atrocidade veio à existência. De certa forma, você pode culpar o # especificação C, que afirma que a dupla deve ter uma precisão de 15 ou 16 dígitos (o resultado da IEEE-754). Um pouco mais adiante (seção 4.1.6) é afirmado que as implementações estão autorizados a usar maior precisão. Lembre-se: maior , e não diminuir. Eles sequer são autorizados a desviar-se IEEE-754: expressões do tipo x * y / z onde x * y renderia +/-INF mas seria em um intervalo válido depois de dividir, não tem que resultar em um erro. Esta característica faz com que seja mais fácil para os compiladores de usar maior precisão em arquiteturas onde isso iria produzir um melhor desempenho.

Mas eu prometi uma "razão". Aqui está uma citação (que solicitou um recurso em um de seus comentários recentes) do Shared Source CLI , em clr/src/vm/comnumber.cpp:

"A fim de dar números que são tanto amigável para exibir e round-trippable, nós analisamos o número usando 15 dígitos e, em seguida, determinar se -lo arredondar viagens para o mesmo valor. E se ele faz, nós converter esse número para um corda, caso contrário, nova análise utilizando 17 dígitos e exibição que ".

Em outras palavras: Equipe de Desenvolvimento CLI do MS decidiu ser tanto round-trippable e mostrar valores bonitas que não são uma dor de ler. Bom ou mal? Eu desejaria para um opt-in ou opt-out.

O truque que faz para descobrir este round-trippability de qualquer número dado? A conversão a uma estrutura NÚMERO genérico (o qual possui campos separados para as propriedades de um duplo) e na parte traseira, e então comparar se o resultado é diferente. Se for diferente, o valor exato é utilizado (como no seu valor médio com 6.9 - i) se for o mesmo, o "muito valor" é usado.

Como você já observou em um comentário para Andyp, 6.90...00 é bit a bit igual a 6.89...9467. E agora você sabe por que 0.0...8818 é usado: é diferente bit a bit de 0.0

.

Este barreira de 15 dígitos é codificado e só pode ser alterada recompilando o CLI, usando Mono ou chamando Microsoft e convencê-los para adicionar uma opção para imprimir "precisão" cheio ( não é realmente precisão, mas pela falta de uma palavra melhor). É provavelmente mais fácil apenas calcular a precisão 52 bits mesmo ou usar a biblioteca mencionado anteriormente.

EDIT: se você gosta de experimentar-se com IEE-754 pontos flutuantes, considerar este ferramenta online, que mostra todas as partes relevantes de um ponto flutuante.

Use

Console.WriteLine(String.Format("  {0:G17}", i));

Isso vai dar-lhe todos os 17 dígitos que tem. Por padrão, um valor duplo contém 15 dígitos decimais de precisão, embora um máximo de 17 dígitos é mantido internamente. . {0: R} não vai sempre dar-lhe 17 dígitos, que lhe dará 15 se o número pode ser representado com que precisão

que retorna 15 dígitos se o número pode ser representado com que a precisão ou 17 dígitos se o número só pode ser representado com a máxima precisão. Não há qualquer coisa que você pode fazer para tornar as a dupla retorno mais dígitos que é a forma como ele é implementado. Se você não gosta que fazer uma nova classe double-se ...

O .NET dupla não pode armazenar mais dígitos do que 17 para que você não pode ver 6,89999999999999946709 no depurador você veria 6,8999999999999995. Por favor, forneça uma imagem para provar que estamos errados.

A resposta para isso é simples e pode ser encontrado na MSDN

Lembre-se que um número de ponto flutuante só pode aproximar um número decimal, e que a precisão de um número de ponto flutuante determina a precisão com que o número se aproxima de um número decimal. Por padrão, um valor duplo contém 15 dígitos decimais de precisão , embora um máximo de 17 dígitos é mantido internamente.

No seu exemplo, o valor de i é 6,89999999999999946709 que tem o número 9 para todas as posições entre o 3º e o dígito 16 (lembre-se de contar a parte inteira nos dígitos). Ao converter a corda, as rondas quadro o número para o dígito 15º.

i     = 6.89999999999999 946709
digit =           111111 111122
        1 23456789012345 678901

Eu tentei reproduzir suas descobertas, mas quando eu assisti 'i' no depurador ele mostrou-se como '6,8999999999999995' não como '6,89999999999999946709' como você escreveu na pergunta. Você pode fornecer passos para reproduzir o que viu?

Para ver o que os shows do depurador, você pode usar um DoubleConverter como na seguinte linha de código:

Console.WriteLine(TypeDescriptor.GetConverter(i).ConvertTo(i, typeof(string)));

Espero que isso ajude!

Edit: Eu acho que estou mais cansado do que eu pensava, é claro que isso é o mesmo que a formatação para o valor de ida e volta (como mencionado anteriormente)

.

A resposta é sim, impressão dupla está quebrado em .NET, eles estão imprimindo arrastando dígitos lixo.

Você pode ler como implementá-lo corretamente aqui .

Eu tive que fazer o mesmo para IronScheme.

> (* 10.0 0.69)
6.8999999999999995
> 6.89999999999999946709
6.8999999999999995
> (- 6.9 (* 10.0 0.69))
8.881784197001252e-16
> 6.9
6.9
> (- 6.9 8.881784197001252e-16)
6.8999999999999995

Nota:. Ambos C e C # tem valor correto, impressão acabado

Update:. Eu ainda estou procurando a lista de discussão conversa que tive que levam até esta descoberta

Eu encontrei esta solução rápida.

    double i = 10 * 0.69;
    System.Diagnostics.Debug.WriteLine(i);


    String s = String.Format("{0:F20}", i).Substring(0,20);
    System.Diagnostics.Debug.WriteLine(s + " " +s.Length );

Console.WriteLine (String.Format ( "Taxas do curso é {0: 0.00}", + cfees));

internal void DisplaycourseDetails()
        {
            Console.WriteLine("Course Code : " + cid);
            Console.WriteLine("Couse Name : " + cname);
            //Console.WriteLine("Couse Name : " + string.Format("{0:0.00}"+ cfees));
            // string s = string.Format("Course Fees is {0:0.00}", +cfees);
            // Console.WriteLine(s);
            Console.WriteLine( string.Format("Course Fees is {0:0.00}", +cfees));
        }
        static void Main(string[] args)
        {
            Course obj1 = new Course(101, "C# .net", 1100.00);
            obj1.DisplaycourseDetails();
            Course obj2 = new Course(102, "Angular", 7000.00);
            obj2.DisplaycourseDetails();
            Course obj3 = new Course(103, "MVC", 1100.00);
            obj3.DisplaycourseDetails();
            Console.ReadLine();
        }
    }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top