[floating-point] Why not use Double or Float to represent currency?

I've always been told never to represent money with double or float types, and this time I pose the question to you: why?

I'm sure there is a very good reason, I simply do not know what it is.

This question is related to floating-point currency

The answer is


I'll risk being downvoted, but I think the unsuitability of floating point numbers for currency calculations is overrated. As long as you make sure you do the cent-rounding correctly and have enough significant digits to work with in order to counter the binary-decimal representation mismatch explained by zneak, there will be no problem.

People calculating with currency in Excel have always used double precision floats (there is no currency type in Excel) and I have yet to see anyone complaining about rounding errors.

Of course, you have to stay within reason; e.g. a simple webshop would probably never experience any problem with double precision floats, but if you do e.g. accounting or anything else that requires adding a large (unrestricted) amount of numbers, you wouldn't want to touch floating point numbers with a ten foot pole.


Float is binary form of Decimal with different design; they are two different things. There are little errors between two types when converted to each other. Also, float is designed to represent infinite large number of values for scientific. That means it is designed to lost precision to extreme small and extreme large number with that fixed number of bytes. Decimal can't represent infinite number of values, it bounds to just that number of decimal digits. So Float and Decimal are for different purpose.

There are some ways to manage the error for currency value:

  1. Use long integer and count in cents instead.

  2. Use double precision, keep your significant digits to 15 only so decimal can be exactly simulated. Round before presenting values; Round often when doing calculations.

  3. Use a decimal library like Java BigDecimal so you don't need to use double to simulate decimal.

p.s. it is interesting to know that most brands of handheld scientific calculators works on decimal instead of float. So no one complaint float conversion errors.


The result of floating point number is not exact, which makes them unsuitable for any financial calculation which requires exact result and not approximation. float and double are designed for engineering and scientific calculation and many times doesn’t produce exact result also result of floating point calculation may vary from JVM to JVM. Look at below example of BigDecimal and double primitive which is used to represent money value, its quite clear that floating point calculation may not be exact and one should use BigDecimal for financial calculations.

    // floating point calculation
    final double amount1 = 2.0;
    final double amount2 = 1.1;
    System.out.println("difference between 2.0 and 1.1 using double is: " + (amount1 - amount2));

    // Use BigDecimal for financial calculation
    final BigDecimal amount3 = new BigDecimal("2.0");
    final BigDecimal amount4 = new BigDecimal("1.1");
    System.out.println("difference between 2.0 and 1.1 using BigDecimal is: " + (amount3.subtract(amount4)));

Output:

difference between 2.0 and 1.1 using double is: 0.8999999999999999
difference between 2.0 and 1.1 using BigDecimal is: 0.9

American currency can easily be represented with dollar and cent amounts. Integers are 100% precise, while floating point binary numbers do not exactly match floating point decimals.


To add on previous answers, there is also option of implementing Joda-Money in Java, besides BigDecimal, when dealing with the problem addressed in the question. Java modul name is org.joda.money.

It requires Java SE 8 or later and has no dependencies.

To be more precise, there is compile-time dependency but it is not required.

<dependency>
  <groupId>org.joda</groupId>
  <artifactId>joda-money</artifactId>
  <version>1.0.1</version>
</dependency>

Examples of using Joda Money:

  // create a monetary value
  Money money = Money.parse("USD 23.87");

  // add another amount with safe double conversion
  CurrencyUnit usd = CurrencyUnit.of("USD");
  money = money.plus(Money.of(usd, 12.43d));

  // subtracts an amount in dollars
  money = money.minusMajor(2);

  // multiplies by 3.5 with rounding
  money = money.multipliedBy(3.5d, RoundingMode.DOWN);

  // compare two amounts
  boolean bigAmount = money.isGreaterThan(dailyWage);

  // convert to GBP using a supplied rate
  BigDecimal conversionRate = ...;  // obtained from code outside Joda-Money
  Money moneyGBP = money.convertedTo(CurrencyUnit.GBP, conversionRate, RoundingMode.HALF_UP);

  // use a BigMoney for more complex calculations where scale matters
  BigMoney moneyCalc = money.toBigMoney();

