When a software developer sees a requirement to store prices and do price calculations he has to decide which type he’s going to use.

Data Type |
Default Value (for fields) |
---|---|

byte | 0 |

short | 0 |

int | 0 |

long | 0L |

float | 0.0f |

double | 0.0d |

char | ‘\u0000’ |

String (or any object) | null |

boolean | false |

Since price is usually explained in Euros (Dollars, Hryvnas, Bitcoins) it has an integer part and a decimal part. So the natural choice is to skip `int`

and take `double`

or `float`

.

This, however, has some consequences that are often learned the hard way. If you already forgot CS 101 here is a reminder what is wrong with doubles.

If you draw possible value spaces, they will look like this:

so the space of doubles is much bigger than the space of prices. That means when you do calculations with doubles you may get **something that is not a price**.

## Example

First, an easy example that starts challenging your belief in math:

System.out.println(8.97); System.out.println(8.97 + 4.95 - 4.95);

The result is not as you have expected:

8.97 8.970000000000002

This one is pretty clear but what if your task is to make some more calculations. Suppose you have the following problem statement:

Calculate discount between old and new price and show as a percentage without decimal signs.

Additional requirement: always round percentage down.

### Solution that is about almost correct

public int getPriceDifferenceInPercentage(Double oldPrice, Double price) { return new BigDecimal((oldPrice - price) * 100 / oldPrice) .setScale(0, BigDecimal.ROUND_DOWN).intValue(); }

The code looks innocent enough. We do discount calculation, convert to BigDecimal, round down. What could possibly go wrong?

Well, it turns out 14.95 – 8.97 in doubles is not 5.98 but 5.979999999999999 and it goes down from there because we round it down.

### Correct solutions

The first solution would be to immediately convert the price from `double`

to `BigDecimal`

with scale 2 and do calcluation is BigDecimals. This makes the code a little bulky but solves the problem:

public int getPriceDifferenceInPercentage(Double oldPrice, Double price) { // Convert doubles to decimals with the scale of 2 to make a precise calculation. BigDecimal oldPriceDecimal = new BigDecimal(oldPrice) .setScale(2, BigDecimal.ROUND_HALF_UP); BigDecimal priceDecimal = new BigDecimal(price) .setScale(2, BigDecimal.ROUND_HALF_UP); BigDecimal percentage = oldPriceDecimal .subtract(priceDecimal) .divide(oldPriceDecimal, 2, BigDecimal.ROUND_DOWN); return percentage.multiply(new BigDecimal(100)).intValue(); }

The second approach is to do all the computations in doubles but do rounding with 1 decimal digit before converting it to the end result. This will often be enough to eliminate the imprecision you got during calculations.

public int getPriceDifferenceInPercentage(Double oldPrice, Double price) { return new BigDecimal((oldPrice - price) * 100 / oldPrice).setScale(1, BigDecimal.ROUND_HALF_UP).intValue(); // ^^ fix imprecision problem ^^ }

Lesson to learn: price is not a double. Use precise numeric types when you’re working with it.