Pergunta

Estamos reescrevendo nosso sistema de contabilidade legado em VB.NET e SQL Server.Trouxemos uma nova equipe de programadores .NET/SQL para fazer a reescrita.A maior parte do sistema já está concluída com os valores em Dólares usando Floats.A linguagem do sistema legado em que programei não tinha Float, então provavelmente teria usado Decimal.

Qual é a sua recomendação?

O tipo de dados Float ou Decimal deve ser usado para valores em dólares?

Quais são alguns dos prós e contras de ambos?

Uma desvantagem mencionada em nossa reunião diária foi que você deve ter cuidado ao calcular um valor que retorna um resultado superior a duas casas decimais.Parece que você terá que arredondar o valor para duas casas decimais.

Outra desvantagem é que todas as exibições e valores impressos precisam ter uma declaração de formato que mostre duas casas decimais.Notei algumas vezes que isso não foi feito e os valores não pareciam corretos.(ou seja,10,2 ou 10,2546)

Um profissional é que o Float ocupa apenas 8 bytes no disco, enquanto o Decimal ocuparia 9 bytes (Decimal 12,2)

Foi útil?

Solução

O tipo de dados Float ou Decimal deve ser usado para valores em dólares?

A resposta é fácil.Nunca flutua. NUNCA !

Os carros alegóricos estavam de acordo com IEEE 754 sempre binário, apenas o novo padrão IEEE 754R formatos decimais definidos.Muitas das partes binárias fracionárias nunca podem ser iguais à representação decimal exata.
Qualquer número binário pode ser escrito como m/2^n (m, n inteiros positivos), qualquer número decimal como m/(2^n*5^n).
Como os binários não têm o primo factor 5, todos os números binários podem ser representados exatamente por decimais, mas não vice-versa.

0.3 = 3/(2^1 * 5^1) = 0.3

0.3 = [0.25/0.5] [0.25/0.375] [0.25/3.125] [0.2825/3.125]

          1/4         1/8         1/16          1/32

Então você acaba com um número maior ou menor que o número decimal fornecido.Sempre.

Por que isso importa?Arredondamento.
O arredondamento normal significa 0..4 para baixo, 5..9 para cima.Então é faz importa se o resultado for 0.049999999999....ou 0.0500000000...Você pode saber que isso significa 5 centavos, mas o computador não sabe disso e arredonda 0.4999...para baixo (errado) e 0.5000...para cima (direita).
Dado que o resultado dos cálculos de ponto flutuante sempre contém pequenos termos de erro, a decisão é pura sorte.Fica impossível se você quiser manipulação decimal de arredondamento para par com números binários.

Não convencido ?Você insiste que no seu sistema de contas está tudo perfeitamente bem?
Ativos e passivos iguais?Ok, então pegue cada um dos números formatados de cada entrada, analise-os e some-os com um sistema decimal independente!Compare isso com a soma formatada.
Ops, há algo errado, não é?

Para esse cálculo, foi necessária extrema precisão e fidelidade (usamos o flutuador da Oracle) para que pudéssemos gravar os "bilionésimos de um centavo" está sendo com a força.

Não ajuda contra esse erro.Como todas as pessoas assumem automaticamente que o computador soma certo, praticamente ninguém verifica de forma independente.

Outras dicas

Primeiro você deveria ler isso O que todo cientista da computação deve saber sobre aritmética de ponto flutuante.Então você realmente deveria considerar usar algum tipo de ponto fixo / número de precisão arbitrária pacote (por ex.java BigNum, módulo decimal python), caso contrário você sofrerá muito.Em seguida, descubra se usar o tipo decimal SQL nativo é suficiente.

Existem flutuadores/duplos para expor o rápido x87 fp que agora está praticamente obsoleto.Não os use se você se preocupa com a precisão dos cálculos e/ou não compensa totalmente suas limitações.

Apenas como um aviso adicional, o SQL Server e a estrutura .Net usam um algoritmo padrão diferente para arredondamento.Certifique-se de verificar o parâmetro MidPointRounding em Math.Round().A estrutura .Net usa o algoritmo Bankers por padrão e o SQL Server usa arredondamento algorítmico simétrico.Confira o artigo da Wikipédia aqui

Pergunte aos seus contadores!Eles vão desaprovar você por usar float.Como alguém postado antes, use float SOMENTE se você não se importa com a precisão.Embora eu sempre fosse contra quando se trata de dinheiro.

Em software de contabilidade NÃO é aceitável um float.Use decimal com 4 casas decimais.

Os pontos flutuantes têm números irracionais inesperados.

Por exemplo, você não pode armazenar 1/3 como decimal, seria 0,3333333333...(e assim por diante)

Na verdade, os flutuadores são armazenados como um valor binário e uma potência de 2 expoentes.

Portanto, 1,5 é armazenado como 3 x 2 elevado a -1 (ou 3/2)

Usar esses expoentes de base 2 cria alguns números irracionais ímpares, por exemplo:

