Existe uma única representação de dados que funcione para todas as moedas (mesmo aquelas diferentes de dólares, euros e libras)?

12

Posso encontrar muitas perguntas sobre bibliotecas a serem usadas para representar valores em alguma moeda. E sobre a questão antiga de por que você não deve armazenar moeda como um número de ponto flutuante IEEE 754. Mas não consigo encontrar mais nada. Certamente há muito mais a saber sobre moeda no uso no mundo real. Estou particularmente interessado no que você precisa saber para representá-lo em uso físico (por exemplo, com o dólar, você nunca tem precisão inferior a US $ 0,01, permitindo a representação como um número inteiro de centavos).

Mas é difícil fazer suposições sobre a versatilidade do seu programa quando as únicas moedas que você conhece são as populares ocidentais (como dólar, euro e libra). O que mais é conhecimento relevante em uma perspectiva puramente programática? Não estou preocupado com o tópico da conversão.

Em particular, o que precisamos saber para poder armazenar valores em alguma moeda e imprimi-los?

Kat
fonte
2
Nem todos os programadores trabalham em setores que manipulam valores monetários.
Whatsisname
4
Ah, claro. Mas isso pode ser dito sobre quase tudo. Acho que podemos supor que aqueles que acham essa pergunta útil seriam aqueles que trabalham com moeda.
Kat
5
Eu fiz muito trabalho de desenvolvimento para bancos e outras empresas de serviços financeiros em Nova York. E posso garantir que não há resposta "fechada" à sua pergunta. Você também pode perguntar: "quanto de matemática eu tenho que saber?" Por exemplo, na década de 1990, os preços das ações passaram de oitavas e 256th para decimais, exigindo muita reprogramação. Quem poderia imaginar que isso aconteceria? Você só precisa entender os negócios que seus aplicativos suportam e como melhor representar os dados (nesse caso, a moeda) para dar suporte a essas necessidades de negócios.
John Forkosh
4
Meu 0,02: o preço é uma coisa. Moeda é outra.
Machado
3
Eu pensei: Oh, deve haver algum denominador comum para todas as moedas. Mas talvez não. A moeda de um centavo é atualmente 1⁄100 de libra, mas era 1⁄240 de libra. Portanto, você não apenas tem uma divisão de 1/240, mas também uma data de troca e, possivelmente, uma taxa de câmbio por centavos antigos ainda não trocada. pt.wikipedia.org/wiki/Penny_sterling Nem me inicie com conchas de caubói! en.wikipedia.org/wiki/Shell_money ou o fato de que o dinheiro requer crença para se tornar real: en.wikipedia.org/wiki/Money Ótima pergunta, mesmo que não se encaixe bem no StackExchange!
GlenPeterson

Respostas:

24

por exemplo, com o dólar, você nunca tem precisão inferior a US $ 0,01

Sério? insira a descrição da imagem aqui

a questão antiga de por que você não deve armazenar moeda como um número de ponto flutuante IEEE 754.

insira a descrição da imagem aqui

Sinta-se à vontade para armazenar polegadas nos números de ponto flutuante IEEE 754 . Eles armazenam exatamente como você esperaria.

Sinta-se à vontade para armazenar qualquer quantia de dinheiro nos números de ponto flutuante IEEE 754 que você pode armazenar usando os ticks que dividem uma régua em frações de polegada.

Por quê? Porque quando você usa o IEEE 754, é assim que você o armazena.

O problema das polegadas é que elas são divididas em duas partes. A coisa sobre a maioria dos tipos de moeda é que eles são divididos em décimos (alguns tipos não são, mas vamos manter o foco).

Essa diferença não seria tão confusa, exceto que, para a maioria das linguagens de programação, a entrada e saída dos números de ponto flutuante IEEE 754 é expressa em decimais! O que é muito estranho, porque eles não são armazenados em decimais.

Por causa disso, você nunca consegue ver como os bits fazem coisas estranhas quando você pede ao computador para armazenar 0.1. Você só vê a estranheza quando faz contas contra ela e ela apresenta erros estranhos.

Do java eficaz de Josh Bloch :

System.out.println(1.03 - .42);

Produz 0.6100000000000001

O que é mais revelador sobre isso não é o 1caminho para a direita. São os números estranhos que precisavam ser usados ​​para obtê-lo. Em vez de usar o exemplo mais popular, 0.1precisamos usar um exemplo que mostre o problema e evite o arredondamento que o ocultaria.

Por exemplo, por que isso funciona?

