Trabalhando com valores monetários

808 views
Skip to first unread message

Márcio Gil

unread,
Jul 23, 2009, 2:14:17 PM7/23/09
to C-cpp-brasil
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.

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.
>

Felipe Magno de Almeida

unread,
Jul 23, 2009, 2:22:32 PM7/23/09
to ccppb...@googlegroups.com
2009/7/23 Márcio Gil <marci...@bol.com.br>:

>
> 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.
>
> 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 ) ...

Espero que meu banco não use ponto flutuante...

--
Felipe Magno de Almeida

Rafael

unread,
Jul 23, 2009, 2:25:39 PM7/23/09
to ccppb...@googlegroups.com
Existe alguma classe/estrutura para trabalhar com ponto flutuante com precisão como tem o BigDecimal no Java?

Rafael ;p

2009/7/23 Felipe Magno de Almeida <felipe.m...@gmail.com>

Thiago A.

unread,
Jul 23, 2009, 2:31:11 PM7/23/09
to ccppbrasil

A representação é importante e com inteiros ela será exata.
As operações tem que serem feitas caso a caso.

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.
> > Neste caso vale a pena criar o tipo.- Ocultar texto das mensagens anteriores -
>
> - Mostrar texto das mensagens anteriores -

Alex Queiroz

unread,
Jul 23, 2009, 2:32:28 PM7/23/09
to ccppb...@googlegroups.com
Hallo,

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/

Virgilio Fornazin

unread,
Jul 23, 2009, 2:59:03 PM7/23/09
to ccppb...@googlegroups.com
O type CY do SDK do Windows (currency) e´um valor int64, sendo q ele representa até de 0x7FFFFFFFFFFFFFFF (+9223372036854775807) ate 0x8000000000000000 (-9223372036854775808), sendo que ele representa os valores multiplicados por 10^4 (+922.337.203.685.477,5807 até -922.337.203.685.477,5808). Se você olhar o projeto do WINE, voce vai ver como manipular e fazer as multiplicacoes e divisoes (existem umas funcones VarMulti* se nao me engano que fazem o trabalho).

Implementamos uma classe dessa para nosso software de calculos financeiros e a performance é excelente, além de evitar rounding errors, ja que os valores nossos sempre usam 2 casas decimais.

Existem algumas classes na internet para numeros gigantes, que eu pesquisei na epoca, mas a solucão com int64 nos atendeu perfeitamente, além da interoperabilidade com OLE para compoenntes com de terceiros.

Agora, quanto ao problema do 0.33, para calcular parcelas geramente se faz um calculo da seguinte maneira:

PARCELA = FINAL / NUM_PARCELAS;
PARCELA_FINAL_OU_INICIAL = FINAL - (PARCELA * (NUM_PARCELAS - 1));

Assim vc garante que o saldo final sempre sera a soma das parcelas, sem problema de centavos.

[]'s

2009/7/23 Alex Queiroz <asan...@gmail.com>

Márcio Gil

unread,
Jul 23, 2009, 3:06:33 PM7/23/09
to ccppb...@googlegroups.com
> -----Mensagem original-----
> De: Thiago A.
>
> A representação é importante e com inteiros ela será exata.
> As operações tem que serem feitas caso a caso.
>

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


Márcio Gil

unread,
Jul 23, 2009, 3:16:01 PM7/23/09
to ccppb...@googlegroups.com

Esqueci de substituir R_PREC por 0.500001L

http://cpp.pastebin.com/f79f794bb

> -----Mensagem original-----
> De: Márcio Gil

Márcio Gil

unread,
Jul 23, 2009, 3:42:30 PM7/23/09
to ccppb...@googlegroups.com
Estava procurando alguma classe monetária e achei esta aqui:

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...

Thiago A.

unread,
Jul 23, 2009, 3:47:56 PM7/23/09
to ccppbrasil

> > A representação é importante e com inteiros ela será exata.
> > As operações tem que serem feitas caso a caso.
>
> 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?


