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.
- Você está agregando valores de ponto flutuante (nesse caso, os erros de precisão são compostos)
- Você constrói valores com base no valor do ponto flutuante (por exemplo, em um algoritmo recursivo)
- 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.