How to represent money in Javascript?

Asked

Viewed 13,788 times

111

What is the best way to represent monetary units (i.e. money) in Javascript? I know it is not good to use floating point, given the rounding problem, but I do not know what would be the best alternative. Other languages have types Decimal as built-ins, but as far as I know there is nothing standardized (or widely used) for this purpose in Javascript.

So far, I have used integers to represent pennies, and done all formatting/converting on hand. There is a better way, or more "clean"?

Additional details: I’m looking for a canonical answer - since this is a common requirement that many of us need to deal with, and it’s often done incorrectly. A good response can both be a library statement - provided that well based - as to a Guideline to implement manually (if the right answer is "depends", i.e. there is no canonical form for this).

Examples of criteria to be considered (not exhaustive):

  1. Reliability - Does this representation produce correct results in mathematical transactions involving monetary units (e.g., correct rounding)? behaves well in borderline cases?
  2. Interoperability - it is simple to convert to and from a format accepted by the server and/or external systems (usually a decimal without a thousand separator and using the dot . to separate the whole part of the fraction)?
  3. Robustness - if the user enters the data incorrectly (e.g.: using the semicolon incorrectly/inconsistently, placing or not the dollar sign, etc.) the conversion to this format interprets the input correctly and/or fails consistently?
  4. Presentation - it is simple to format this type of data to be presented to the user?
  • 1

    Related: http://ux.stackexchange.com/questions/9105/international-currency-formatting-guidelines-currency-codes

7 answers

71


All the answers add great information, but I would like to give a more direct answer.

Representing a currency (money)

The most suitable way is to use integers (int or long). The reason to avoid floating point numbers are binary representation problems that cause differences in values even in very simple operations.

In addition to Fowler (cited by Leonardo), Bloch also advocates the use of integers in the book Effective Java. Another source is this issue of SOEN.

The idea here is analogous to representing mass in grams rather than kilograms. In the case of Real, we represent the values in cents instead of real. So, the value R$ 10,99 (ten reais and ninety new cents) is represented by 1099 (one thousand ninety-nine cents).

Mathematical and financial operations

The operations of sum, subtraction and multiplication are carried out directly, without any difficulty. However, if there are splitting operations, the final result should be rounded appropriately to integer again.

Rounding is an interesting topic and we cannot be simplistic. The truth is that there is no fixed rule to round up or down. When we talk about money, we are not only considering precision, but who loses and who wins.

One example of legislation affecting rounding is the case of the Consumer Protection Code. The consumer charge should always be rounded down.

Another example used a lot in financial means is the distribution of plots. For example, suppose the calculation of plots of a loan R$ 100,70 three times without interest. In terms of legislation, the customer cannot pay more than the total amount to be charged. Note that the value divided into three parts is equal to 33,5666... and we can’t round up (R$ 33,57), otherwise the customer will pay the total of R$ 100,71 (a penny more). A solution to this is to round the parcel down (R$ 33,56), resulting in a total of R$ 100,68, and the company assumes the loss of 2 cents.

However, many financial institutions do not want these differences polluting accounting. We must remember that each round-off must be justified on an accounting basis. As a result, it is common in systems where I worked we have an adjustment in the last installment. Continuing with the example above, we would have the installments R$ 33,57, R$ 33,57 and R$ 33,56, totaling exactly R$ 100,70.

Conversion between different currencies

The conversion from one currency to the other can follow the same concept of mathematical operations, only by adjusting the basic unit of media.

Presentation to the user

One of the advantages of using integers is separating the internal representation from the visual presentation.

The whole number can be easily formatted using any routine, with the of this SOEN response:

Number.prototype.formatMoney = function(c, d, t){
var n = this, 
    c = isNaN(c = Math.abs(c)) ? 2 : c, 
    d = d == undefined ? "." : d, 
    t = t == undefined ? "," : t, 
    s = n < 0 ? "-" : "", 
    i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "", 
    j = (j = i.length) > 3 ? j % 3 : 0;
   return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
 };

Note that the function must be adapted to extract the two decimal places of the number.

Considerations of the solution

Reliability

The results will always be correct and the solution behaves well in borderline cases, since there are no rounding problems. One should observe only the business rules of how and when round, as mentioned above.

Interoperability

This can be a complex point for any solution, as the number formatted in a input HTML may not be correctly interpreted on the server.

Different technologies and frameworks validate and interpret data differently. The point is that if the numbers are formatted in the Submit, the server must extract all non-numeric characters and then convert the value to an integer number.

If the technology is not a limiting factor, the interoperability of this solution is excellent, even avoiding confusion with semicolons.

Robustness

Data entry robustness is not directly related to the representation form.