Não entendi bem qual é a dúvida. Porque o correto seria 66.67?
O correto para mim é dizer:
200 reais dividido por 3, são 66.66 reais e sobra 2 centavos.

Então no caso de calcular parcelas eu faria:

const int nparcelas = 3;
int valor = 20000 / nparcelas;
int resto = 2000 % nparcelas;
int parcela1 = valor + resto;

cout << parcela1 / 100.0 << " reais" << endl;
for (int i = 1; i < nparcelas; i++)
{
cout << valor / 100.0 << " reais" << endl;
}

Os inteiros representariam sempre centavos pois é a menor unidade.

Quando existe uma conta naturalmente fracionária (talvez a taxa de
juros) então tem que haver uma convenção de arredondamento.
Por exemplo, se alguém lhe falar que dá 1.56% de juros ao mes e você
tem 126.66 reais no banco então no próximo mes terá:
126.66 + 1.56 / 100 * 126.66 = 126.66 + 1.975896 reais = 128.635896.

Naturalmente no seu extrato vai estar 128.63 ou 128.64 dependendo da
convenção de arredondamento.

Thiago A.

unread,
Jul 23, 2009, 4:02:47 PM7/23/09
to ccppbrasil
Arredondar no começo ou fim?
O ideal seria não arrendondar nunca...

No caso de operações de divisão, multiplicação, soma subtração,
poderia ser usada uma classe de precisão infinita (ou quase). Basta
para isso trabalhar e representar as frações.

Eu acho que os bancos não fazem isso (talves tenha uma norma) mas
também as operações com juros não podem ser representadas com frações
(valor presente, futuro etc..) então não tem como representar com
números fracionários e os números ficam irracionais.

Aí... a unica solução é usar a melhor precisão que tiver e criar
convensões para ficar claro o que está acontecendo. O acúmulo de erros
tem que ser evitado, mas teria que ver cada caso. Eu nunca trabalhei
com software para bancos.

Márcio Gil

unread,
Jul 23, 2009, 4:25:47 PM7/23/09
to ccppb...@googlegroups.com
> -----Mensagem original-----
> De: Thiago A.
>
> > > A representação é importante e com inteiros ela será exata.
> > > As operações tem que serem feitas caso a caso.
> >
> > 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?
>
>
> Não entendi bem qual é a dúvida. Porque o correto seria 66.67?
> O correto para mim é dizer:
> 200 reais dividido por 3, são 66.66 reais e sobra 2 centavos.
>
Em operações monetárias, _geralmente_ os resultados são arredondados. No
caso do parcelamento não é este o problema, pois o somatórios das parcelas
deve ser igual ao total.

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.


Thiago A.

unread,
Jul 23, 2009, 5:19:35 PM7/23/09
to ccppbrasil
> ou seja lá o que for.- Ocultar texto das mensagens anteriores -
>

Eu tinha entendido.
Como eu disse, operações de - + * / com frações serão sempre frações.
Então se você só precisar disso (precisao total), pode fazer uma
classe money que tenha um numerador e um denominador em inteiros e
trabalhe com fracoes.
Vou ver se faço uma classe desta de exemplo e coloco aqui amanha.

O problema disto, são operações que não podem ser representadas como
frações como por exemplo raiz quadrada, expoentes etc..


Márcio Gil

unread,
Jul 23, 2009, 5:39:53 PM7/23/09
to ccppb...@googlegroups.com
> -----Mensagem original-----
> De: Thiago A.
>
> > 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.
> >
>
> Eu tinha entendido.
> Como eu disse, operações de - + * / com frações serão sempre
> frações. Então se você só precisar disso (precisao total), pode
> fazer uma classe money que tenha um numerador e um denominador em
> inteiros e trabalhe com fracoes.
> Vou ver se faço uma classe desta de exemplo e coloco aqui amanha.
>
> O problema disto, são operações que não podem ser representadas
> como frações como por exemplo raiz quadrada, expoentes etc..
>

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.

Thiago A.