System.out.println(.01 - .02);

Produz -0.01

Porque nós tivemos sorte.

Eu odeio problemas difíceis de diagnosticar porque às vezes tenho "sorte".

O IEEE 754 simplesmente não pode armazenar 0,1 com precisão. Mas se você pedir para armazenar 0,1 e depois imprimir, será exibido 0,1 e você achará que está tudo bem. Não está bom, mas você não pode ver isso porque está arredondando para voltar a 0,1.

Algumas pessoas confundem os outros chamando essas discrepâncias de arredondamento de erros. Não, estes não são erros de arredondamento. O arredondamento está fazendo o que deveria e transformando o que não é decimal em decimal, para que possa ser impresso na tela.

Mas isso oculta a incompatibilidade entre como o número é exibido e como é armazenado. O erro não ocorreu quando o arredondamento aconteceu. Aconteceu quando você decidiu inserir um número em um sistema que não pode armazená-lo com precisão e supôs que ele estava sendo armazenado com precisão quando não estava.

Ninguém espera que π seja armazenado com precisão em uma calculadora e eles conseguem trabalhar com ela muito bem. Portanto, o problema não é sequer sobre precisão. É sobre a precisão esperada. Os computadores exibem um décimo 0.1da mesma forma que nossas calculadoras, portanto esperamos que eles armazenem um décimo perfeitamente da maneira que nossas calculadoras. Eles não. O que é surpreendente, já que os computadores são mais caros.

Deixe-me mostrar a incompatibilidade:

insira a descrição da imagem aqui

Observe que 1/2 e 0,5 se alinham perfeitamente. Mas 0.1 simplesmente não se alinha. Claro que você pode se aproximar se continuar dividindo por 2, mas nunca acertará exatamente. E precisamos de mais e mais bits cada vez que dividimos por 2. Portanto, representar 0,1 com qualquer sistema que divida por 2 precisa de um número infinito de bits. Meu disco rígido não é tão grande assim.

Portanto, o IEEE 754 para de tentar quando fica sem bits. O que é legal porque preciso de espaço no meu disco rígido para ... fotos de família. Não mesmo. Fotos de família. : P

De qualquer forma, o que você digita e o que vê são decimais (à direita), mas o que você armazena são bicimais (à esquerda). Às vezes, esses são perfeitamente iguais. Às vezes não são. Às vezes, parece que são iguais quando simplesmente não são. Esse é o arredondamento.

Em particular, o que precisamos saber para poder armazenar valores em alguma moeda e imprimi-los?

Por favor, se você estiver lidando com meu dinheiro com base decimal, não use carros alegóricos ou duplos.

Se você tem certeza de que coisas como décimos de centavos não estarão envolvidas, basta guardar centavos. Caso contrário, descubra qual será a menor unidade dessa moeda e use-a. Se não puder, use algo como BigDecimal .

Meu patrimônio líquido provavelmente sempre se encaixará perfeitamente em um número inteiro de 64 bits, mas coisas como o BigInteger funcionam bem em projetos maiores que isso. Eles são apenas mais lentos que os tipos nativos.

Descobrir como armazenar é apenas metade do problema. Lembre-se de que você também deve poder exibi-lo. Um bom design separará essas duas coisas. O verdadeiro problema com o uso de carros alegóricos aqui é que essas duas coisas são misturadas.

insira a descrição da imagem aqui

candied_orange
fonte
6
Eu realmente não estava procurando mais uma explicação de por que o IEEE 754 não funciona por dinheiro. Mencionei no OP que isso foi bem explicado em outro lugar. Da mesma forma, há uma razão pela qual eu usei o termo "uso físico". Ou seja, porque, embora os preços do gás, os valores da bolsa de valores, etc. possam ter precisão arbitrária, o dinheiro físico (e o grande número de transações de usuários) não ultrapasse o lugar das centenas (e vários softwares querem representar apenas o que pode ser pago com dinheiro físico). Uma das informações que me perguntei foi se existiam moedas que tinham mais casas decimais.
Kat
3
A "estranheza" do ponto flutuante não se limita apenas à metade versus 1/10 de. O aspecto "ponto flutuante" no nome também causa resultados às vezes inesperados.
Whatsisname
6
Antes da decimalização, a moeda do Reino Unido era libras, xelins e moedas de um centavo, (£ sd), com £ 1 == 20s, 1s = 12d, então £ 1 = 240d, então 1d = 0,0046666666666. Antes de ingressar no euro, a lira italiana chegou a US $ 2346,4 (em 2001), para US $, então coisas como salários e preços de casas às vezes atingem o limite máximo de flutuadores de precisão únicos e os números econômicos estavam atingindo os níveis mais altos de dobra.
21917 Steve Barnes
2
Eu apenas argumentaria que a moeda é uma coisa e o preço é outra. Você pode ter mais de US $ 0,01 de precisão nos preços, mas não pode efetivamente pagar com essa precisão.
Machado
1
Além disso, fotos de família . Pft. :)
Machado
10