Documentation: http://joda-money.sourceforge.net/apidocs/org/joda/money/Money.html

Implementation examples: https://www.programcreek.com/java-api-examples/?api=org.joda.money.Money


If your computation involves various steps, arbitrary precision arithmetic won't cover you 100%.

The only reliable way to use a perfect representation of results(Use a custom Fraction data type that will batch division operations to the last step) and only convert to decimal notation in the last step.

Arbitrary precision won't help because there always can be numbers that has so many decimal places, or some results such as 0.6666666... No arbitrary representation will cover the last example. So you will have small errors in each step.

These errors will add-up, may eventually become not easy to ignore anymore. This is called Error Propagation.


While it's true that floating point type can represent only approximatively decimal data, it's also true that if one rounds numbers to the necessary precision before presenting them, one obtains the correct result. Usually.

Usually because the double type has a precision less than 16 figures. If you require better precision it's not a suitable type. Also approximations can accumulate.

It must be said that even if you use fixed point arithmetic you still have to round numbers, were it not for the fact that BigInteger and BigDecimal give errors if you obtain periodic decimal numbers. So there is an approximation also here.

For example COBOL, historically used for financial calculations, has a maximum precision of 18 figures. So there is often an implicit rounding.

Concluding, in my opinion the double is unsuitable mostly for its 16 digit precision, which can be insufficient, not because it is approximate.

Consider the following output of the subsequent program. It shows that after rounding double give the same result as BigDecimal up to precision 16.

Precision 14
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611

Precision 15
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110

Precision 16
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101

Precision 17
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.000051110111115611011
Double                        : 56789.012345 / 1111111111 = 0.000051110111115611013

Precision 18
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.0000511101111156110111
Double                        : 56789.012345 / 1111111111 = 0.0000511101111156110125

Precision 19
------------------------------------------------------
BigDecimalNoRound             : 56789.012345 / 1111111111 = Non-terminating decimal expansion; no exact representable decimal result.
DoubleNoRound                 : 56789.012345 / 1111111111 = 5.111011111561101E-5
BigDecimal                    : 56789.012345 / 1111111111 = 0.00005111011111561101111
Double                        : 56789.012345 / 1111111111 = 0.00005111011111561101252

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.MathContext;

public class Exercise {
    public static void main(String[] args) throws IllegalArgumentException,
            SecurityException, IllegalAccessException,
            InvocationTargetException, NoSuchMethodException {
        String amount = "56789.012345";
        String quantity = "1111111111";
        int [] precisions = new int [] {14, 15, 16, 17, 18, 19};
        for (int i = 0; i < precisions.length; i++) {
            int precision = precisions[i];
            System.out.println(String.format("Precision %d", precision));
            System.out.println("------------------------------------------------------");
            execute("BigDecimalNoRound", amount, quantity, precision);
            execute("DoubleNoRound", amount, quantity, precision);
            execute("BigDecimal", amount, quantity, precision);
            execute("Double", amount, quantity, precision);
            System.out.println();
        }
    }

    private static void execute(String test, String amount, String quantity,
            int precision) throws IllegalArgumentException, SecurityException,
            IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {
        Method impl = Exercise.class.getMethod("divideUsing" + test, String.class,
                String.class, int.class);
        String price;
        try {
            price = (String) impl.invoke(null, amount, quantity, precision);
        } catch (InvocationTargetException e) {
            price = e.getTargetException().getMessage();
        }
        System.out.println(String.format("%-30s: %s / %s = %s", test, amount,
                quantity, price));
    }

    public static String divideUsingDoubleNoRound(String amount,
            String quantity, int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        String price = Double.toString(price0);
        return price;
    }

    public static String divideUsingDouble(String amount, String quantity,
            int precision) {
        // acceptance
        double amount0 = Double.parseDouble(amount);
        double quantity0 = Double.parseDouble(quantity);

        //calculation
        double price0 = amount0 / quantity0;

        // presentation
        MathContext precision0 = new MathContext(precision);
        String price = new BigDecimal(price0, precision0)
                .toString();
        return price;
    }