unread,
Jul 23, 2009, 7:27:04 PM7/23/09
to ccppbrasil

Coloquei um código de classe para numeros racionais.

http://www.thradams.com/codeblog/rational.htm

A classe money poderia usar o rational para representar os centavos.
Entao as operacoes definidas nao perdem precisao.
> Depois a gente vê como ficou.- Ocultar texto das mensagens anteriores -
>
> - Mostrar texto das mensagens anteriores -

Márcio Gil

unread,
Jul 23, 2009, 10:21:40 PM7/23/09
to ccppb...@googlegroups.com
A muito tempo atrás eu também fiz algo com aritmética de racionais, mas era
em C e era uma calculadora. Na época eu reparei que as operações chegavam no
limite muito rápido, ocasionando resultados incorretos. Conforme os
numeradores e denominadores se tornam primos entre si a simplificação não os
diminui e então um cálculo simples estoura a capacidade da fração. Gostei da
sua classe, depois vou fazer uns testes livres.

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'.

Márcio Gil

unread,
Jul 24, 2009, 10:15:42 AM7/24/09
to ccppb...@googlegroups.com
Fiz um pequeno teste com sua classe, tive apenas que implementar o operador
menor:

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
>

Thiago A.

unread,
Jul 24, 2009, 10:34:05 AM7/24/09
to ccppbrasil
Eu vi que existe uma classe na boost para isso:

http://www.boost.org/doc/libs/1_39_0/libs/rational/index.html

Se realmente precisar de uma classe rational, iria demorar um tempo só
para estabilizar o código rascunho que eu fiz.

Eu fiz outra versão com os operadores fora da classe.
Esta no mesmo lugar:
http://www.thradams.com/codeblog/rational.htm

Mas ainda faltam várias coisas.. Acho que para seguranca a
normalizacao tem que ser feita toda fez que os valores mudarem.

Thiago A.

unread,
Jul 24, 2009, 10:40:54 AM7/24/09
to ccppbrasil
que compilador você usa?
o long long é um inteiro 64 bits no seu compilador?

Márcio Gil

unread,
Jul 24, 2009, 10:55:57 AM7/24/09
to ccppb...@googlegroups.com
> -----Mensagem original-----
> De: Thiago A.
>
> que compilador você usa?
> o long long é um inteiro 64 bits no seu compilador?
>
Sim o 'long long' tem 64 bits:

...
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.


Márcio Gil

unread,
Jul 24, 2009, 1:47:26 PM7/24/09
to ccppb...@googlegroups.com
> -----Mensagem original-----
> De: Thiago A.
>
> Eu vi que existe uma classe na boost para isso:
>
> http://www.boost.org/doc/libs/1_39_0/libs/rational/index.html
>
> Se realmente precisar de uma classe rational, iria demorar um tempo só
> para estabilizar o código rascunho que eu fiz.
>
> Eu fiz outra versão com os operadores fora da classe.
> Esta no mesmo lugar:
> http://www.thradams.com/codeblog/rational.htm
>
> Mas ainda faltam várias coisas.. Acho que para seguranca a
> normalizacao tem que ser feita toda fez que os valores mudarem.
>
>

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.


Márcio Gil

unread,
Jul 26, 2009, 10:35:55 AM7/26/09
to ccppb...@googlegroups.com
> -----Original Message-----
> From: Thiago A.
>
> Eu vi que existe uma classe na boost para isso:
>
> http://www.boost.org/doc/libs/1_39_0/libs/rational/index.html
>

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.

Márcio Gil

unread,
Jul 26, 2009, 1:43:28 PM7/26/09
to ccppb...@googlegroups.com
Melhorei o teste para converter o racional para formato em ponto
decimal:

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 Lamarão

unread,
Jul 27, 2009, 2:19:06 PM7/27/09
to ccppbrasil
On 23 jul, 15:14, Márcio Gil <marciom...@bol.com.br> wrote:

> 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.