Converta 1.1 em float e depois converta-o novamente, seu resultado será algo como:1.099999999989

Isso ocorre porque a representação binária de 1,1 é na verdade 154811237190861 x 2 ^ -47, mais do que um double pode suportar.

Mais sobre esse assunto em meu blog, mas basicamente, para armazenamento, é melhor usar decimais.

No servidor Microsoft SQL você tem o money tipo de dados - geralmente é melhor para armazenamento financeiro.Tem precisão de 4 casas decimais.

Para cálculos, você tem mais problemas - a imprecisão é uma pequena fração, mas coloque-a em uma função de potência e ela rapidamente se tornará significativa.

No entanto, os decimais não são muito bons para nenhum tipo de matemática - não há suporte nativo para potências decimais, por exemplo.

Use o servidor SQL decimal tipo.

Não use dinheiro ou flutuador.

dinheiro usa 4 casas decimais, é mais rápido do que usar decimal MAS sofre de alguns problemas óbvios e outros não tão óbvios com arredondamento (veja este problema de conexão)

O que eu recomendo é usar números inteiros de 64 bits que armazenem tudo em centavos.

Um pouco de contexto aqui....

Nenhum sistema numérico pode lidar com todos os números reais com precisão.Todos têm suas limitações, e isso inclui o ponto flutuante padrão IEEE e o decimal assinado.O ponto flutuante IEEE é mais preciso por bit usado, mas isso não importa aqui.

Os números financeiros baseiam-se em séculos de prática de papel e caneta, com convenções associadas.Eles são razoavelmente precisos, mas, mais importante, são reproduzíveis.Dois contadores trabalhando com vários números e taxas devem chegar ao mesmo número.Qualquer espaço para discrepância é espaço para fraude.

Portanto, para cálculos financeiros, a resposta certa é aquela que dá a mesma resposta que um contador que é bom em aritmética.Esta é aritmética decimal, não ponto flutuante IEEE.

A única razão para usar o Float por dinheiro é se você não se importa com respostas precisas.

Os floats não são representações exatas; problemas de precisão são possíveis, por exemplo, ao adicionar valores muito grandes e muito pequenos.É por isso que os tipos decimais são recomendados para moeda, mesmo que o problema de precisão possa ser suficientemente raro.

Para esclarecer, o tipo decimal 12,2 armazenará exatamente esses 14 dígitos, enquanto o float não, pois usa uma representação binária internamente.Por exemplo, 0,01 não pode ser representado exatamente por um número de ponto flutuante - a representação mais próxima é na verdade 0,0099999998

Num sistema bancário que ajudei a desenvolver, fui responsável pela parte de “acréscimo de juros” do sistema.A cada dia, meu código calculava quantos juros haviam sido acumulados (ganhos) no saldo daquele dia.

Para esse cálculo, era necessária extrema precisão e fidelidade (usamos o FLOAT da Oracle) para que pudéssemos registrar o "bilionésimo de um centavo" sendo acumulado.

Quando se tratou de “capitalizar” os juros (ou seja,pagando os juros de volta em sua conta) o valor foi arredondado para um centavo.O tipo de dados para os saldos das contas era de duas casas decimais.(Na verdade, era mais complicado, pois era um sistema multimoedas que podia funcionar com muitas casas decimais - mas sempre arredondávamos para o "centavo" dessa moeda).Sim - havia "frações" de perdas e ganhos, mas quando os números dos computadores eram atualizados (dinheiro pago ou pago), eram sempre valores monetários REAIS.

Isso satisfez os contadores, auditores e testadores.

Portanto, verifique com seus clientes.Eles lhe dirão suas regras e práticas bancárias/contábeis.

Ainda melhor do que usar decimais é usar apenas números inteiros antigos (ou talvez algum tipo de bigint).Desta forma você sempre terá a maior precisão possível, mas a precisão pode ser especificada.Por exemplo o número 100 poderia significar 1.00, que está formatado assim:

int cents = num % 100;
int dollars = (num - cents) / 100;
printf("%d.%02d", dollars, cents);

Se quiser ter mais precisão, você pode alterar 100 para um valor maior, como:10 ^ n, onde n é o número de decimais.

Outra coisa que você deve estar ciente nos sistemas contábeis é que ninguém deve ter acesso direto às tabelas.Isso significa que todo acesso ao sistema de contabilidade deve ser feito por meio de processos armazenados.Isso evita fraudes e não apenas ataques de injeção de SQL.Um usuário interno que deseja cometer fraude nunca deve ter a capacidade de alterar diretamente os dados nas tabelas do banco de dados.Este é um controle interno crítico em seu sistema.Você realmente quer que algum funcionário insatisfeito vá até o back-end do seu banco de dados e faça com que ele comece a emitir cheques?Ou esconder que aprovaram uma despesa para um fornecedor não autorizado quando não têm autoridade de aprovação?Apenas duas pessoas em toda a sua organização devem poder acessar diretamente os dados do seu banco de dados financeiro, do seu dba e do backup dele.Se você tiver muitos dbas, apenas dois deles deverão ter esse acesso.