However, rationing by thinking of whole numbers can also help in this aspect. You know those systems (like at Atms), where you type the full value from right to left, always specifying the decimal part at the end? Like in this matter. It is possible to format with a plugin, as I did in this fiddle.

Presentation

Item already considered in previous topic. Any formatting routine works as long as you consider that the decimals are represented in the entire part of the number.

Efficiency

Even when there is a "native" type in the language, such as Decimal or BigDecimal, working with integers is more efficient from a computational point of view for obvious reasons.

  • 1

    The other answers (in particular that of Leonardo Otto) are very good, but so far this is the most complete. Too bad in practice this means reinventing the wheel: there are no whole types in Javascript (except for Int32Array and the like), and everything else would have to be done by hand (as I’ve been doing, by the way). Anyway, it’s this kind of Guideline that I was looking for, and if one of the recommended libraries meets these requirements I will leave a mention here.

  • 1

    @mgibsonbr I have analyzed some libraries and the only one that seems to satisfy the question of accuracy is the implementation of BigDecimal Java in Javascript. I didn’t get to test, but I read somewhere that the script is about 300kb! Also, most libraries only care about formatting and currency conversion. It’s a little frustrating.

  • 1

    @mgibsonbr On the other hand, in my experience it is not common to implement financial calculations in Javascript, except simple accounts for user visualization, as a totalizer, but nothing that compromises the final value. All companies in the financial segment I know work with Java or C# and do the calculations in the back end. I don’t know what the people who work with Node.js are doing.

  • This (integer) methodology can be complicated if your product should serve customers who use both two decimal places and four, even worse, if the number of decimal places can be set by the user. For example; R$ 10,99 saw 1099 cents, however if your customer decides to use 3 decimal places (ex.: gas station) 1099 come to represent R$ 1,099! I use bigmoneyjs (which uses the above mentioned bigdecimal), and to format I use the numeraljs. Bigmoney includes the Fowler allocate function, cited in this answer.

  • @renatoargh I think you have confused a little my answer. Do not confuse internal representation with value presentation. Didn’t say the user will see or enter numbers as 1099, that would be just how the number is stored internally. However, it is possible to work with enough precision for the project context and then format as the user wishes. The advantage of this is having speed to perform calculations, something that Bigdecimal leaves to be desired in both Javascript and Java.

  • 1

    @utluiz I understood this, perfectly. I’m saying it doesn’t apply to my project for example, where each of the users can set the price to as many decimal places as they want. For example, one gas station prices have three houses, one exchange company uses eight houses for some operations and less for others, and the other 99% of users use only two. I agree with your answer and for a context where two houses are always used I think the most appropriate to adopt, but I believe that in my case it would be more complicated to deal with this complexity so.

  • @renatoargh Yes, this whole-use solution is a classic of computing, but there are contexts where it doesn’t apply - especially in strictly financial systems where each type of operation uses a different amount of decimals. In these cases integers are even used in the internal representation, but it is necessary to encapsulate it (ex: Java Bigdecimal) to enable the use of different amounts of decimals in different parts of the code.

  • @utluiz Now that I noticed the last part of your reply: "Even when there is a "native" type in the language (Bigdecimal), working with integers is more efficient". If you refer to the Java Bigdecimal, notice that it is already a way of working with integers instead of floating point (it encapsulates an integer and all calculations are done on this integer).

Show 3 more comments

39

Before analyzing your requirements I will add some settings:

Interoperability

Interoperability is the ability of a system (computerised or otherwise) to communicate transparent form (or nearest) with another system (similar or not). To a system to be considered interoperable, it is very important that it works with standards open or ontologies. Wikipedia

Reliability

Reliability or reliability (systemic definition) is the capacity of a person or system to perform and maintain its operation in routine circumstances, as well as in hostile and unexpected circumstances. Wiki.

Robustness

It is the ability of a computer system to handle errors during its execution or the ability of an algorithm to continue operating despite abnormalities of inputs and calculations. Wiki

Your requirements are as follows:

  1. Reliability, first - the type of data/way of calculation should produce correct results

    • You will only achieve library reliability ( aka calculations) if you make one extensive test suite across the library code. This will prove the reliability.
  2. Interoperability when communicating with the server (if applicable);

    • JSON is an interoperable standard for communication between Javascript and the server. I would extensively use JSON for communication
  3. Interoperability/robustness of formats

    • Your API must support multiple forms of input format representation. By example "R$ 1.23", "1.23", "1.23","102.123,30" or fail consistently accepting only the correct shape of the format and stating the exact place of the error. I recommend accepting only one format so as not to cause confusion. Recalling the concept of Ubiquity as recommended by Evans in DDD
  4. The presentation, how to show the user outputs

    • You have a die, and it will have multiple representations. If you have a standard algorithm place inside the object, if it has multiple representations you need to isolate it.