Multiplicação não deve truncar nunca, já que a multiplicação de dois
valores inteiros é sempre um valor inteiro.
Pode ocorrer overflow, um problema diferente.

A divisão com o operador / pode causar truncamento porque os operandos
desse operador são sempre do tipo double; se os argumentos forem ints
eles sofrerão uma conversão implícita.
Isso significa que programas trabalhando com valores inteiros não
devem dividir com /, mas sim com funções de biblioteca como div.

Assim o programa pode se guardar de absurdos e calcular parcelas como
o Thiago falou -- fazendo bom uso do resto.

--
P.

Márcio Gil

unread,
Jul 27, 2009, 4:22:41 PM7/27/09
to ccppb...@googlegroups.com
> -----Original Message-----
> From: Pedro Lamarão
>
> On 23 jul, 15:14, Márcio Gil <marciom...@bol.com.br> wrote:
> > 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.
>
> Multiplicação não deve truncar nunca, já que a multiplicação de
> dois valores inteiros é sempre um valor inteiro.
> Pode ocorrer overflow, um problema diferente.
>

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:

http://cpp.pastebin.com/f55150720

Thiago A.

unread,
Jul 27, 2009, 5:17:55 PM7/27/09
to ccppbrasil
A melhor representação vai depender do tipo de cálculo.

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 fracao
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 bando, eu
usaria um numero de precisao infinita.
Se fosse para dividir e somar numeros 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?

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?
Ja tem o contexto aonde quer usar?
Por que ela é melhor que um double ou um int 64 cru ?

Márcio Gil

unread,
Jul 27, 2009, 6:15:16 PM7/27/09
to ccppb...@googlegroups.com
> -----Original Message-----
> From: Thiago A.
>
> A melhor representação vai depender do tipo de cálculo.
>

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.

Pedro Lamarão

unread,
Jul 27, 2009, 10:17:30 PM7/27/09
to ccppbrasil
On 27 jul, 17:22, Márcio Gil <marciom...@bol.com.br> wrote:
> > -----Original Message-----
> > From: Pedro Lamarão
>
> > Multiplicação não deve truncar nunca, já que a multiplicação de
> > dois valores inteiros é sempre um valor inteiro.
> > Pode ocorrer overflow, um problema diferente.
>
> Pedro, estamos falando de representar um número com 2 casas decimais
> em inteiro.

E eu estou falando que a multiplicação de inteiros sempre resulta um
número inteiro, de modo que nunca provoca truncamento.

> Exemplo: 123,45 fica 12345.
>
> a conta:
> 123,45 x 0,50 = 61,725
>
> seria feita assim:
> (12345 x 50) / 100 = 6172

A expressão à esquerda é composta por duas sub-expressões.

A sub-expressão 12345 * 50 resulta, em um sistema de 32bits,
exatamente um valor 617250.
Não há truncamento aqui.
Em um sistema de 16bits ela provocaria overflow.

A sub-expressão 617250 / 100 resulta o quociente da divisão inteira,
que é de fato 6172.
Você pode interpretar esse comportamento como "truncamento", ou pode
interpretá-lo como o resultado natural do algoritmo de divisão de
números inteiros; ISO C++ interpreta como "arredondar para baixo".

Este programa deve usar a função de biblioteca div, que retorna
corretamente o quociente e o resto, e respeitar as regras da
aritmética em números inteiros.

div_t qt = div((12345 x 50), 100);
assert(qt.quot == 6172);
assert(qt.rem == 50);

--
P.

Márcio Gil

unread,
Jul 28, 2009, 9:21:50 AM7/28/09
to ccppb...@googlegroups.com
> -----Original Message-----
> From: Pedro Lamarão
>
> E eu estou falando que a multiplicação de inteiros sempre resulta
um
> número inteiro, de modo que nunca provoca truncamento.
>
Estou falando de números no formato IIIIIIDD, sendo I a parte
inteira e D a parte decimal.

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.

Pedro Lamarão