    public static String divideUsingBigDecimal(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);
        MathContext precision0 = new MathContext(precision);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0, precision0);

        // presentation
        String price = price0.toString();
        return price;
    }

    public static String divideUsingBigDecimalNoRound(String amount, String quantity,
            int precision) {
        // acceptance
        BigDecimal amount0 = new BigDecimal(amount);
        BigDecimal quantity0 = new BigDecimal(quantity);

        //calculation
        BigDecimal price0 = amount0.divide(quantity0);

        // presentation
        String price = price0.toString();
        return price;
    }
}

Most answers have highlighted the reasons why one should not use doubles for money and currency calculations. And I totally agree with them.

It doesn't mean though that doubles can never be used for that purpose.

I have worked on a number of projects with very low gc requirements, and having BigDecimal objects was a big contributor to that overhead.

It's the lack of understanding about double representation and lack of experience in handling the accuracy and precision that brings about this wise suggestion.

You can make it work if you are able to handle the precision and accuracy requirements of your project, which has to be done based on what range of double values is one dealing with.

You can refer to guava's FuzzyCompare method to get more idea. The parameter tolerance is the key. We dealt with this problem for a securities trading application and we did an exhaustive research on what tolerances to use for different numerical values in different ranges.

Also, there might be situations when you're tempted to use Double wrappers as a map key with hash map being the implementation. It is very risky because Double.equals and hash code for example values "0.5" & "0.6 - 0.1" will cause a big mess.


From Bloch, J., Effective Java, 2nd ed, Item 48:

The float and double types are particularly ill-suited for monetary calculations because it is impossible to represent 0.1 (or any other negative power of ten) as a float or double exactly.

For example, suppose you have $1.03 and you spend 42c. How much money do you have left?

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

prints out 0.6100000000000001.

The right way to solve this problem is to use BigDecimal, int or long for monetary calculations.

Though BigDecimal has some caveats (please see currently accepted answer).


Floats and doubles are approximate. If you create a BigDecimal and pass a float into the constructor you see what the float actually equals:

groovy:000> new BigDecimal(1.0F)
===> 1
groovy:000> new BigDecimal(1.01F)
===> 1.0099999904632568359375

this probably isn't how you want to represent $1.01.

The problem is that the IEEE spec doesn't have a way to exactly represent all fractions, some of them end up as repeating fractions so you end up with approximation errors. Since accountants like things to come out exactly to the penny, and customers will be annoyed if they pay their bill and after the payment is processed they owe .01 and they get charged a fee or can't close their account, it's better to use exact types like decimal (in C#) or java.math.BigDecimal in Java.

It's not that the error isn't controllable if you round: see this article by Peter Lawrey. It's just easier not to have to round in the first place. Most applications that handle money don't call for a lot of math, the operations consist of adding things or allocating amounts to different buckets. Introducing floating point and rounding just complicates things.


I'm troubled by some of these responses. I think doubles and floats have a place in financial calculations. Certainly, when adding and subtracting non-fractional monetary amounts there will be no loss of precision when using integer classes or BigDecimal classes. But when performing more complex operations, you often end up with results that go out several or many decimal places, no matter how you store the numbers. The issue is how you present the result.

If your result is on the borderline between being rounded up and rounded down, and that last penny really matters, you should be probably be telling the viewer that the answer is nearly in the middle - by displaying more decimal places.

The problem with doubles, and more so with floats, is when they are used to combine large numbers and small numbers. In java,

System.out.println(1000000.0f + 1.2f - 1000000.0f);

results in

1.1875

Here are some tips on working with float and decimals.

0.1 x 10 = 1. Seems reasonable, but at computer level you as developer should handle that:

At any programming language (this was tested with Delphi, VBScript, Visual Basic, JavaScript and now with Java/Android):

    double total = 0.0;

    // do 10 adds of 10 cents
    for (int i = 0; i < 10; i++) {
        total += 0.1;  // adds 10 cents
    }

    Log.d("round problems?", "current total: " + total);

    // looks like total equals to 1.0, don't?

    // now, do reverse
    for (int i = 0; i < 10; i++) {
        total -= 0.1;  // removes 10 cents
    }

    // looks like total equals to 0.0, don't?
    Log.d("round problems?", "current total: " + total);
    if (total == 0.0) {
        Log.d("round problems?", "is total equal to ZERO? YES, of course!!");
    } else {
        Log.d("round problems?", "is total equal to ZERO? No...");
        // so be careful comparing equality in this cases!!!
    }

OUTPUT:

 round problems?: current total: 0.9999999999999999
 round problems?: current total: 2.7755575615628914E-17
 round problems?: is total equal to ZERO? No... 

As said earlier "Representing money as a double or float will probably look good at first as the software rounds off the tiny errors, but as you perform more additions, subtractions, multiplications and divisions on inexact numbers, you’ll lose more and more precision as the errors add up. This makes floats and doubles inadequate for dealing with money, where perfect accuracy for multiples of base 10 powers is required."

Finally Java has a standard way to work with Currency And Money!

JSR 354: Money and Currency API

JSR 354 provides an API for representing, transporting, and performing comprehensive calculations with Money and Currency. You can download it from this link:

JSR 354: Money and Currency API Download

The specification consists of the following things:

  1. An API for handling e. g. monetary amounts and currencies
  2. APIs to support interchangeable implementations
  3. Factories for creating instances of the implementation classes
  4. Functionality for calculations, conversion and formatting of monetary amounts
  5. Java API for working with Money and Currencies, which is planned to be included in Java 9.
  6. All specification classes and interfaces are located in the javax.money.* package.

Sample Examples of JSR 354: Money and Currency API:

An example of creating a MonetaryAmount and printing it to the console looks like this:

MonetaryAmountFactory<?> amountFactory = Monetary.getDefaultAmountFactory();
MonetaryAmount monetaryAmount = amountFactory.setCurrency(Monetary.getCurrency("EUR")).setNumber(12345.67).create();
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

When using the reference implementation API, the necessary code is much simpler:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmountFormat format = MonetaryFormats.getAmountFormat(Locale.getDefault());
System.out.println(format.format(monetaryAmount));

The API also supports calculations with MonetaryAmounts:

MonetaryAmount monetaryAmount = Money.of(12345.67, "EUR");
MonetaryAmount otherMonetaryAmount = monetaryAmount.divide(2).add(Money.of(5, "EUR"));

CurrencyUnit and MonetaryAmount

// getting CurrencyUnits by locale
CurrencyUnit yen = MonetaryCurrencies.getCurrency(Locale.JAPAN);
CurrencyUnit canadianDollar = MonetaryCurrencies.getCurrency(Locale.CANADA);

MonetaryAmount has various methods that allow accessing the assigned currency, the numeric amount, its precision and more:

MonetaryAmount monetaryAmount = Money.of(123.45, euro);
CurrencyUnit currency = monetaryAmount.getCurrency();
NumberValue numberValue = monetaryAmount.getNumber();

int intValue = numberValue.intValue(); // 123
double doubleValue = numberValue.doubleValue(); // 123.45
long fractionDenominator = numberValue.getAmountFractionDenominator(); // 100
long fractionNumerator = numberValue.getAmountFractionNumerator(); // 45
int precision = numberValue.getPrecision(); // 5

// NumberValue extends java.lang.Number.
// So we assign numberValue to a variable of type Number
Number number = numberValue;

MonetaryAmounts can be rounded using a rounding operator:

CurrencyUnit usd = MonetaryCurrencies.getCurrency("USD");
MonetaryAmount dollars = Money.of(12.34567, usd);
MonetaryOperator roundingOperator = MonetaryRoundings.getRounding(usd);
MonetaryAmount roundedDollars = dollars.with(roundingOperator); // USD 12.35

When working with collections of MonetaryAmounts, some nice utility methods for filtering, sorting and grouping are available.

List<MonetaryAmount> amounts = new ArrayList<>();
amounts.add(Money.of(2, "EUR"));
amounts.add(Money.of(42, "USD"));
amounts.add(Money.of(7, "USD"));
amounts.add(Money.of(13.37, "JPY"));
amounts.add(Money.of(18, "USD"));

