2

I'm trying to split a bill and need to calculate how much each person would owe if the bill was split in even amounts. I know one amount will be different than the rest to account for the lost cents.

Assume 3 people try to split a bill for 200. 200 / 3 is 66.6666666667. What I planned on doing was charging the 2 first people 66.67 and the last gets lucky with the 66.66 bill.

At the minute, I have this so far:

private String calculateAmountToPay(String noOfParticipants, String owed) {
    double amountOwed = Double.parseDouble(owed);
    int noOfMembers = Integer.parseInt(noOfParticipants);
    DecimalFormat amountFormat = new DecimalFormat("#.##");
    amountFormat.setRoundingMode(RoundingMode.CEILING);
    return amountFormat.format((amountOwed/(double)noOfMembers) / 100);
  }

But this always will return 66.67. Is there a way that I can get it to only round up if there is a number greater than 2 decimal places, if not, it stays at 66.66 for example?

Maybe I'm approaching this the wrong way. I know currency can be finicky to deal with.

Marco R.
  • 2,667
  • 15
  • 33
Dylan
  • 454
  • 2
  • 16
  • Fascinating. Try `System.out.println(0.05 + 0.05 + 0.05);` – Elliott Frisch May 14 '19 at 22:24
  • 2
    `double` is an entirely inappropriate storage format for currency. I don't think you will be able to get this to work they way you expect without using a decimal storage format of some kind. – Daniel Pryden May 14 '19 at 22:26
  • @DanielPryden I actually store the value as a BigDecimal, but this is part of an android application. At one stage, I need to pass the value that is owed through an intent, which means it needs to be stored as a string at some point. I just converted the BigDecimal to a double when passing through the intent and representing it on screen – Dylan May 14 '19 at 22:31
  • 1
    And what will you do if the total was, say, 199 00 instead of 200.00? Use long int for number of cents assigned to each one, then assign residual cents one at a time until you run out of the residual. – FredK May 14 '19 at 22:38
  • @FredK This is another case. Again, there is a strong possibility I'm not attacking this problem the right way. Maths is not a strong skill of mine so I'm sure I'm missing out on plenty of cases... I'm open to alternative solutions by all means! – Dylan May 14 '19 at 22:41

3 Answers3

2

Before even thinking about arithmetic, you need to know that double is not an appropriate data type for use with currency, because it’s imprecise. So, stop using a floating point type (eg double) as the data type for the quantity of dollars and start using a precise type (eg long) as the data type for the quantity of cents.

The steps then to do the calculation would be to immediately convert everything, with rounding, to cents:

double amountOwed = ...;
int noOfMembers = ...;
long centsOwed = Math.round(amountOwed * 100);
long portionCents = Math.round(amountOwed * 100 / noOfMembers);
long errorCents = portionCents * noOfMembers - centsOwed;

Here’s one way to deal with the error:

long lastPortionCents = portionCents - errorCents;

But it’s possible that the error is more than 1 cent, so a better solution would be to spread the error out evenly by subtracting (or adding if the error is negative) 1 cent from the first (or last, or randomly chosen) errorCents diners.

The rest of the solution is then about rending the above, which I leave to the reader.

As a side note, using cents is how banks transmit amounts (at least for EFTPOS anyway).


Regarding basic software design, I would create a separate method that accepts integer cents and people count as its parameters and returns an array of the "split" amounts. Doing this will not only make your code easier to read, but it will compartmentaise the arithmetic operation and thus enable lots of simple tests to more easily be written, so you can know you have all the edge cases that you can think of covered.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • Excellent answer. Thorough, you explain clearly and concisely why not to use FP data types, and give a clear explanation as a workaround. +1 Mate, cheers! – Dylan May 15 '19 at 13:15
0

You can use BigDecimal with half rounding mode:

    private String calculateAmountToPay(String noOfParticipants, String owed) {
        double amountOwed = Double.parseDouble(owed);
        int noOfMembers = Integer.parseInt(noOfParticipants);
        BigDecimal amount= new BigDecimal((amountOwed / (double) noOfMembers) / 100);
        return amount.setScale(2, RoundingMode.HALF_UP).toString();
    }
0

You can just do all the computation with basic primitives converting everything to cents (2 decimals precision), and dividing the left over cents over a portion of the members, no need to overcomplicate it with extra sdks/math manipulations. The following is a working example solving this problem entirely, using these suggestions:

public class AmountDivider {
    private int totalMembers, luckies, unluckies;
    private double totalAmount, amountPerLucky, amountPerUnlucky;
    
    public AmountDivider(int numMembers, double amountOwed) {
        totalMembers = numMembers;
        totalAmount = amountOwed;

        double centsOwed = amountOwed * 100;
        int centsPerMember = (int)(centsOwed / totalMembers);
        int centsLeft = (int)centsOwed - centsPerMember * totalMembers;
        
        luckies = totalMembers - centsLeft;
        amountPerLucky = centsPerMember / 100.0;
        unluckies = centsLeft;
        amountPerUnlucky = (centsPerMember + 1) / 100.0;
    }

    public String toString() {
        String luckiesStr = String.format("%d lucky persons will pay %.2f", luckies, amountPerLucky);
        String unluckiesStr = String.format("%d unlucky persons will pay %.2f", unluckies, amountPerUnlucky);
        return String.format("For amount %f divided among %d: \n%s\n%s\n", 
                                totalAmount, totalMembers, luckiesStr, unluckiesStr);
    }
    
    public static void main(String[] args) {
        System.out.println(new AmountDivider(3, 200));
        System.out.println(new AmountDivider(17, 365.99));
    }
}

Complete code on GitHub

Hope this helps.

Marco R.
  • 2,667
  • 15
  • 33