Eu costumo utilizar inteiro para representar monetário em grandes somas. Mas
quando entra multiplicações e divisões me parece que fica problemático, pois
os resultados ficam truncados.
Se eu criar uma classe monetário (ainda não tempo de analisar nenhuma destas
classes de biblioteca), acredito que possa garantir que estas operações
sejam sempre arredondadas. Mas e se eu quiser que o arredondamento (ou
truncamento) ocorra só no final da operação? Teria que converter os
operadores de monetário para double, depois o resultado (double) para
monetário (que o deveria arredondar).
Costumo resolver tudo utilizando 'double' e minhas funções de
arredondamento, até para comparações:
if (round( x - y ) >= 0) ...
// equivale a if ( x >= y ) ...
> -----Mensagem original-----
> De: Thiago A.
>
>
> Uma outra observação importante, é que em aplicações monetárias
> sérias, não é bom usar double para representar valores monetários, ao
> invés disso pode-se usar inteiro que representam todos os centavos...
> Neste caso vale a pena criar o tipo.
>
Espero que meu banco não use ponto flutuante...
--
Felipe Magno de Almeida
On 7/23/09, Márcio Gil <marci...@bol.com.br> wrote:
>
> Esta é outra questão muito interessante. Por isso eu criei uma discussão
> separada.
>
> Eu costumo utilizar inteiro para representar monetário em grandes somas. Mas
> quando entra multiplicações e divisões me parece que fica problemático, pois
> os resultados ficam truncados.
>
O problema tambem ocorre com pequenos valores. Ate' 0.1 nao tem
uma representacao exata em binario fracional.
> Se eu criar uma classe monetário (ainda não tempo de analisar nenhuma destas
> classes de biblioteca), acredito que possa garantir que estas operações
> sejam sempre arredondadas. Mas e se eu quiser que o arredondamento (ou
> truncamento) ocorra só no final da operação? Teria que converter os
> operadores de monetário para double, depois o resultado (double) para
> monetário (que o deveria arredondar).
>
Nada impede que voce guarde os centavos em um double. Ao
contrario de float, um double pode representar exatamente todos os
inteiros de 32 bits, ainda mais, se nao me engano ate' 53 bits.
--
-alex
http://www.ventonegro.org/
Thiago, como você faria com inteiros uma operação como essa:
x = 200.0 / 3.0;
std:cout << x;
// 66.66666666666666666666
// O resultado correto seria 66.67
já que 20000 / 300 = 66, ou 20000 * 100 / 300 = 6666 (66.66);
seria então:
x = 20000 * 1000 / 300;
x = (x%10<5)? x/10: x/10+1;
// logo x = 6667 (66.67)
??
E a questão de que certos cálculos deveriam ser arredondados só no final?
> Por exemplo, se fizer uma compra de 100 reais no cartão de crédito em
> 3 prestações, daria 33.33333... cada.
>
> Então o programa do banco tem que ser esperto para colocar uma
> prestação de 34 reais e duas de 33. Ou 33.30 33.30 e outra de 33.40.
> Se fosse arredondar todas para 33.30 o banco perderia 10 centavos
> neste caso.
>
Curioso, recentemente eu criei uma função para isto:
http://cpp.pastebin.com/f5e3bbf18
http://cpp.pastebin.com/f79f794bb
> -----Mensagem original-----
> De: Márcio Gil
http://www.di-mare.com/adolfo/p/money.htm
Só que ela utiliza 'double' para representar o número...
e um método 'FIX' muito parecido com o meu 'round'.
Tentei entender esta aqui:
http://software.intel.com/en-us/articles/intel-decimal-floating-point-math-l
ibrary/
Mas parece ser complexa demais...
Esperimenta colocar "=200/3" no Excel e configurar para duas casas
decimais... o resultado vai ser 66,67.
Já a questão de quando fazer o arredondamento vou explicar. Imagina que em
uma operação arbitrária (não estou falando de parcelamento) tenho a fórmula
f(x,y) = x/3 * y;
Se for calcular f(200,3) e o arredondamento ocorrer em cada operação (porque
estou utilizando uma classe Currency) tenho:
f(200,3) = 66.67 * 3;
f(200,3) = 200.01;
Viu e erro? Se o arredondamento fosse feito apenas no final da operação,
então o resultado seria:
f(200,3) = 200,00.
A questão que eu quero levantar é sobre operações que necessitam de maior
precisão durante o cálculo, e só precisam ser arredondados no final. Um
exemplo prático: numa soma de sub-totais, cada sub-total precisa ser
arredondado e a soma total deve utilizar os sub-totais exatamente como
aparecem, depois de uma série de cálculos com descontos, juros, acréscimos
ou seja lá o que for.
Também estou fazendo uma agora, com int64. Só que vou trabalhar com
duas precisões, uma para arredondamento/truncamento explícito, e
outra para garantir precisão nos cálculos, ex: 2 casas decimais e 4
casas decimais. Então no exemplo seria 2000000 * 10000 / 30000 =
666666, arredondado: 666700.
Depois a gente vê como ficou.
Também fiz minha classe:
http://cpp.pastebin.com/f36307b21
O número de casas decimais e a precisão são determinados pelos parâmetros de
template. Então:
Currency<2,4> x(2.3456);
// Trabalha com 2 casas decimais (ao arredondar)
// e até 4 dígitos de precisão
x.round();
// x = 2.3500
Outra coisa que eu fiz foi permitir converter valores entre instâncias
diferentes da classe:
Currency<4,5> pi(3.141592);
Currency<2,4> a(pi); // 3.1415
pi.round();
Currency<2,4> b(pi); // 3.1416
Mas ainda tem muito o que melhorar. Especialmente o métodos e a funções
'round' e 'trunc'.
http://cpp.pastebin.com/f48a9dafa
No teste eu faço a composição de várias frações 1/1..9/9;
O resultado que obtive com o teste foi este:
Teste sem simplificação...
[24] Falhou: 242351120793600/15801827328000 + 3/6 =
1501512206745600/94810963968000 ???
[25] Falhou: 1501512206745600/94810963968000 + 4/1 =
1880756062617600/94810963968000 ???
[26] Falhou: 1880756062617600/94810963968000 + 5/1 =
2354810882457600/94810963968000 ???
[33] Falhou: 2698943078006784000/68263894056960000 + 6/7 =
855440836679696384/477847258398720000 ???
[38] Falhou: 3309679782769197056/-6978409872140271616 + 7/5 =
4593017956283187200/2001438786717745152 ???
[41] Falhou: 1880334572663603200/4002877573435490304 + 8/3 =
770536158055628800/-6438111353403080704 ???
[47] Falhou: 6494668570054623232/2189888687431483392 + 9/6 =
3336777386082435072/-5307411949120651264 ???
Resultado: 3336777386082435072/-5307411949120651264 = -0.628701
Teste com simplificação...
[48] Falhou: 3336777386082435072/-5307411949120651264 + 1/1 =
14682371639/39543300488 ???
[49] Falhou: 14682371639/39543300488 + 2/1 = 93768972615/39543300488 ???
[52] Falhou: 271713824811/39543300488 + 3/3 = 311257125299/39543300488 ???
[54] Falhou: 469430327251/39543300488 + 4/2 = 548516928227/39543300488 ???
[55] Falhou: 548516928227/39543300488 + 5/1 = 746233430667/39543300488 ???
[56] Falhou: 746233430667/39543300488 + 6/1 = 983493233595/39543300488 ???
[58] Falhou: 1260296337011/39543300488 + 7/2 = 1398697888719/39543300488 ???
[62] Falhou: 5935998887629/118629901464 + 8/4 = 6173258690557/118629901464
???
[65] Falhou: 7774762360321/118629901464 + 9/3 = 8130652064713/118629901464
???
Resultado: 8130652064713/118629901464 = 68.538
Então eu tentei criar um limite de segurança, para evitar os estouros:
http://cpp.pastebin.com/f2fd3fd61
Mas não funcionou tão bem:
Teste sem simplificação...
[9] Falhou: 109584/40320 + 1/9 = 1026576/362880 ???
[13] Falhou: 14142816/2177280 + 2/4 = 60925824/8709120 ???
[15] Falhou: 87053184/8709120 + 3/2 = 200233728/17418240 ???
[18] Falhou: 609486336/34836480 + 4/3 = 1967804928/104509440 ???
[20] Falhou: 2490352128/104509440 + 5/2 = 66349/2520 ???
[25] Falhou: 2348376/60480 + 6/5 = 12104760/302400 ???
[27] Falhou: 14221560/302400 + 7/2 = 30559920/604800 ???
[32] Falhou: 975358080/14515200 + 8/5 = 34673/504 ???
[38] Falhou: 5403624/60480 + 9/6 = 32966064/362880 ???
Resultado: 32966064/362880 = 90.8456
Teste com simplificação...
Resultado: 68717/315 = 218.149
Fiz um teste com a BC, para ver qual seria o resultado:
[marcio@localhost cpp]$ bc -l
bc 1.06
Copyright 1991-1994, 1997, 1998, 2000 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
define f()
{
x = y = s = 0;
for (x = 1; x < 10; ++x) {
for (y = 1; y < 10; ++y) {
s = s + (x/y);
}
}
return s;
}
f()
127.30357142857142857129
Na segunda versão você perde metade da precisão. Alguém da lista talvez nos
dê uma luz.
> -----Mensagem original-----
> De: Márcio Gil
>
...
std::cout << "class Currency (" << sizeof(x)
<< " bytes) long long: " << sizeof(long long) << std::endl;
...
[marcio@localhost cpp]$ g++ currency.cpp
[marcio@localhost cpp]$ ./a.exe
class Currency (8 bytes) long long: 8
x = 1349.93
y = 66.67
z = 4.5
pi = 3.1416
[marcio@localhost cpp]$ g++ --version
g++ (GCC) 3.4.4 (cygming special, gdc 0.12, using dmd 0.125)
Copyright (C) 2004 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Thiago, seguindo seus passos, eu melhorei um pouco minha classe Currency:
http://cpp.pastebin.com/f55150720
Acrescentei no final o mesmo teste que fiz na sua classe:
[marcio@localhost cpp]$ g++ currency.cpp && ./a.exe
class Currency (8 bytes) long long: 8
x = 1349.93
y = 66.67
z = 4.5
pi = 3.1416
s = 127.303571414
O resultado com 7 dígitos decimais (9 de precisão) foi bem próximo da que
encontrei na BC (127.30357142857142857129). Com 8 dígitos não funciona.
Talvez os colegas possam me ajudar a melhorar meu código ou sugerir algum
outro.
Fiz o seguinte teste com esta classe:
http://cpp.pastebin.com/f2b1df615
Resultado: 7129/56 = 127.3035714285714306015506736
Resulta da BC: 127.30357142857142857129
Resultado excelente, a diferença se deve por ter alcançado o limite
do tipo double, na hora de exibir o resultado. Só não sei se há
necessidade de utilizar uma classe destas para valores monetários...
talvez se acrescentar um método para arredondamento, outro para
converter para formato decimal (para ser exibido ou armazenado num
BD), e não sendo crítico o espaço extra utilizado ou os cálculos a
mais não pesem no desempenho, e se não houver problemas em utilizar
boost no projeto... pode ser uma solução.
http://cpp.pastebin.com/f2cfe4bf8
Para compilar você deve desabilitar o operador << do rational.hpp
(ou substituir pela versão do teste). Dá para ver que foi possível
encontrar o valor exato da conta:
Resultado: 7129/56 = 127.303571428571428571428571428571
Resultado na BC: 127.303571428571428571428571428558
Que é uma dízima periódica: 127.303(571428...)
na BC eu refiz o teste com mais precisão:
scale=30
define f()
{
x = y = s = 0;
for (x = 1; x < 42; ++x) {
for (y = 1; y < 42; ++y) {
s = s + (x/y);
}
}
return s;
}
f()
127.303571428571428571428571428558
A BC faz todos os cálculos em base decimal, garantindo precisão
máxima.
Por curiosidade, refiz o teste com laços maiores. Até 41 tudo bem:
Resultado: 85205313628946333/24286052077560 =
3508.405291927828308943653598301207
Na BC: 3508.40529192782830894365359830[0711]
(Entre colchetes o que difere.)
Já com 42 começou a dar problema:
[Marcio@LOCALHOST cpp]$ g++ rational_boost.cpp -I . && ./a.exe
[462] Falhou: 1059259625276477383/3319093783933200 + 12/11 =
-6755059070261102003/36510031623265200 ???
...
Resultado: -8183533653110063613/219060189739591200 =
-37.357466287408390269105828950068
Na BC: 3704.825556524219724390836278215738
Teste que falhou:
http://cpp.pastebin.com/f41bdffb6
Desta forma retiro o que disse, mesmo a rational do boost não é
totalmente segura para utilizar em cálculos monetários, só mesmo
para algum programas de teste. Continuo com a minha classe currency
ou outra que vierem a sugerir.
Márcio Gil.
Pedro, estamos falando de representar um número com 2 casas decimais
em inteiro.
Exemplo: 123,45 fica 12345.
a conta:
123,45 x 0,50 = 61,725
seria feita assim:
(12345 x 50) / 100 = 6172
(o 5 foi truncado pois estamos trabalhando sempre com 2 casa
decimais neste exemplo).
O programa teria que verificar se (12345 x 50) % 100 >= 50 e somar
um para arredondar.
Por isso eu optei por trabalhar com duas casas a mais (ou quantas
desejar) na minha biblioteca Currency. O arredondamento seria
explícito (só quando necessário), depois dá uma olhada:
Como o título da discussão é "Trabalhando com valores monetários",
então estamos falando de dinheiro, não de cálculos científicos ou
outra coisa.
> Algumas vezes não vai fazer sentido usar inteiros, se for uma
> conta que use raiz quadrada por exemplo ou um numero elevado a uma
> fração etc.. que é o calculo de juros.
>
> Se fosse contas de somar dividir etc.. acho que um inteiro 64 bits
> pode ser suficiente para a maioria dos casos.
>
> Se fosse para fazer uma conta exata, que indica o extrato do
> banco, eu usaria um numero de precisão infinita.
Mas um saldo bancário é justamente um caso em que *não* se usa
precisão infinita... Você nunca vai tirar um extrato e verificar que
tem 3,1415926535897932384626433832795028 reais na sua conta.
Cálculo com valores monetários sempre trabalha com um número fixo de
casas decimais, pode ser 2 casas para representar os centavos, as
vezes pode ser necessário trabalhar com 3 ou 4 casas (tal como em
preços de combustíveis), mas sempre vai haver uma regra bem definida
de quantas casas utilizar e se os resultados são truncados ou
arredondados.
> Se fosse para dividir e somar números grandes, também eu usaria
> uma numero racional com dois inteiros de precisão infinita e
> acabava com o problema de arredondamento. Mas a questão é? Faz
> diferença tudo isso para o caso específico?
>
Como eu demonstrei, trabalhar com racionais não é totalmente seguro,
de repente um cálculo simples, como somar 1/7 pode estourar a
capacidade do numerador ou do denominador. Talvez até consigamos
melhorar aquela classe, mas não é necessário precisão absoluta ao se
trabalha com valores monetários.
> Por isso, usar um int 64 e tratar cada caso me parece a solução
> ideal.
>
> Marcio,
> Eu não entendi qual é o objetivo da classe Currency, poderia
> explicar com palavras que problema ela quer resolver?
> Já tem o contexto aonde quer usar?
> Por que ela é melhor que um double ou um int 64 cru ?
>
1 - O problema que eu quero responder é esse: se não é seguro
trabalhar com 'double' para cálculos de valores *monetários* (ou
seja, dinheiro), então qual é a maneira mais adequada.
NOTA: eu mesmo já encontrei problemas ao se fazer somas com
'double', e por isso faço estes somatórios com int64. Para pequenos
cálculos, que não envolvam muitas operações, ainda não tive
problemas, mas é bom ver se realmente há melhores alternativas.
2 - Decidi verificar se é viável trabalhar com int64 para cálculos
com 2 casas decimais. Verifiquei que manter os números com 2 casas
gera resultados sempre truncados (ver a discussão nas mensagens
anteriores).
3 - Poderia então verificar o resto da operação com 2 casas decimais
para arredondar os resultados, mas em muitos casos não é desejável
arredondar a cada operação. Então tive a idéia de trabalhar com
determinada precisão para os cálculos e outra precisão para o
arredondamento, que seria feito apenas quando necessário.
4 - A minha proposta então é criar uma classe template que trabalhe
com determinada precisão nos cálculos e permita arredondar ou
truncar se desejado. Exemplo:
// Tipo Currency com 2 casas decimais no arredondamento e 4 casas
// decimais de precisão.
Currency<2,4> x, y, z;
x = 200.0;
y = x / 3.0; // y = 66.6666
z = round(y); // z = 66.6700, y = 66.6666
y.round(); // y = 66.6700
Então é isso. Se alguém tiver alguma sugestão, ou recomendar uma
biblioteca já pronta, seria ótimo.
Conceitualmente:
mul( A, B ): X = A * B
sendo A, B e X no formato IIIIIIDD
Manter o resultado X no formato IIIIIIDD pode gerar truncamento (ou
"arredondar para baixo" se preferir) caso utilize a fórmula mínima
para isso, já que uma multiplicação simples resulta em X no formato
IIIIDDDD.
Como esta operação é feita é detalhe de implementação. Pode ser
feita assim: (A*X+50)/100 e nós temos um arredondamento no lugar de
um truncamento.
Outro detalhe é que centavos multiplicado por centavos é centavos ao
quadrado. Dinheiro você multiplica por uma quantidade ou por uma
determinada taxa.
> -----Original Message-----
> From: Pedro Lamarão
>
On 7/28/09, Márcio Gil <marci...@bol.com.br> wrote:
>
> Seguindo sua definição então posso dizer que o tipo 'double' é um
> número inteiro composto de normalmente 64 dígitos binários (ou 52
> dígitos binários significativos), e qualquer perda de informação,
> que não seja overflow, decorrente de multiplicações é uma "ilusão de
> ótica".
>
A analogia esta' totalmente incorreta. Um variavel double e' de
ponto flutuante, valores monetarios sao de ponto fixo. A melhor forma
de armazena-los e' realmente como um inteiro.
--
-alex
http://www.ventonegro.org/
O ponto decimal é flutuante mas o número de dígitos significativos é
fixo. Então o problema de perda de informação ao multiplicar é
similar.
Nao, porque inteiros nao perdem precisao quando multiplicados.
--
-alex
http://www.ventonegro.org/
Então você se contradiz, pois números fracionários, sejam de ponto
fixo ou flutuante, podem perder precisão se multiplicados.
Logo não devo acreditar em meus olhos? É uma pena que esta discussão
tenha desandado para: "quem veio primeiro? o ovo ou a galinha?".
On 7/28/09, Márcio Gil <marci...@bol.com.br> wrote:
>
> Então você se contradiz, pois números fracionários, sejam de ponto
> fixo ou flutuante, podem perder precisão se multiplicados.
>
Nao, eu nao me contradisse. O que eu quis dizer e' que na
implementacao de ponto flutuante usada na maioria dos computadores,
ate' mesmo 0.1 (10 centavos) nao e' guardado de forma exata. A melhor
forma de guardar esses numeros e' como um inteiro que representa o
numero de centavos, *o que pode ser visto como uma forma de numero de
ponto fixo*.
Em um grande numero de calculos (juros, descontos etc.) serao
necessarios mais de duas casas de precisao, o que esse tipo de dado
nao da'. Ai' e' que entram os varios casos especiais e
arrendondamentos. Mas seriam inevitaveis em qualquer caso, mesmo
usando tipos de dados decimais de precisao arbritraria.
--
-alex
http://www.ventonegro.org/
On 7/28/09, Thiago A. <thiago...@gmail.com> wrote:
>
> Só por curiosidade, em 2008, PIB do Brasil atingiu R$ 2,9 trilhões.
>
> Ou 2.900.000.000.000
>
> Se contar os centavos seria 290.000.000.000.000 centavos.
>
> Então um inteiro de 32 bits não seria sufiente para somar este valor.
> (de –2.147.483.648 até 2.147.483.647)
>
> Mas um inteiro de 64 bits seria mais que suficiente. Daria para somar
> um PIB igual por 31 anos.
> de –9.223.372.036.854.775.808 até 9.223.372.036.854.775.807
>
>
> Vamos supor que todo o PIB fosse distribuído entre os brasileiros.
>
> 290.000.000.000.000 / 183.900.000
> daria 1576943 centavos para cada ou (R$ 15.769,43)
>
> e sobraria 182300000 centavos. Entao ficaria de credito 182300000 /
> 183900000 ou 1823 / 1839 centavos para cada brasileiro.
> Faltaria mais 1.600.000 para fechar 1 centavo para cada um.
>
Muito interessante. Esse e' um assunto complexo. Como quero
comecar um negocio, vou fazer um componente COM e vender pelos olhos
da cara. :)
--
-alex
http://www.ventonegro.org/
Veja bem, se compro 150 gramas de jujubas a R$ 1,75 por 100 gramas,
na verdade o preço é R$ 17,50 o quilo e estou comprando 0,150Kg.
Provavelmente aparecerá na registradora R$ 2,62 a pagar (ou 2,63 se
estiver configurado para arredondamento, o que é possível), então
houve meio centavo de perda neste item. Provavelmente a perda do
cliente será de dois centavos e meio pois geralmente o comércio não
tem moedas de um centavo e efetua o truncamento do troco, quando na
verdade, de acordo com os direitos do consumidor, deveria ocorrer o
truncamento do valor a pagar se o comerciante não tem como dar o
troco exato.
Se no mesmo comércio eu compro meia barra de chocolate a R$ 3,45, a
nota fiscal vai aparecer assim:
------------------------------------------------
CUPOM FISCAL
ITEM CÓDIGO DESCRIÇÃO QTD.UN.VL_UNIT( R$) ST VL_
ITEM( R$)
------------------------------------------------
001 1 Jujuba
0,15 X 17,50 T12,00% 2,62g
002 2 Barra de chocolate
0,5 X 3,45 T12,00% 1,72g
TOTAL R$ 4,34
Dinheiro 5,00
TROCO R$ 0,66
------------------------------------------------
(sim, eu utilizei um simulador de cupom fiscal. O safado do
comerciante me deu 60 centavos de troco e não quis fazer confusão
por causa de 6 centavos, embora tenha dado vontade :-))
Eu não somo 2,625 com 1,725, eu somo 1,62 com 1,72. Então sim,
existe perda de informação com esta conta. Não existe perda de
informação se trabalho com quantidades inteiras, como em uma loja de
informática, mas com quantidades fracionárias sim. A não ser que me
diga que isto é ilusão de ótica e que não devo acreditar em meus
olhos...
> -----Original Message-----
> From: Alex Queiroz
>
> Não, eu não me contradisse. O que eu quis dizer e' que na
> implementação de ponto flutuante usada na maioria dos
computadores,
> ate' mesmo 0.1 (10 centavos) não e' guardado de forma exata.
>
A questão não é esta, esta perda de informação se deve a diferença
de representação em bases diferentes. Se o computador trabalhasse
com base 3, 0.1(base3) não teria representação exata em base
decimal: 0.3333...(base10).
Este mesmo número em base binária é assim:
1.0101010101010101010101010101010101010101010101010101*2^-2
se multiplico por 0.5 (0.1(base2)):
0.1010101010101010101010101010101010101010101010101010*2^-2
normalizando:
1.0101010101010101010101010101010101010101010101010100*2^-3
Então houve perda de precisão. De forma similar 1,23 * 0.50 = 0,24
em ponto decimal fixo com 2 casas, só que aqui estou trabalhando em
base 10.
http://www.gnu.org/software/gsl/manual/html_node/Representation-of-f
loating-point-numbers.html
On 7/28/09, Márcio Gil <marci...@bol.com.br> wrote:
>
> >
> A questão não é esta, esta perda de informação se deve a diferença
> de representação em bases diferentes. Se o computador trabalhasse
> com base 3, 0.1(base3) não teria representação exata em base
> decimal: 0.3333...(base10).
>
Sim, eu sei que se deve `a diferenca de base. E como eu disse no
email anterior, eu reconheco que tambem ha' perda de precisao com os
numeros de ponto fixo. Voce tem razao. Mas meu argumento era que
perdendo por perdendo, usar inteiros mantem a precisao para mais casos
comuns do que usando um double.
--
-alex
http://www.ventonegro.org/
> Eu admito prontamente que, ao apresentar uma conta ao cliente, é
> errôneo que ela contenha um valor como 2,625.
> (Exceto se você é uma bomba de gasolina.)
>
> Porém, insistirei uma última vez que a aritmética necessária para
> implementar a conta do exemplo dentro do sistema nunca deve causar
> truncamento:
>
> 0,15 x R$ 17,50 <=> 15/100 x R$ 1750/100 <=> R$ 26250/10000
> +
> 0,5 x R$ 3,45 <=> 50/100 x R$ 345/100 <=> R$ 17250/10000
> =
> R$ 43500/10000 <=> R$ 435/100 <=> R$ 4.35
>
> um número totalmente exato.
I) Quais as consequências de seguir esta lógica que você diz?
A - Seria necessário armazenar a informação de número de casas
decimais no objeto.
class Numeric {
unsigned short int m_decimal;
long long int m_value;
...
B - O operador multiplicação poderia retornar um tipo diferente dos
parâmetros.
template <unsigned DECIMAL>
class Numeric {
long long int m_value;
...
template <undigned N, unsigned M>
Numeric<N+M> operator*( const Numeric<N>& left,
const Numeric<M>& right )
{...
Tanto na opção A quanto na B, após algumas operações de
multiplicação poderíamos ter umas 19 casas decimais e então uma
operação simples geraria overflow. É claro que na opção A a classe
pode ser esperta o suficiente para impedir tal desastre, mesmo que a
custa de perder precisão.
Quais as vantagens desta precisão máxima em aplicações monetárias?
Não muita, certamente nenhuma. Talvez isto seja útil em aplicações
matemáticas. No entanto temos uma classe mais complexa e mais gorda,
sem necessidade.
O que quero mostrar é que uma classe com um número fixo de casas
decimais é mais do que suficiente para aplicações monetárias. Pode
ser necessário um pouco mais de precisão para efetuar sub-operações,
como de taxas e quantificação, mas no final da operação as regras de
negócio vão determinar a ocorrência de arredondamento ou truncagem
em N casas decimais.
II) Quais as consequências de se trabalhar com um número fixo de
casas decimais, tanto nos operadores quanto no resultado?
R: Vai haver um truncamento ou arredondamento involuntariamente.
Isto explica o mal entendido: você falava de ponto decimal variável
e eu de ponto decimal fixo.
> Toda operação aritmética entre racionais deve resultar um
> racional, do que se conclui que toda conta possível para esse
> sistema seria exatamente representável dessa forma -- mesmo que
> sua representação decimal fosse uma dízima.
>
Você está vendo do ponto de vista matemático. Se fosse trabalhar com
física, por exemplo, não daria para apresentar como resposta uma
dízima periódica se o seu equipamento de medição te dá um dado com 3
casas decimais de precisão. Então você deveria também calcular o
número de dígitos significativos e descartar todo o resto.
> Eu só espero agora que você não apresente um caso onde o cliente
> compra uma número irracional de algum produto, como raiz-de-2
> canetas. :-(
>
Com canetas não dá. Mas imagina que você precisa colocar uma barra
de ferro na diagonal de uma moldura de 1m por 1m? Você teria que
comprar uma barra de ferro medindo raiz de 2 metros... ;-)