Custom MonetaryAmount operations

// A monetary operator that returns 10% of the input MonetaryAmount
// Implemented using Java 8 Lambdas
MonetaryOperator tenPercentOperator = (MonetaryAmount amount) -> {
    BigDecimal baseAmount = amount.getNumber().numberValue(BigDecimal.class);
    BigDecimal tenPercent = baseAmount.multiply(new BigDecimal("0.1"));
    return Money.of(tenPercent, amount.getCurrency());
};

MonetaryAmount dollars = Money.of(12.34567, "USD");

// apply tenPercentOperator to MonetaryAmount
MonetaryAmount tenPercentDollars = dollars.with(tenPercentOperator); // USD 1.234567

Resources:

Handling money and currencies in Java with JSR 354

Looking into the Java 9 Money and Currency API (JSR 354)

See Also: JSR 354 - Currency and Money


This is not a matter of accuracy, nor is it a matter of precision. It is a matter of meeting the expectations of humans who use base 10 for calculations instead of base 2. For example, using doubles for financial calculations does not produce answers that are "wrong" in a mathematical sense, but it can produce answers that are not what is expected in a financial sense.

Even if you round off your results at the last minute before output, you can still occasionally get a result using doubles that does not match expectations.

Using a calculator, or calculating results by hand, 1.40 * 165 = 231 exactly. However, internally using doubles, on my compiler / operating system environment, it is stored as a binary number close to 230.99999... so if you truncate the number, you get 230 instead of 231. You may reason that rounding instead of truncating would have given the desired result of 231. That is true, but rounding always involves truncation. Whatever rounding technique you use, there are still boundary conditions like this one that will round down when you expect it to round up. They are rare enough that they often will not be found through casual testing or observation. You may have to write some code to search for examples that illustrate outcomes that do not behave as expected.

Assume you want to round something to the nearest penny. So you take your final result, multiply by 100, add 0.5, truncate, then divide the result by 100 to get back to pennies. If the internal number you stored was 3.46499999.... instead of 3.465, you are going to get 3.46 instead 3.47 when you round the number to the nearest penny. But your base 10 calculations may have indicated that the answer should be 3.465 exactly, which clearly should round up to 3.47, not down to 3.46. These kinds of things happen occasionally in real life when you use doubles for financial calculations. It is rare, so it often goes unnoticed as an issue, but it happens.

If you use base 10 for your internal calculations instead of doubles, the answers are always exactly what is expected by humans, assuming no other bugs in your code.


Many of the answers posted to this question discuss IEEE and the standards surrounding floating-point arithmetic.

Coming from a non-computer science background (physics and engineering), I tend to look at problems from a different perspective. For me, the reason why I wouldn't use a double or float in a mathematical calculation is that I would lose too much information.

What are the alternatives? There are many (and many more of which I am not aware!).

BigDecimal in Java is native to the Java language. Apfloat is another arbitrary-precision library for Java.

The decimal data type in C# is Microsoft's .NET alternative for 28 significant figures.

SciPy (Scientific Python) can probably also handle financial calculations (I haven't tried, but I suspect so).

The GNU Multiple Precision Library (GMP) and the GNU MFPR Library are two free and open-source resources for C and C++.

There are also numerical precision libraries for JavaScript(!) and I think PHP which can handle financial calculations.

There are also proprietary (particularly, I think, for Fortran) and open-source solutions as well for many computer languages.

I'm not a computer scientist by training. However, I tend to lean towards either BigDecimal in Java or decimal in C#. I haven't tried the other solutions I've listed, but they are probably very good as well.

For me, I like BigDecimal because of the methods it supports. C#'s decimal is very nice, but I haven't had the chance to work with it as much as I'd like. I do scientific calculations of interest to me in my spare time, and BigDecimal seems to work very well because I can set the precision of my floating point numbers. The disadvantage to BigDecimal? It can be slow at times, especially if you're using the divide method.

You might, for speed, look into the free and proprietary libraries in C, C++, and Fortran.