unread,
Jul 28, 2009, 1:32:08 PM7/28/09
to ccppbrasil
On 28 jul, 10:21, Márcio Gil <marciom...@bol.com.br> wrote:
> > From: Pedro Lamarão
>
> > E eu estou falando que a multiplicação de inteiros sempre resulta
> um
> > número inteiro, de modo que nunca provoca truncamento.
>
> Estou falando de números no formato IIIIIIDD, sendo I a parte
> inteira e D a parte decimal.

Este número é um número inteiro, o numerador de um número racional
cujo denominador é 100.

Se eu tenho R$ 62,70 reais então eu tenho 6270 centavos de reais, um
número inteiro.
(Se você é uma bomba de gasolina pode compreender que eu tenho 62700
milésimos de reais, que também é um número inteiro.)

Como o denominador é invariável, projetar esses valores no domínio dos
números inteiros é trivial; você toma o numerador e pronto.
Sendo o denominador fixo, a única "variável" é o numerador, de modo
que o conjunto de valores racionais do tipo X/100 é idêntico ao
conjunto dos números X inteiros.

O problema de "truncamento" que você menciona é uma ilusão de ótica,
causada pela interpretação deste valor como um valor no domínio dos
reais, o que ele claramente não é.
Esta má interpretação leva a crer que existe um problema sério na
manutenção de "dígitos significativos" -- porque o número de Ds
aumenta e só podem haver dois Ds.

Isto é um engano facilmente detectável pela analogia com o mundo real.
Um valor monetário é um valor contável, um valor inteiro.
Qualquer quantidade de centavos de real pode ser multiplicada por
qualquer outra quantidade inteira de centavos resultando um valor
exato de centavos de real.
Pode ser que o número de centavos ao final seja impossível de guardar
no bolso -- causando um overflow na sua calça jeans -- mas ele
permanece exato.

É um absurdo que eu possa realizar essa multiplicação em uma
calculadora ou no papel com exatidão e um sistema computacional tenha
problemas de truncamento, o que exibe a fragilidade desse modelo
IIIIIDD.

O único problema aritmético substancial envolvendo valores monetários
deve ser a divisão, porque a divisão no domínio dos inteiros resulta
quociente e resto.
Assim sendo, não é ótimo usar o operador / da linguagem de programação
para calcular a divisão; ele forçará você a usar o operador % logo em
seguida, o que é um desperdício.
Funções como div são mais eficientes -- e elegantes, na minha opinião.

A única variável membro necessária em uma classe representando valores
em real deve ser um long -- o numerador de uma fração cujo divisor é
100.
Se você deseja reusabilidade por parte de bombas de gasolina pode
acrescentar um parâmetro de template long chamado Denominator, e
incluir alguns testes estáticos para garantir que Denominator é sempre
um múltiplo de 10.
Implementar operador<< é trivial com base na divisão de inteiros.
Uma tentativa rápida sem compilador resulta:

template <typename CharT, typename TraitsT, long Denominator>
std::basic_ostream<CharT, TraitsT>&
operator<< (std::basic_ostream<CharT, TraitsT>& os, Money<Denominator>
const& m)
{
div_t d = div(m.value, Denominator);
return os << "R$ " << d.quot << ',' << d.rem;
}

--
P.

Márcio Gil

unread,
Jul 28, 2009, 3:00:14 PM7/28/09
to ccppb...@googlegroups.com
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".

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
>

Alex Queiroz

unread,
Jul 28, 2009, 3:12:23 PM7/28/09
to ccppb...@googlegroups.com
Hallo,

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/

Márcio Gil

unread,
Jul 28, 2009, 3:46:55 PM7/28/09
to ccppb...@googlegroups.com
> -----Original Message-----
> From: Alex Queiroz
>
> Hallo,
>
> 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 variável double e'
> de ponto flutuante, valores monetários são de ponto fixo. A melhor
> forma de armazená-los e' realmente como um inteiro.
>

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.

Alex Queiroz

unread,
Jul 28, 2009, 3:51:10 PM7/28/09
to ccppb...@googlegroups.com
Hallo,