Posso encontrar muitas perguntas sobre bibliotecas a serem usadas para representar valores em alguma moeda.

"Bibliotecas" são desnecessárias, a menos que a biblioteca padrão do seu idioma esteja ausente em certos tipos de dados, como explicarei.

Certamente há muito mais a saber sobre moeda no uso no mundo real. Estou particularmente interessado no que você precisa saber para representá-lo em uso físico (por exemplo, com o dólar, você nunca tem precisão inferior a US $ 0,01, permitindo a representação como um número inteiro de centavos).

Simplesmente, você precisa de ponto decimal fixo , e não ponto decimal flutuante . Por exemplo, a classe BigDecimal do Java pode ser usada para armazenar uma quantia em moeda. Outras linguagens modernas têm tipos semelhantes embutidos, incluindo C # e Python . As implementações variam, mas geralmente armazenam um número como um número inteiro, com o local decimal como um membro de dados separado. Isso fornece precisão exata, mesmo ao executar aritmética que daria restos ímpares (por exemplo, 0,0000001) com números de ponto flutuante IEEE.

Em particular, o que precisamos saber para poder armazenar valores em alguma moeda e imprimi-los?

Existem alguns pontos importantes.

  1. Use um tipo decimal real em vez de ponto flutuante.

  2. Entenda que o valor da moeda possui dois componentes: um valor (5,63) e um código ou tipo de moeda (USD, CAD, GBP, EUR, et al). Às vezes você pode ignorar o código da moeda, outras vezes é vital. E se você estiver trabalhando em um sistema financeiro ou de varejo / comércio eletrônico que permita várias moedas? O que acontece se você estiver tentando receber dinheiro de um cliente em CAD, mas ele deseja pagar com MXN? Você precisa de um tipo de "dinheiro" com um código de moeda e um valor de moeda para poder misturar esses valores (também taxa de câmbio, mas não quero ir muito longe na tangente). Ao mesmo tempo, meu software de finanças pessoais nunca precisa se preocupar com isso, porque tudo está em dólares americanos (ele pode misturar moedas, mas eu nunca preciso).

  3. Enquanto uma moeda pode ter uma unidade física menor na prática (CAD e USD têm centavos, o JPY é apenas ... Yen), é possível ficar menor. A resposta de CandiedOrange aponta os preços dos combustíveis em décimos de centavo. Meus impostos sobre a propriedade são avaliados como moinhos por dólar ou décimos de centavo (1/1000 de um dólar americano). Não se limite a US $ 0,01. Embora você possa exibir esses valores na maioria das vezes, seus tipos devem permitir menores (os tipos decimais mencionados acima permitem).

  4. Cálculos intermediários certamente devem permitir mais precisão do que um único centavo. Trabalhei em sistemas de varejo / comércio eletrônico em que os valores internos foram arredondados para US $ 0,00000001 internamente. Normalmente, a precisão infinita não é suportada por tipos decimais (ou SQL); portanto, deve haver algum limite. Por exemplo, dividir 1/3 usando o BigDecimal do Java lançará uma exceção sem um RoundingMode ou MathContext especificado, porque o valor não pode ser representado exatamente.

    De qualquer forma, isso é crítico em certos casos. Vamos supor que você tenha um usuário com seis itens no carrinho de compras e ele fará o check-out. Seu sistema precisa calcular impostos e faz isso por item, pois os itens podem ser tributados de maneira diferente. Se você arredondar os impostos para cada item, poderá receber erros de arredondamento de um centavo no nível da transação / carrinho. Uma abordagem para corrigir isso pode ser armazenar impostos em mais casas decimais por item, obter o total de toda a transação e voltar e arredondar cada item para que o imposto total esteja correto (talvez um item seja arredondado para cima, outro para baixo).

