Quando devo usar o dobro em vez do decimal?


265

Posso citar três vantagens em usar double(ou float) em vez de decimal:

  1. Usa menos memória.
  2. Mais rápido porque as operações matemáticas de ponto flutuante são suportadas nativamente pelos processadores.
  3. Pode representar um intervalo maior de números.

Mas essas vantagens parecem se aplicar apenas a operações intensivas de cálculo, como as encontradas no software de modelagem. Obviamente, duplos não devem ser usados ​​quando a precisão é necessária, como cálculos financeiros. Então, existem razões práticas para escolher double(ou float) em vez de decimalem aplicativos "normais"?

Editado para adicionar: Obrigado por todas as ótimas respostas que aprendi com elas.

Mais uma pergunta: algumas pessoas argumentaram que o dobro pode representar mais precisamente números reais. Quando declarado, eu pensaria que eles geralmente os representam com mais precisão também. Mas é uma afirmação verdadeira de que a precisão pode diminuir (às vezes significativamente) quando operações de ponto flutuante são executadas?



5
Isso é votado regularmente e eu ainda luto com isso. Por exemplo, eu estou trabalhando em um aplicativo que faz cálculos financeiros, então estou usando o decimal todo. Mas as funções Math e VisualBasic.Financial usam o dobro, então há muitas conversões, o que me deixa constantemente adivinhando o uso do decimal.
21714 Jamie Ide

@ JamieIde que loucura, as funções financeiras usam o dobro, o dinheiro deve sempre estar em decimal.
Chris Marisic

@ChrisMarisic Mas o que Jamie Ide pode fazer trabalhando com porcaria herdada usando o dobro? Então você deve usar dupla também outra das muitas conversões irá causar erros de arredondamento ... há maravilhas que ele mencionou VisualBasic pfffhh .....
Elisabeth

@ Elizabeth provavelmente usaria uma biblioteca diferente que suportava corretamente decimal. Seja qual for VisualBasic.Financial fornece provavelmente existe em várias outras bibliotecas hoje
Chris Marisic

Respostas:


306

Eu acho que você resumiu as vantagens muito bem. Você está perdendo um ponto. O decimaltipo é apenas mais preciso na representação dos números da base 10 (por exemplo, aqueles usados ​​em cálculos monetários / financeiros). Em geral, o doubletipo oferece pelo menos a mesma precisão (alguém me corrija se eu estiver errado) e definitivamente maior velocidade para números reais arbitrários. A conclusão simples é: ao considerar qual usar, use sempre, a doublemenos que você precise da base 10precisão que decimaloferece.

Editar:

Com relação à sua pergunta adicional sobre a diminuição da precisão dos números de ponto flutuante após as operações, esse é um problema um pouco mais sutil. De fato, a precisão (eu uso o termo de forma intercambiável para precisão aqui) diminuirá constantemente após cada operação. Isto é devido a duas razões:

  1. o fato de que certos números (obviamente decimais) não podem ser realmente representados na forma de ponto flutuante
  2. ocorrem erros de arredondamento, como se você estivesse fazendo o cálculo manualmente. Depende muito do contexto (quantas operações você está executando) se esses erros são significativos o suficiente para justificar muita reflexão.

Em todos os casos, se você quiser comparar dois números de ponto flutuante que, em teoria, deveriam ser equivalentes (mas foram calculados usando cálculos diferentes), é necessário permitir um certo grau de tolerância (quanto varia, mas geralmente é muito pequeno) .

Para uma visão geral mais detalhada dos casos particulares em que erros de precisão podem ser introduzidos, consulte a seção Precisão do artigo da Wikipedia . Por fim, se você quiser uma discussão profundamente aprofundada (e matemática) sobre números / operações de ponto flutuante no nível da máquina, tente ler o artigo frequentemente citado O que todo cientista da computação deve saber sobre aritmética de ponto flutuante .