How to implement?

For the Trials:

  • A testing framework [OS in English]

(https://stackoverflow.com/questions/300855/javascript-unit-test-tools-for-tdd/). The testing framework is a personal choice. Usually an aesthetic issue as a function of your API. After choosing it I recommend checking the framework tests. For me it is unacceptable that a testing framework does not use itself to test its functionalities. (Something like: "Who came before: The Egg or the Chicken?") The Accounting I think I have a good one test set.

For modeling:

As recommended by Martin Fowler in his book Patterns of Enterprise Application Architecture you must use:

  1. An integer type with the amount (1000 = R$ 10,00)
  2. Currency type (Real or Dollars - Use currency code).

You should avoid using any type of floating point as this may cause rounding problems which is what you want to avoid. In the calculations you should always take into account the type of currency.

Patterns of Enterprise Application Architecture

Most of the time, people want monetary values rounded to the smallest currency unit, like the cents on the dollar. However, there are times when units fractional are needed.

It is important to make clear with what kind of money you is working, especially in an application that uses both types. It makes sense to have different types for both cases as they behave quite differently as regards arithmetic.

Money is a Value Object, so it must have its equality operations and hash code envelopes to be based on currency and amount.

Money needs arithmetic operations, so you can use objects of this type as easily as using numbers. However, arithmetic operations with money have some important differences in relation to transactions with numbers.

The most obvious, any addition or subtraction needs to be aware of the currency, so that you need to react if you try to add different types of jellyfish. The simplest and most common answer is to treat the addition of incompatible coins to a error. In some more sophisticated situations, you can use Ward Cunningham’s idea of a bag of money. This is an object that contains coins of different types together on an object. This object can then participate in calculation as any object money.

Multiplication and division end up being more complicated due to problems of rounding. When you multiply money, do this with a scalar greatness. If you want to add a 5% fee to an account, multiply it by 0.05 so that you only worry about multiplication by normal numeric types.

There are more details inside the book I can’t pass the whole chapter here for questions legal.

For the performance:

You have multiple representation strategies. Use the Strategy standard for each of representation strategies.

As indicated by Sergio you can use a list with the settings of each type of currency and uses the standard if any different need arises to representation.

And the wheel?

Implementing a robust library for monetary treatment is not so simple, mainly because of the special cases. So we have to look for the wheels before recreate them. Here has a list of Javascript libraries to deal with money and coins. You need to analyze if they meet your points:

  • Extensive testing

  • Easy communication with JSON or an easy way to create a module in the library that does that.

An example

If you find that current libraries do not meet your requirements I will leave a suggested user-level API implementation just out of curiosity

You could use javascript prototypes to do something like a DSL, as follows

"R$ 1,00".money()
"R$ 1,00".add("R$ 2,00")

Or if you like the jQuery style

$$("R$ 1,00").add("R$ 1,00").to("USD")
//Ou
$$("BRL 1.00").add("BRL 1.00").to("USD")
//Ou
$$("BRL 1.00").usd()

Using the actual monetary format would be best:

$$("R$ 1,00").add("R$ 1,00").to("$").mul(3)

It is an interesting solution if you are working only with Brazilian natives, otherwise you have to define the nationality in some way. Because more than one nationality can use '$' as a currency symbol.

  • The second half of the answer is excellent, but I don’t think I expressed myself well on the requirements: 2) interoperability in the sense that the server can receive/send decimals (1.25) while js can use integers as you suggested (125) or other representation; 3) Robustness in the sense that the user can type using semicolon, with or without the R$, etc, and the system has to interpret correctly or fail consistently. Reliability is well covered by the section you mentioned ("multiply by scalar greatness", "pay attention to the type", etc).

  • But how can the user type a semicolon? In Brazilian Portuguese, the comma is a decimal separator and the dot is a hundred separator, if the user misspelles one of these characters, it is his mistake and he needs to learn Portuguese first of all.

  • @Andrade Yes, but this does not give the BD the right to "corrupt" rsrs. That’s why I said "interpret correctly or fail consistently" - All right the system reject incorrect entries, what he can’t find is that 1,000 is 1 and save it.

18

I found an interesting JSON here (and possible original here) that have useful information for formatting money. Missing 3 aspects that would be interesting to have:

  • how to separate from 999, the point is usually used.
  • how to separate decimal values. Some coins don’t seem to even have, but the have doesn’t say how to separate.
  • on which side of the value is the name of the currency. Whether it should be 100£ or £100. I assume this is the second case.

Anyway, using this JSON, here is a function hint to rinse the format :) I updated the function to accept only numbers and return only strings with format x.xxx,xx, to generate at least consistent data.