Nao, porque inteiros nao perdem precisao quando multiplicados.

--
-alex
http://www.ventonegro.org/

Márcio Gil

unread,
Jul 28, 2009, 4:15:14 PM7/28/09
to ccppb...@googlegroups.com
> -----Original Message-----
> From: Alex Queiroz
> Sent: Tuesday, July 28, 2009 4:12 PM
>
> (...) valores monetários são de ponto fixo. (...)

>
> -----Original Message-----
> From: Alex Queiroz
> Sent: Tuesday, July 28, 2009 4:51 PM
>
> Não, porque inteiros não perdem precisão quando multiplicados.
>

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?".

Pedro Lamarão

unread,
Jul 28, 2009, 4:26:05 PM7/28/09
to ccppbrasil
On 28 jul, 16:00, Márcio Gil <marciom...@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".

Você está confundindo um schema de representação na máquina física com
o domínio algébrico de um valor.
O schema "double" tem o propósito de representar com eficiência
valores no domínio dos reais.

A mesma confusão ocorre com o tal modelo IIIIDD.
O que quer que seja esse schema de representação, seu significado é
uma quantidade de dinheiro, que certamente é um valor no domínio dos
inteiros.

> Outro detalhe é que centavos multiplicado por centavos é centavos ao
> quadrado. Dinheiro você multiplica por uma quantidade ou por uma
> determinada taxa.

Em que situação do mundo real você multiplica centavos por centavos?
Dez quantidades de 500 centavos não são 500 centavos vezes 10
centavos, são 500 centavos vezes 10.

--
P.

Alex Queiroz

unread,
Jul 28, 2009, 4:31:08 PM7/28/09
to ccppb...@googlegroups.com
Hallo,

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/

Thiago A.

unread,
Jul 28, 2009, 4:49:32 PM7/28/09
to ccppbrasil
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.


-x-

No começo deste ano, coloquei no meu site fórmulas para juros.
http://www.thradams.com/codeblog/finance.htm
Ela usa logaritimo e exponencial. Para este tipo de coisa, não fazia
sentido trabalhar com inteiros pois de qualquer forma os números não
terão representação exata.



On 28 jul, 17:31, Alex Queiroz <asand...@gmail.com> wrote:
> Hallo,
>

Alex Queiroz

unread,
Jul 28, 2009, 5:00:20 PM7/28/09
to ccppb...@googlegroups.com
Hallo,

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/

Márcio Gil

unread,
Jul 28, 2009, 5:09:45 PM7/28/09
to ccppb...@googlegroups.com
> -----Original Message-----
> From: Pedro Lamarão
> Sent: Tuesday, July 28, 2009 2:32 PM
>
> (...)Qualquer quantidade de centavos de real pode ser multiplicada

por
> qualquer outra quantidade inteira de centavos resultando um valor
> exato de centavos de real.(...)

>
> -----Original Message-----
> From: Pedro Lamarão
> Sent: Tuesday, July 28, 2009 5:26 PM
>
> Em que situação do mundo real você multiplica centavos por
centavos?
> Dez quantidades de 500 centavos não são 500 centavos vezes 10
> centavos, são 500 centavos vezes 10.
>

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...

Márcio Gil

unread,
Jul 28, 2009, 5:31:05 PM7/28/09
to ccppb...@googlegroups.com

> -----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


Alex Queiroz

unread,
Jul 28, 2009, 5:40:28 PM7/28/09
to ccppb...@googlegroups.com
Hallo,

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/

Pedro Lamarão

unread,
Jul 29, 2009, 11:13:55 AM7/29/09
to ccppbrasil
On 28 jul, 18:09, Márcio Gil <marciom...@bol.com.br> wrote:

> 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.