1
Você pode fornecer um exame e de um número base 10 com o qual a precisão é perdida ao converter para a base 2?
31820 Mark Cidade

@ Mark: 1.000001 é um exemplo, pelo menos de acordo com Jon Skeet. (Veja a pergunta 3 desta página: yoda.arachsys.com/csharp/teasers-answers.html )
Noldorin

25
@ Mark: exemplo muito simples: 0,1 é uma fração periódica na base 2, portanto não pode ser expressa com precisão em a double. Os computadores modernos ainda imprimirão o valor correto, mas apenas porque "adivinhar" o resultado - não porque ele realmente seja expresso corretamente.
219 Konrad Rudolph

1
O Decimaltipo tem 93 bits de precisão na mantissa, em comparação com cerca de 52 para double. Eu gostaria que a Microsoft suportasse o formato IEEE de 80 bits, mesmo que ele tivesse que ser preenchido com 16 bytes; teria permitido uma faixa maior que doubleou Decimal, muito melhor velocidade do que Decimal, suporte para operações transcendentais (por exemplo, sin (x), log (x), etc.) e precisão que, embora não tão boa quanto Decimalseria muito melhor do que double.
supercat 3/09/13

@charlotte: Se você ler meu post completo, verá que isso é explicado.
Noldorin

59

Você parece ter certeza dos benefícios de usar um tipo de ponto flutuante. Eu costumo projetar decimais em todos os casos, e confio em um criador de perfil para me informar se as operações em decimais estão causando gargalos ou lentidão. Nesses casos, eu irei "converter para baixo" para dobrar ou flutuar, mas apenas o faço internamente, e tentarei cuidadosamente gerenciar a perda de precisão limitando o número de dígitos significativos na operação matemática que está sendo executada.

Em geral, se seu valor é transitório (não reutilizado), você pode usar um tipo de ponto flutuante. O verdadeiro problema com os tipos de ponto flutuante são os três cenários a seguir.

  1. Você está agregando valores de ponto flutuante (nesse caso, os erros de precisão são compostos)
  2. Você constrói valores com base no valor do ponto flutuante (por exemplo, em um algoritmo recursivo)
  3. Você está fazendo contas com um número muito amplo de dígitos significativos (por exemplo, 123456789.1 * .000000000000000987654321)

EDITAR

De acordo com a documentação de referência em decimais C # :

A palavra-chave decimal indica um tipo de dados de 128 bits. Comparado aos tipos de ponto flutuante, o tipo decimal possui uma precisão maior e um intervalo menor, o que o torna adequado para cálculos financeiros e monetários.

Então, para esclarecer minha afirmação acima:

Eu costumo projetar decimais em todos os casos, e confio em um criador de perfil para me informar se as operações em decimais estão causando gargalos ou lentidão.

Eu só trabalhei em indústrias onde os decimais são favoráveis. Se você estiver trabalhando em mecanismos físicos ou gráficos, é provavelmente muito mais benéfico projetar para um tipo de ponto flutuante (flutuante ou duplo).

Decimal não é infinitamente preciso (é impossível representar precisão infinita para não integral em um tipo de dados primitivo), mas é muito mais preciso que o dobro:

  • decimal = 28-29 dígitos significativos
  • double = 15-16 dígitos significativos
  • float = 7 dígitos significativos

EDIT 2

Em resposta ao comentário de Konrad Rudolph , o item 1 (acima) está definitivamente correto. A agregação de imprecisões realmente se compõe. Veja o código abaixo para um exemplo:

private const float THREE_FIFTHS = 3f / 5f;
private const int ONE_MILLION = 1000000;

