Split a restaurant bill, including tip

Just looking for a little feedback on the below tip calculator I've coded rounding the output to nearest 2 decimal places.

Just wondered if it was readable and code friendly?

bill = float(input("What was the total bill? $")) tip = float(input("How much tip would you like to give? 10, 12, or 15 ")) no_of_people = float(input("How many people to split the bill?")) cost_of_tip = bill / 100 * tip total_cost = (bill + cost_of_tip) / no_of_people final_per_person = round(total_cost, 2) final_amount = "{:.2f}".format(final_per_person) print(f"Each person should pay:${final_amount}")

New contributor
DexterDevOps is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
• Looks good for beginner code! Very clear. I don't think you need to both round the sum as well as display it with two decimals :2f though, you're kind of doing the same thing twice.
Jan 12 at 19:00
• I once worked for a company that spent a few hundred thousand dollars fixing the "we used float for money mistake. Jan 12 at 22:50
• Floats are fine to split a dinner bill though. Jan 12 at 22:51
• One thing that is not accounted for -- in either question or answers so far -- is that dividing money is tricky business. For example, imagine that I decide to split a $10 bill with 2 other friends: the answer is NOT that we should each pay$3.33 dollars, but instead that 2 should pay $3.33 and one should pay$3.34. Are you interested on feedback on this logic, or are you happy with 3.33 because bill splitting was just an excuse to write some code? Jan 13 at 16:13
• To make certain not to split cents, use floor to round down. If the sum of final_per_person adds up to less than total_cost then report that x cents are outstanding, or distribute the missing cents randomly among the persons. 2 days ago

Confusing variable name. Is the tip an amount or percentage? I assumed the former based on variable name, but a commenter has corrected me.

Keep calculation and presentation logically separate. You don't need to create a variable for the rounded amount. Just compute the per person cost in the natural way. When you are ready to display that value in a human-readable form, format it as needed.

# Get user input.
bill = float(input('Bill: '))
tip_pct = float(input('Tip percentage: '))
npeople = int(input('People: '))  # int() seems more appropriate.

# Calculation.
per_person = bill * (1 + tip_pct / 100.0) / npeople

# Presentation.
message = f'Per person: ${per_person:.2f}' print(message)  Next step: start putting your code in functions. As you try to write programs longer than a few lines, adopt a strict discipline of putting all code in functions and never relying on global variables (constants are fine). There are many reviews on this site emphasizing such points. The logical divisions illustrated above (collecting input, performing calculations, presenting results) will continue to be relevant as you start using functions. Another next step: validating and generalizing the collection of user input. Currently, you ask the user for 3 numbers, but people make mistakes. There are many reviews on this site illustrating how to write a simple function taking a message (eg, "Enter the bill") and returning a validated value, with the opportunity to recover from user errors. • You can infer from the output that the tip is a percentage, not a fixed amount. – ades Jan 12 at 18:57 • @ades When the user is prompted with "How much tip would you like to give? 10, 12, or 15 ", it's not at all obvious that they are asked for a percentage. Once they receive the output f"Each person should pay:${final_amount}", yes, they can infer it was a percentage. But that's a bit late.
– Stef
Jan 13 at 14:59
• If you know how tips work in the US, it's clear this must be a percentage. It would make no sense for $10,$12, or $15 to be the only tip options. 2 days ago • The receipts I've seen all ask for a tip amount, not percentage. If anyone is using the software, copying receipt values into the input, they'll go very wrong. Also, it doesn't make too much sense to ask for "10, 12 or 15" when you're not checking the value anyway. Renaming the prompt to "How much tip .. give (percentage)" would definitely improve UI. yesterday • @nasch who says this is used in the USA? yesterday There's a bug in the logic: If I select 0% tip, it is quite possible that adding the rounded amounts each person pays don't add up to the bill total. You should probably use ceil instead of round, to ensure you round up, never down. Of course giving a negative tip percentage also makes you not pay the bill total, but that would clearly be user error. It's good to guard against user error, but it is not strictly a bug in the code. I would suggest though that on input you make it clear that you are looking for a percentage when asking about the tip. "How much tip would you like to give? 10, 12, or 15 " could be interpreted as a dollar amount rather than a percentage. Get into the habit of localisation instead of hard-coding currency formats and symbols. There's not much of a point to asking for a tip among a list of options if you aren't going to validate it. Even if you were to validate it, I would enforce "between 0 and (perhaps) 100". The tip amount itself isn't used; so just get the total cost directly. Don't round - let locale.currency choose the number of decimal positions. "number of people" isn't a float; it's an int. Suggested from locale import localeconv, setlocale, LC_ALL, currency setlocale(LC_ALL, '') symbol = localeconv()['currency_symbol'] bill = float(input(f"What was the total bill? {symbol}")) tip = float(input("How much tip would you like to give? %")) no_of_people = int(input("How many people to split the bill? ")) total_cost = bill * (1 + tip/100) final_per_person = total_cost / no_of_people final_amount = currency(final_per_person) print(f"Each person should pay: {final_amount}")  • I think the localization overshoots the target here and does so inconsequently. What about localizing the prompts? Jan 13 at 10:05 • @RichardNeumann Which is it - too much ("overshooting") or too little (not enough localisation)? Localisation is not all-or-nothing - it's certainly possible to over-engineer it, but it exists on a continuum. IMO the offered approach is a useful taste for the OP to see how locale-agnostic formatting is done. Jan 13 at 16:16 • Fair enough. I think that the (partial) localization is too much for the task at hand and too little for actual localization, though. Jan 13 at 16:23 • @RichardNeumann how is it only partial localisation? 2 days ago • @theonlygusti because if all you do is localize the currency, then it's still only suitable for English speaking locales. 2 days ago Separate your concerns Split input parsing from your calculation logic. Read and parse the input in one step and do the calculation in a separate function. Use fitting data types. Using float as a data type can have nasty side-effects. Consider using decimal.Decimal instead. Also the three parameters to calculate the bill belong together, since they are only meaningful in their combination for the given use case. Hence, they should be contained in a common data structure. Use the if __name__ == '__main__': guard To prevent your script from executing when being imported for re-use in other scripts. Suggested change from decimal import Decimal from typing import NamedTuple class SplitBill(NamedTuple): """A split bill with a tip.""" bill: Decimal tip: int people: int @classmethod def read(cls): """Interactively read the parameters.""" return cls( Decimal(input("What was the total bill?$")),
int(input("How much tip would you like to give? 10, 12, or 15  ")),
int(input("How many people to split the bill?"))
)

@property
def tip_cost(self) -> Decimal:
"""Return the cost of the tip."""
return self.bill * self.tip / 100

@property
def total_cost(self) -> Decimal:
"""Return the total cost with tip."""
return self.bill + self.tip_cost

@property
def cost_per_person(self) -> Decimal:
"""Return the cost for each person."""
return round(self.total_cost / self.people, 2)

def main() -> None:
"""Reads the values and calculates a split bill with a tip."""

`