Me perdoe, mas até esse momento eu entendi que estávamos falando sobre
truncamento como um problema sofrido pelo sistema involuntariamente,
ao invés de uma parte integrante das regras do negócio do cliente.

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.
Perceba que a soma dos valores intermediários, por mais que
irrepresentável como um decimal com duas casas "fracionárias", resulta
ao final um valor perfeitamente representável dessa forma.
O arredondamento nas sub-parcelas está literalmente lesionando a loja
em um centavo.

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.
É claro que o exemplo acima exige do sistema uma inteligência maior do
que as minhas alegações iniciais, de que o denominador seria sempre
fixo em 100.

O que o sistema fará com esses números depois é cumprir as regras do
negócio, e se essas regras exigem arredondamento para baixo em cada
sub-parcela, que assim seja. Mas essa decisão é do negócio, e não dos
números.

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. :-(

--
P.

Márcio Gil

unread,
Jul 29, 2009, 2:10:06 PM7/29/09
to ccppb...@googlegroups.com
> -----Original Message-----
> From: Pedro Lamarão
>
> Me perdoe, mas até esse momento eu entendi que estávamos falando
> sobre truncamento como um problema sofrido pelo sistema
> involuntariamente, ao invés de uma parte integrante das regras do
> negócio do cliente.
>
Tanto um como o outro. Veja na parte II da minha resposta asseguir.

> 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... ;-)


Pedro Lamarão

unread,
Jul 29, 2009, 8:48:45 PM7/29/09
to ccppbrasil
On 29 jul, 15:10, Márcio Gil <marciom...@bol.com.br> wrote:

> A - Seria necessário armazenar a informação de número de casas
> decimais no objeto.

(Antes de começar, eu gostaria de dizer que estou de férias.)

A notação de fração sugere uma representação em tupla: (numerador,
denominador).
(Vou usar uma notação esquisita para construção, pra economizar
espaço.)

struct Rational
{
long num; // numerador
long den; // denominador
};

A multiplicação em racionais representados assim é trivial:

Rational operator* (Rational x, Rational y)
{
return Rational { x.num * y.num , x.den * y.den };
}

A multiplicação por um inteiro i é um caso degenerado da multiplicação
por um Rational { i, 1 }.
O inverso é útil:

Rational inverse (Rational x)
{
return Rational { x.den, x.num };
}

inverse sugere uma função power capaz de expoente negativo.
Com o inverso podemos definir a divisão racional:

Rational operator/ (Rational x, Rational y)
{
return x * inverse(y);
}

Versões inicias para adição e subtração:

Rational operator+ (Rational x, Rational y)
{
return Rational { x.num * y.den + y.num * x.den, x.den * y.den };
}

Rational operator- (Rational x, Rational y)
{
return Rational { x.num * y.den + y.num * x.den, x.den * y.den };
}

Uma versão ingênua para equivalência de racionais seria:

bool operator== (Rational x, Rational y)
{
return (x.num == y.num) && (x.den == y.den);
}

mas é claro que está incorreta, já que 1/2 é equivalente a 2/4.
Para definir uma equivalência adequada, podemos usar a noção de fração
irredutível: uma fração é irredutível quando não há número inteiro p >
1 que divida ambos o numerador e o denominador.
Frações redutíveis a uma mesma fração irredutível fazem parte da mesma
classe de equivalência.

Suponha que temos à disposição um algoritmo gcd que determina o máximo
divisor comum entre x e y.
Este é o predicado para irredutível:

bool irreducible (Rational x)
{
return gcd(x.num, x.den) == 1;
}

É possível implementar uma função de redução para frações:

Rational reduce (Rational x)
{
long z = gcd(x.num, x.den);
return Rational { x.num / z, x.den / z };
}

Com o uso de irreducible e de reduce você poderá controlar a
representação dos seus objetos durante uma conta complicada; com gcd
você também pode implementar adição e subtração com resultado
irredutível.

Com reduce é possível implementar a equivalência corretamente:

bool operator== (Rational x, Rational y)
{
Rational x1 = reduce(x);
Rational y1 = reduce(y);
return (x1.num == y1.num) && (x1.den == y1.den);
}

--
P.
Reply all
Reply to author
Forward
0 new messages