Menciono isso porque se seus programadores usaram float em um sistema de contabilidade, provavelmente não estão familiarizados com a ideia de controles internos e não os consideraram em seu esforço de programação.

Você sempre pode escrever algo como um tipo Money para .Net.

Dê uma olhada neste artigo: Um tipo Money para o CLR - O autor fez um excelente trabalho na minha opinião.

Eu estava usando o tipo money do SQL para armazenar valores monetários.Recentemente, tive que trabalhar com vários sistemas de pagamento online e percebi que alguns deles usam números inteiros para armazenar valores monetários.Em meus projetos atuais e novos, comecei a usar números inteiros e estou bastante satisfeito com esta solução.

Das 100 frações n/100, onde n é um número natural tal que 0 <= n e n <100, apenas quatro podem ser representadas como números de ponto flutuante.Dê uma olhada na saída deste programa C:

#include <stdio.h>

int main()
{
    printf("Mapping 100 numbers between 0 and 1 ");
    printf("to their hexadecimal exponential form (HEF).\n");
    printf("Most of them do not equal their HEFs. That means ");
    printf("that their representations as floats ");
    printf("differ from their actual values.\n");
    double f = 0.01;
    int i;
    for (i = 0; i < 100; i++) {
        printf("%1.2f -> %a\n",f*i,f*i);
    }
    printf("Printing 128 'float-compatible' numbers ");
    printf("together with their HEFs for comparison.\n");
    f = 0x1p-7; // ==0.0071825
    for (i = 0; i < 0x80; i++) {
        printf("%1.7f -> %a\n",f*i,f*i);
    }
    return 0;
}

Você já pensou em usar o tipo de dados monetários para armazenar valores em dólares?

Em relação ao Con que decimal ocupa mais um byte, eu diria que não me importo com isso.Em 1 milhão de linhas você usará apenas 1 MB a mais e o armazenamento é muito barato hoje em dia.

Faça o que fizer, você precisa ter cuidado com erros de arredondamento.Calcule usando um grau de precisão maior do que o exibido.

Você provavelmente desejará usar alguma forma de representação de ponto fixo para valores monetários.Você também vai querer investigar o arredondamento do Banker (também conhecido como "arredondamento pela metade par".) Ele evita preconceitos que existem no método usual de "arredondamento pela metade para cima".

Seus contadores vão querer controlar como você arredonda.Usar float significa que você estará constantemente arredondando, geralmente com uma instrução do tipo FORMAT(), que não é a maneira que você deseja fazer (em vez disso, use piso/teto).

Você tem tipos de dados de moeda (money, smallmoney), que devem ser usados ​​em vez de float ou real.Armazenar decimal (12,2) eliminará seus arredondamentos, mas também os eliminará durante as etapas intermediárias - o que realmente não é o que você deseja em uma aplicação financeira.

Sempre use Decimal.Float fornecerá valores imprecisos devido a problemas de arredondamento.

Números de ponto flutuante podem apenas representam números que são uma soma de múltiplos negativos da base - para ponto flutuante binário, é claro, são dois.

Existem apenas quatro frações decimais representáveis ​​precisamente em ponto flutuante binário:0, 0,25, 0,5 e 0,75.Todo o resto é uma aproximação, da mesma forma que 0,3333...é uma aproximação para 1/3 em aritmética decimal.

O ponto flutuante é uma boa escolha para cálculos onde a escala do resultado é o que importa.É uma má escolha quando você tenta ter precisão de algumas casas decimais.

Este é um excelente artigo que descreve quando usar float e decimal.Float armazena um valor aproximado e decimal armazena um valor exato.

Em resumo, valores exatos como dinheiro devem usar decimal, e valores aproximados como medições científicas devem usar float.

Aqui está um exemplo interessante que mostra que tanto float quanto decimal são capazes de perder precisão.Ao adicionar um número que não é um número inteiro e depois subtrair esse mesmo número, float resulta na perda de precisão, enquanto decimal não:

    DECLARE @Float1 float, @Float2 float, @Float3 float, @Float4 float; 
    SET @Float1 = 54; 
    SET @Float2 = 3.1; 
    SET @Float3 = 0 + @Float1 + @Float2; 
    SELECT @Float3 - @Float1 - @Float2 AS "Should be 0";

Should be 0 
---------------------- 
1.13797860024079E-15

Ao multiplicar um número não inteiro e dividir por esse mesmo número, os decimais perdem a precisão, enquanto os números flutuantes não.

DECLARE @Fixed1 decimal(8,4), @Fixed2 decimal(8,4), @Fixed3 decimal(8,4); 
SET @Fixed1 = 54; 
SET @Fixed2 = 0.03; 
SET @Fixed3 = 1 * @Fixed1 / @Fixed2; 
SELECT @Fixed3 / @Fixed1 * @Fixed2 AS "Should be 1";

Should be 1 
--------------------------------------- 
0.99999999999999900
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top