public static void Main(string[] args)
{
    Console.WriteLine("Three Fifths: {0}", THREE_FIFTHS.ToString("F10"));
    float asSingle = 0f;
    double asDouble = 0d;
    decimal asDecimal = 0M;

    for (int i = 0; i < ONE_MILLION; i++)
    {
        asSingle += THREE_FIFTHS;
        asDouble += THREE_FIFTHS;
        asDecimal += (decimal) THREE_FIFTHS;
    }
    Console.WriteLine("Six Hundred Thousand: {0:F10}", THREE_FIFTHS * ONE_MILLION);
    Console.WriteLine("Single: {0}", asSingle.ToString("F10"));
    Console.WriteLine("Double: {0}", asDouble.ToString("F10"));
    Console.WriteLine("Decimal: {0}", asDecimal.ToString("F10"));
    Console.ReadLine();
}

Isso gera o seguinte:

Three Fifths: 0.6000000000
Six Hundred Thousand: 600000.0000000000
Single: 599093.4000000000
Double: 599999.9999886850
Decimal: 600000.0000000000

Como você pode ver, apesar de estarmos adicionando da mesma fonte constante, os resultados do duplo são menos precisos (embora provavelmente sejam arredondados corretamente), e o flutuador é muito menos preciso, a ponto de ter sido reduzido a apenas dois dígitos significativos.


1
O ponto 1 está incorreto. Os erros de precisão / arredondamento ocorrem apenas na conversão, não nos cálculos. É claro que é correto que a maioria das operações matemáticas seja instável, multiplicando o erro. Mas esse é outro problema e aplica-se o mesmo a todos os tipos de dados de precisão limitada, especialmente para decimal.
289 Konrad Rudolph

1
@ Konrad Rudolph, veja o exemplo em "EDIT 2" como evidência do argumento que eu estava tentando enfatizar no item # 1. Freqüentemente, esse problema não se manifesta porque a imprecisão positiva se equilibra com a imprecisão negativa, e eles lavam o agregado, mas agregando o mesmo número (como fiz no exemplo) destaca o problema.
Michael Meadows

Ótimo exemplo. Apenas mostrei aos meus desenvolvedores juniores, as crianças ficaram maravilhadas.
Machado

Agora você pode fazer o mesmo com 2 / 3rds em vez de 3 / 5s ... Você deve aprender sobre o sistema de números sexagesimal que lida com 2 / 3rds perfeitamente.
gnasher729

1
@ gnasher729, usar 2 / 3rds em vez de 3 / 5ths não foi tratado perfeitamente bem para os diferentes tipos. Curiosamente, o valor flutuante rendeu Single: 667660.400000000000enquanto o valor decimal rendeu Decimal: 666666.7000000000. O valor flutuante é um pouco menor que mil acima do valor correto.
jhenninger

25

Use decimal para os valores da base 10, por exemplo, cálculos financeiros, como outros sugeriram.

Mas o dobro é geralmente mais preciso para valores calculados arbitrariamente.

Por exemplo, se você deseja calcular o peso de cada linha em um portfólio, use o dobro, pois o resultado aumentará quase 100%.

No exemplo a seguir, doubleResult está mais próximo de 1 que decimalResult:

// Add one third + one third + one third with decimal
decimal decimalValue = 1M / 3M;
decimal decimalResult = decimalValue + decimalValue + decimalValue;
// Add one third + one third + one third with double
double doubleValue = 1D / 3D;
double doubleResult = doubleValue + doubleValue + doubleValue;

Então, novamente, tomando o exemplo de um portfólio:

  • O valor de mercado de cada linha do portfólio é um valor monetário e provavelmente seria melhor representado como decimal.

  • O peso de cada linha do portfólio (= Valor de mercado / SUM (Valor de mercado)) geralmente é melhor representado como o dobro.


6

Use um double ou um float quando não precisar de precisão, por exemplo, em um jogo de plataforma que escrevi, usei um float para armazenar as velocidades dos jogadores. Obviamente, não preciso de superprecisão aqui, porque acabo arredondando para um Int para desenhar na tela.


3
A precisão é a ÚNICA vantagem das casas decimais, isso é correto. Você não deve perguntar quando deve usar números de ponto flutuante sobre decimais. Esse deve ser o seu primeiro pensamento. A questão então é quando você deve usar casas decimais (e a resposta está aqui ... quando a precisão é importante).
Instance Hunter