O importante a se perceber com tudo isso é que algo tão importante quanto arredondar moedas de um centavo pode ser muito importante para as pessoas certas (por exemplo, alguns de meus clientes anteriores que tiveram que pagar os impostos governamentais sobre vendas em nome de seus clientes). No entanto, todos esses são problemas resolvidos. Lembre-se dos pontos acima e faça algumas experiências por conta própria, e você aprenderá fazendo isso.


fonte
Em 2, acho que a taxa de câmbio é suficiente para obter pelo menos seu próprio número. Você deve considerar que as taxas mudam com o tempo, o que tem algumas maneiras diferentes de lidar.
Izkata 17/03/19
2
+1. Além disso, as moedas mudam com o tempo. No Brasil, tivemos várias mudanças de moeda nas décadas de 80 e 90, cortando zeros e renomeando a moeda quando necessário devido à inflação fora de controle. Isso significa que todos os aspectos de uma moeda podem mudar ao longo do tempo.
Machado
@ Izkata certamente, trabalhei em sistemas que mudam de moeda. Eu queria tocar no tópico, pois é relevante. No entanto, esse não é o foco da pergunta e eu provavelmente poderia digitar outra resposta, desde que esta sobre o tema da troca de moeda.
'"Bibliotecas" são desnecessárias, a menos que a biblioteca padrão do seu idioma esteja ausente em certos tipos de dados, como explicarei.' As moedas podem ter metades, quartos, décimos, oitavos etc., o que significa que pelo menos precisamos de uma representação decimal para elas. Mas estamos completamente certos de que não há moedas com uma peça de 1/3 do todo?
Daniel McLaury 31/03
4

Um lugar onde muitos desenvolvedores são confrontados com uma única representação de dados para qualquer moeda é para compras no aplicativo para aplicativos iOS. Seu cliente pode estar conectado a uma loja em quase qualquer país do mundo. E nessa situação, você receberá um preço de compra que consiste em um número de precisão dupla e um código de moeda.

Você precisa estar ciente de que os números podem ser grandes. Existem moedas em que o equivalente a, digamos, dez dólares, é superior a 100.000 unidades. E temos sorte de não haver moedas como o dólar do Zimbábue no momento, onde você pode ter notas de cem trilhões de dólares!

Para exibir moedas, você precisará de uma biblioteca - você não tem chance de fazer tudo isso sozinho. A exibição depende de duas coisas: o código da moeda e a localidade do usuário. Pense em como os dólares americanos e os canadenses seriam exibidos com um código de idioma dos EUA e um código de idioma canadense: nos EUA, você tem $ vs CAN $ e no Canadá você tem US $ vs $. Espero que esteja embutido no sistema operacional ou você tenha uma boa biblioteca.

Para cálculos, qualquer cálculo terminará com uma etapa de arredondamento. Você terá que descobrir como deve executar esse arredondamento legalmente . Isso não é um problema de programação, é um problema legal. Por exemplo, se você calcular o IVA no Reino Unido, precisará calcular o imposto por item ou por linha de item e arredondá-lo para centavos. O que você arredondar depende da moeda. Mas as regras obviamente dependem do país. Você não pode esperar que um cálculo legalmente correto no Reino Unido seja legalmente correto no Japão e vice-versa.

gnasher729
fonte
0

Em particular, o que precisamos saber para poder armazenar valores em alguma moeda e imprimi-los?

  • "para armazenar valores" : um tipo de dado de número de ponto fixo e um tipo de moeda. Eu acho que isso é bem óbvio. Armazenar em número de ponto não fixo pode envolver alguns arredondamentos e alterar o valor. O cálculo da moeda seria uma questão diferente.
  • "para imprimi-los" : localidade do usuário. Se tiver sorte, você obtém a biblioteca padrão para fazer tudo, por exemplo, símbolo de moeda, separador de milhar, separador de decimais, precisão, etc.

Alguns exemplos de problemas relacionados ao código do idioma:

  • 100 USD na localidade dos EUA deve ser de US $ 100,00, em outra localidade com ponto como separador decimal seria US $ 100,00.
  • Alguns países usam uma miríade de milhares para agrupar números, por exemplo, em vez de 10.000,00, seria apenas 1,0000,00
  • Por razões práticas, as pessoas não imprimem todos os números para moedas pequenas, por exemplo, em vez de US $ 1.000.000.000,00, elas apenas querem que você imprima US $ 1 bilhão.

Outro problema em potencial é que, mesmo que você armazene a moeda corretamente no número de ponto fixo, ela pode precisar ser convertida em número de ponto flutuante, porque a biblioteca usada para impressão suporta apenas ponto flutuante.

imel96
fonte