[firebase-br] Problema casas decimais

Sandro Souza escovadordebits em gmail.com
Sex Jul 15 10:43:06 -03 2011


Bom dia/tarde Samuel.

Nobre amigo, o que ocorre é que os tipos de dado Single, Double e Extended
são apenas aproximações de um valor, ou seja, quando você atribui o valor 1
(um), na verdade, foi armazenado 0.999999999999999999999999, que "tende" a
1.

Como você pode notar, é uma "maravilha" quando necessitamos de precisão
numérica, como é o caso dos valores monetários.

É importante lembrar que esses tipos não foram inventados pela Borland. Eles
fazem parte da arquitetura dos processadores que temos hoje em dia, que já
tem instruções, em linguagem de máquina, para esses tipos, e o que a Borland
fez, foi apenas dar suporte a esses tipos nativos.

Isso também quer dizer que esse problema não existe apenas no Delphi, mas em
outras linguagens de programação que suportam esses tipos nativos.

A propriedade "AsFloat", dos campos, é do tipo Double, ou seja, não é
precisa, é uma aproximação numérica (usando logarítmos internamente).

Para resolver essa questão, existe o tipo numérico Currency (moeda), que na
verdade, é um Int64 disfarçado, ou seja, quando você declara uma variável do
tipo Currency, ela é na verdade um Int64.

A questão das 4 casas decimais fixas do tipo Currency é tratada
automaticamente, ou seja, os valores utilizados no tipo Currency são
automaticamente multiplicados ou divididos por 10000 (dez mil) para termos
as tais 4 casas decimais.

Na prática, internamente e de forma transparente p/ nós programadores,
quando se atribui um valor 1 (um) a um Currency, ele fica com o valor 10000
(1 * 10000 = 10000).

Quando efetuamos algum cálculo com o tipo Currency, o valor é dividido por
10000 naquele momento, para que fique tudo coerente, e caso seja armazenado
em alguma variável do tipo Currency, é multiplicado por 10000 p/ ser
armazenado novamente.

Experimente o seguinte:

var
  ValorC: Currency; // Internamente ficou assim: ValorC: Int64;
  ValorI: Int64;
begin
  ValorC := 1; // Internamente ficou assim: ValorC := 10000;
  Move(ValorC, ValorI, SizeOf(ValorI)); // Atribuiu o valor de ValorC à
ValorI na força bruta, sem conversão.
  ShowMessage('ValorI = ' + IntToStr(ValorI)); // Vai exibir 10000.
end;

Como se trata de um Int64 disfarçado, e o intervalo de valores de um Int64 é
de -9.223.372.036.854.775.808 até +9.223.372.036.854.775.807, faz com que o
intervalo de valores de um Currency seja de -922,337.203.685.477,5808 até
+922.337.203.685.477,5807 (a se meu salário fosse esse...).

No caso do Firebird, essa idéia de utilizar internamente valores inteiros
para representar valores decimais existe também, mas a partir do dialeto 3.

Nesse caso, se você criar uma nova base de dados no dialeto 3, ou já tiver
uma base no dialeto 3, internamente tudo funcionará dessa forma, ou seja, se
você criar um campo do tipo NUMERIC(8,4), por exemplo, ficará armazenado
internamente como um Integer, pois um Integer suporta todos os 8 dígitos
tranquilamente. Já um NUMERIC ou DECIMAL com mais de 8 dígitos, passa a ser
armazenado como um Int64.

Se você desejar um Int64 explicitamente, basta criar um campo do tipo BigInt
(http://www.firebirdsql.org/refdocs/langrefupd15-bigint.html).

Note que todos os campos, além de possuirem a propriedade "AsFloat", também
possuem a propriedade "AsCurrency", portanto, eu te aconselho a sempre
utilizar "AsCurrency" no lugar de "AsFloat" para manter o máximo de precisão
numérica.

Isso também vale para as variáveis comuns, ou seja, se é para trabalhar com
valores monetários, procure sempre usar o tipo Currency ao invés dos tipos
de ponto flutuante (Single, Double, Extended e quaisquer outros que, por
ventura, passem a existir).

Com relação ao seu exemplo, experimente criar a mesma tabela em uma base com
dialeto 3, e procure não utilizar a proprieade "Value" dos campos, até
porque, ela é do tipo Variant, o que gera mais código assembly internamente.

Troque a propriedade "Value" por "AsCurrency" e repita o mesmo teste.

Peço desculpas pelos "quilos" de texto, mas sempre tenho essa mania chata de
querer entrar nos detalhes e acabo por me estender demais. :D

Espero ter ajudado mais que atrapalhado. :D

Em 15 de julho de 2011 08:18, Samuel M. Basso <samuelbasso em gmail.com>escreveu:

> Bom dia.
>
> Estou tendo problemas com casas decimais, diferença de centavos, utilizo
> Delphi 7 e Firebird.
>
> Os campos:
> ALQ_IPI NUMERIC(5,3)
> VLR_B_CAL_IPI NUMERIC(11,2)
> VLR_IPI NUMERIC(11,2)
>
> O calculo no delphi:
> ItensVLR_IPI.Value := ItensVLR_B_CAL_IPI.Value * (ItensALQ_IPI.Value /
> 100);
>
> ItensVLR_IPI.Value := 2.857,50 * 0,15
> ItensVLR_IPI.Value = 428,62
>
> Agora se eu fizer na calculadora:
> 2.857,50 * 0,15 = 428,63
>
>
> Diferença de 1(UM) centavo.
>
>
> Alguém tem idéia do motivo do delphi dar essa diferença? Como os campos no
> banco de dados estão com 2 casas decimais o delphi não deveria arredondar?
>
> --
>
>
> Atenciosamente,
>
> *Samuel M. Basso*
> Fone: (54) 3462-5522
> Cel: (54) 8135-3723
> Skype: samuelbasso
> Twitter: @samuelbasso
> E-mail/MSN: samuelbasso em gmail.com
> Web site: www.otimizy.com.br
> ______________________________________________
> FireBase-BR (www.firebase.com.br) - Hospedado em www.locador.com.br
> Para saber como gerenciar/excluir seu cadastro na lista, use:
> http://www.firebase.com.br/fb/artigo.php?id=1107
> Para consultar mensagens antigas: http://firebase.com.br/pesquisa
>



Mais detalhes sobre a lista de discussão lista