function lavarDinheiro(moeda, valor) {
    if (typeof valor != 'number') return false; // para garantir que o input é um numero
    valor = ('' + valor).replace(',', '.');
    valor = ('' + valor).split('.');
    var parteInteira = valor[0] + '';
    var parteDecimal = valor[1];

    // tratar a parte inteira
    var rx = /(\d+)(\d{3})/;
    parteInteira = parteInteira.replace(/^\d+/, function (w) {
        while (rx.test(w)) {
            w = w.replace(rx, '$1.$2');
        }
        return w;
    });

    // tratar a parte decimal
    var formatoDecimal = json[moeda].decimal_digits;

    if (parteDecimal) parteDecimal = parteDecimal.slice(0, formatoDecimal);
    else if (!parteDecimal && formatoDecimal) {
        parteDecimal = '';
        while (parteDecimal.length < formatoDecimal) {
            parteDecimal = '0' + parteDecimal;
        }
    }
    return parteDecimal ? [parteInteira, parteDecimal].join(',') : parteInteira;
}

Example

10

Ideal for money representation in is using a more complex data structure, which stores the decimal value, the integer value, the comma separator, etc.

There are several small libraries in to manipulate money that go down this path, saving you the worry of having to take care of it manually:

8

It depends on the size and accuracy of the value you want to deal with:

  • If it’s less than $200 million and cents accuracy, as is the case with many simple applications. use whole numbers, representing CENTS, not real. Enter a comma or dot only when showing the user or printing.

  • If they are values greater than $200 million, you will need to create a "Currency" or similar object, which stores the number

  • If they are values with variable precision (some in cents, others with 4 decimal places etc) a library type Bigdecimal is the path.

6

Instead of storing the value with decimal separator, because do not store the amount of cents for example, ie just determine the minimum amount of money to be stored, and store the multiples of that minimum unit... so there would be no rounding problems with the calculations.

In input, considering '.' or ',' as separator is a good tactic. If the user copies a value from another place, he could consider only the last separator to be the decimal.

In output, ai would have to configure which format to use, as it depends on the target audience of the system.

2

For those who need a quick solution to solve small problems, follow a few simple functions in javascript:

    <script>

    // Abaixo seguem 3 funções genéricas que podem ser utilizadas em qualquer lugar


    // Converte   [valor texto com vírgula para  centavos]    para    [float]

    function textoParaFloat(texto)
    {
        // Retira pontos que separam milhar se existirem. Ex: 1.000,25 vira 1000,25
        texto = texto.replace("." , "");

        // Substitui vírgula separando a casa decimal por ponto ex: 1000,25 vira 1000.25
        texto = texto.replace("," , "."); // isso é necessário para converter para float corretamente

        // Converte de texto para número real
        numero = parseFloat(texto);

        return numero;  // Retorna um número float para ser usado para fazer cálculos    
    }



    // Converte   [valor float com ponto para casa decimal]  para  [texto com vírgula para separar centavos]

    function floatParaTexto(numero)
    {
        numero = numero.toFixed(2); // Duas casas decimais

        texto = numero.toString(); 
        texto = texto.replace("." , ","); // substitui a vírgula por ponto 

        return texto;
    }



    // Apenas prevenção para pessoas que digitam ponto de milhar por costume
    function removePonto(x)
    {
        x.value = x.value.replace('.', '');
    }

    </script>





    Valor: <input type="text" name="valor" id="valor" onkeyup="removePonto(this)"><br>
    <small>Digite apenas números e vírgula para separar os centavos</small>
    <br><br>

    Qtd:   <input type="text" name="qtd"   id="qtd"><br>
    <small>Digite apenas números</small>
    <br><br>

    <input type="button" value="Calcular" onclick="testar();">
    <br><br>
    
    Valor calculado: <span id="teste"></span>




    <script>
    // Essa função abaixo testa as funções genéricas declaradas no bloco de script acima, com os valores digitados

    function testar()
    {
        valor_texto = document.getElementById("valor").value;

        // Convertendo valor_texto com vírgula  para  float  para poder usar em cálculo
        valor_float = textoParaFloat(valor_texto);

        qtd = document.getElementById("qtd").value;
        qtd = parseInt(qtd);

        valor_vezes_qtd = valor_float * qtd; // Esse é um valor númerico calculado

        // converte o valor multiplicado para texto com vírgula
        valor_multiplicado_texto = floatParaTexto(valor_vezes_qtd);

        //exibe o valor multiplicado na tela
        document.getElementById("teste").innerHTML = valor_multiplicado_texto;
    }
    </script>

Browser other questions tagged

You are not signed in. Login or sign up in order to post.