3
@ Daniel Straight, é engraçado, mas tenho a opinião oposta. Eu acho que usar um tipo menos preciso por causa de suas características de desempenho equivale a uma pré-otimização. Você provavelmente terá que pagar por essa pré-otimização várias vezes antes de perceber seu benefício.
265 Michael Meadows

3
@ Michael Meadows, eu posso entender esse argumento. Algo a se notar é que uma das principais reclamações com otimização prematura é que os programadores não tendem a saber o que será lento. Sabemos, sem dúvida, que os decimais são mais lentos que o dobro. No entanto, suponho que na maioria dos casos, a melhoria de desempenho não será perceptível para o usuário. Obviamente, na maioria dos casos, a precisão também não é necessária. Heh.
Instance Hunter

O ponto flutuante decimal é realmente menos preciso que o ponto flutuante binário usando o mesmo número de bits. A vantagem da Decimal é poder representar exatamente frações DECIMAIS como 0,01, comuns no cálculo financeiro.
dan04

Bem, isso não está correto :) - em muitos jogos, os números de ponto flutuante podem ser indesejáveis, devido ao fato de não serem consistentes. Veja aqui
BlueRaja - Danny Pflughoeft

4

Em algumas Contabilidade, considere a possibilidade de usar tipos integrais em vez disso ou em conjunto. Por exemplo, digamos que as regras em que você opera exigem que cada resultado de cálculo seja transportado com pelo menos 6 casas decimais e o resultado final será arredondado para o centavo mais próximo.

Um cálculo de 1/6 de $ 100 produz $ 16.66666666666666 ..., portanto, o valor realizado em uma planilha será $ 16.666667. O dobro e o decimal devem produzir esse resultado com precisão para 6 casas decimais. No entanto, podemos evitar qualquer erro cumulativo, transmitindo o resultado como um número inteiro 16666667. Cada cálculo subsequente pode ser feito com a mesma precisão e transmitido de forma semelhante. Continuando o exemplo, calculo o imposto sobre vendas do Texas nesse valor (16666667 * .0825 = 1375000). Adicionando os dois (é uma planilha curta) 1666667 + 1375000 = 18041667. Mover o ponto decimal de volta fornece 18.041667 ou US $ 18,04.

Embora este pequeno exemplo não produza um erro cumulativo usando o dobro ou o decimal, é bastante fácil mostrar casos em que o simples cálculo do dobro ou decimal e o avanço levariam a acumular um erro significativo. Se as regras sob as quais você opera requerem um número limitado de casas decimais, armazene cada valor como um número inteiro multiplicando por 10 ^ (número necessário de casas decimais) e depois dividindo por 10 ^ (número necessário de casas decimais) para obter o valor real O valor evitará qualquer erro cumulativo.

Nas situações em que frações de centavos não ocorrem (por exemplo, uma máquina de venda automática), não há razão para usar tipos não integrais. Basta pensar nisso como contar centavos, não dólares. Eu vi código em que todos os cálculos envolviam apenas centavos inteiros, mas o uso do dobro levou a erros! Somente matemática inteira removeu o problema. Portanto, minha resposta não convencional é, quando possível, renunciar ao dobro e ao decimal.


3

Se você precisar interropar binário com outros idiomas ou plataformas, poderá ser necessário usar float ou double, que são padronizados.


2

Nota: esta postagem é baseada em informações dos recursos do tipo decimal em http://csharpindepth.com/Articles/General/Decimal.aspx e em minha própria interpretação do que isso significa. Vou assumir que Double é normal precisão dupla IEEE.

Nota 2: menor e maior neste post referem-se à magnitude do número.

Prós de "decimal".

  • "decimal" pode representar exatamente números que podem ser escritos como frações decimais (suficientemente curtas), o dobro não pode. Isso é importante em livros contábeis e similares, onde é importante que os resultados correspondam exatamente ao que um ser humano fazendo os cálculos daria.
  • "decimal" tem uma mantissa muito maior que "double". Isso significa que, para valores dentro do intervalo normalizado, "decimal" terá uma precisão muito maior que o dobro.

Cons de decimal

  • Será muito mais lento (não tenho benchmarks, mas acho que pelo menos uma ordem de magnitude talvez mais), decimal não se beneficiará de nenhuma aceleração de hardware e a aritmética exigirá uma multiplicação / divisão relativamente cara por potências de 10 ( que é muito mais caro que a multiplicação e a divisão por potências de 2) para combinar o expoente antes da adição / subtração e trazer o expoente de volta ao alcance após a multiplicação / divisão.
  • decimal transbordará mais cedo que o dobro. decimal pode representar apenas números até ± 2 96 -1. Por comparação, o dobro pode representar números até quase ± 2 1024
  • decimal ficará abaixo mais cedo. Os menores números representáveis ​​em decimal são ± 10 -28 . Por comparação, o dobro pode representar valores de até 2 -149 (aproximadamente 10 a 45 ) se houver suporte a números subnômicos e 2 a 126 (aproximadamente 10 a 38 ) se não houver.
  • decimal ocupa duas vezes mais memória que o dobro.

Minha opinião é que você deve usar o "decimal" para trabalhar com dinheiro e outros casos em que a correspondência humana é exatamente importante e que você deve usar o dobro da opção padrão pelo resto do tempo.


2

Depende do que você precisa.

Como float e double são tipos de dados binários, você tem algumas diferenças e erros no caminho dos números das rodadas; portanto, por exemplo, double arredondaria 0,1 a 0,100000001490116, o double também arredondaria 1/3 a 0,333333334326441. Simplificando, nem todos os números reais têm representação precisa em tipos duplos

Felizmente, o C # também suporta a chamada aritmética decimal de ponto flutuante, em que os números são representados pelo sistema numérico decimal e não pelo sistema binário. Portanto, a aritmética de ponto flutuante decimal não perde a precisão ao armazenar e processar números de ponto flutuante. Isso o torna imensamente adequado para cálculos em que é necessário um alto nível de precisão.


0

Use pontos flutuantes se você valorizar o desempenho acima da correção.


6
Os números decimais não são mais corretos, exceto em certos casos limitados que às vezes (de maneira alguma sempre) são importantes.
21715 David Thornley

0

Escolha o tipo de função do seu aplicativo. Se você precisa de precisão, como na análise financeira, respondeu à sua pergunta. Mas se o seu aplicativo puder resolver com uma estimativa, você concorda com o dobro.

Seu aplicativo precisa de um cálculo rápido ou ele terá todo o tempo do mundo para lhe dar uma resposta? Realmente depende do tipo de aplicação.

Gráfico com fome? flutuar ou dobrar é suficiente. Análise de dados financeiros, meteoro atingindo um planeta tipo de precisão? Aqueles precisariam de um pouco de precisão :)


8
Números decimais também são estimativas. Eles estão de acordo com as convenções da aritmética financeira, mas não há vantagem em, digamos, cálculos envolvendo física.
22630 David Thornley

0

O decimal tem bytes mais largos, o dobro é suportado nativamente pela CPU. O decimal é base 10, portanto, uma conversão decimal para o dobro ocorre enquanto um decimal é calculado.

For accounting - decimal
For finance - double
For heavy computation - double

Lembre-se de que o .NET CLR suporta apenas Math.Pow (duplo, duplo). Decimal não é suportado.

.NET Framework 4

[SecuritySafeCritical]
public static extern double Pow(double x, double y);

0

Um valor duplo será serializado para notação científica por padrão se essa notação for menor que a exibição decimal. (por exemplo, .00000003 será 3e-8) Os valores decimais nunca serão serializados para notação científica. Ao serializar para consumo por uma parte externa, isso pode ser uma consideração.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.