1

Beginner in python here. I'm trying to create a program that takes the takes the total price of many things and rounds them up or down depending on their last digit. My issue is that my first "if" statement always gets ignored but all my other "elif" statements work just fine

Code:

if str(finalTotalPrice).endswith("1" or "2") :
    roundedDown = round(finalTotalPrice, 1)
    print("Final Total = $" + str(roundedDown) + str(0))
    print()
    
    cashPayment = float( input("How much cash will you pay with? $"))
    change = (cashPayment - roundedDown)
    change = round(change, 3)
    print("Your change is $" + str(change))

elif str(finalTotalPrice).endswith("8" or "9") :
    roundedUp = round(finalTotalPrice, 1)
    print("Final Total = $" + str(roundedUp) + str(0))
    print()

    cashPayment = float( input("How much cash will you pay with? $"))
    change = (cashPayment - roundedUp)
    change = round(change, 3)
    print("Your change is $" + str(change))

elif str(finalTotalPrice).endswith("5" or "0") :
    print("Final Total = $" + str(finalTotalPrice))
    print()

    cashPayment = float( input("How much cash will you pay with? $"))
    change = (cashPayment - finalTotalPrice)
    change = round(change, 3)
    print("Your change is $" + str(change))


Abdul Hadi
  • 11
  • 2

2 Answers2

3

Python is not natural language. and and or do not behave the way you are used to. Let's look at the documentation:

>>> help(str.endswith)
Help on method_descriptor:

endswith(...)
    S.endswith(suffix[, start[, end]]) -> bool
    
    Return True if S ends with the specified suffix, False otherwise.
    With optional start, test S beginning at that position.
    With optional end, stop comparing S at that position.
    suffix can also be a tuple of strings to try.

Your if statements should look like this:

if str(finalTotalPrice).endswith(("1", "2")):
wizzwizz4
  • 6,140
  • 2
  • 26
  • 62
  • 1
    Probably a more natural articulation anyway would be to convert `finalTotalPrice` to an integer if necessary, then `1 <= finalTotalPrice % 10 <= 2` or `finalTotalPrice % 10 in (1, 2)` – tripleee Dec 18 '20 at 13:38
  • @tripleee I think `finalTotalPrice` is a float – and floats should _never_ be used for money, as we well know. There are many issues with this code. – wizzwizz4 Dec 18 '20 at 13:39
  • @tripleee But feel free to post an answer describing the _proper_ way of doing it. – wizzwizz4 Dec 18 '20 at 13:40
  • Nah, I need to run. – tripleee Dec 18 '20 at 13:41
  • @wizzwizz4 I guess you can use floats but when you need to display them, round them to the nearest 100th, convert them into a string and if needed add a 0 on the end. Floats usually take up less space on the memory/disk than strings, therefore making the code run slightly faster. This 'slightly' would add up if this code was executed lots of times. – Arca Ege Cengiz Dec 18 '20 at 13:57
  • 1
    @ArcaEgeCengiz [You can't use floats for money.](https://husobee.github.io/money/float/2016/09/23/never-use-floats-for-currency.html) – wizzwizz4 Dec 18 '20 at 13:57
  • I think I had said you can round them to the nearest 100th. I can't see a reason why when you add big numbers you won't get the exact result. All the calculations there have the output they are supposed to have normally on the calculator on my computer and on Python shell. – Arca Ege Cengiz Dec 18 '20 at 14:13
  • @ArcaEgeCengiz Just because you don't see the reason doesn't mean it won't happen. Try typing `100200300400500.68` into Python. – wizzwizz4 Dec 18 '20 at 14:17
1

As pointed out by tripleee, you're actually trying to do logically the wrong thing.

roundedDown = round(finalTotalPrice, 1)

This suggests that finalTotalPrice is a float. Never use floats for money. It'll mostly work for small values, but one day things will stop adding up. Instead, use an integer representing the amount of the smallest denomination you have – here, it looks like your dollar is divided into mils (you used round(..., 3)), so a dollar should be represented as 1000.

But can you still use the round function?

>>> help(round)
Help on built-in function round in module builtins:

round(number, ndigits=None)
    Round a number to a given precision in decimal digits.
    
    The return value is an integer if ndigits is omitted or None.  Otherwise
    the return value has the same type as the number.  ndigits may be negative.

Yes, you can; just put in -1 to round to the nearest 10, -2 to round to the nearest 100, etc..

str(finalTotalPrice).endswith("1" or "2")

Ignoring the bug for a moment, this is a bad solution. If you're using floats, it will usually give a completely wrong answer, and if you're using ints it's inefficient. Once you're using ints, you can fix this; finalTotalPrice % 10 will get the last digit, finalTotalPrice % 100 will get the last two digits, etc.. Then you can do:

if finalTotalPrice % 10 in (1, 2):
if finalTotalPrice % 10 > 7:
if finalTotalPrice % 5 == 0:

as necessary.


Additionally, your cash payment code is identical in each if branch, so it should be written after them, not in each branch. And, while we're at it, let's make your variable names conform to PEP 8, the Python Style Guide, and improve input handling:

import re

MONEY_REGEX = re.compile(r"[$]?(\d*)(?:\.(\d+))?")
def input_dollars(prompt="", places=3):
    """Input a dollar amount, returned as an integer.
    
    The prompt string, if given, is passed to the input function.
    
    The places argument determines how many decimal places are allowed.
    The return value is shifted by that many decimal places,
    so that it is an integer.

    >>> input_dollars("prompt? ", places=2)
    prompt? a
    invalid input
    prompt? 
    empty input
    prompt? 0.
    invalid input
    prompt? $32
    3200
    >>> input_dollars("prompt? ", places=2)
    prompt? 32.450
    too many decimal places
    prompt? 32.4
    3240
    >>> input_dollars("prompt? ", places=2)
    prompt? .6
    60
    >>> input_dollars("prompt? ", places=4)
    prompt? $.472
    4720
    """

    fix = 10 ** places
    while True:
        text = input(prompt)
        match = MONEY_REGEX.fullmatch(text)
        if match is None:
            print("invalid input")
            continue
        integer, fractional = match.groups()
        if fractional is None:
            if len(integer) == 0:
                print("empty input")
                continue  
            return int(integer) * fix
        if len(fractional) > places:
            print("too many decimal places")
            continue
        ipart = int(integer) if integer else 0
        fpart = int(fractional.ljust(places, '0'))
        return ipart * fix + fpart

def format_dollars(dollars, places=3):
    fix = 10 ** places
    return "${}.{:0>{}}".format(dollars // fix, dollars % fix, places)

def print_final_total(final_total, places=3):
    print("Final Total =", format_dollars(final_total, places))
    print()

final_total_price = input_dollars("What's the final total price? ")

if final_total_price % 10 in (1, 2, 8, 9):
    print_final_total(round(final_total_price, -2))
elif final_total_price % 5 == 0:
    print_final_total(final_total_price)

cash_payment = input_dollars("How much cash will you pay with? $")
change = cash_payment - final_total_price
print("Your change is", format_dollars(change))

This code probably doesn't do what you want it to. But, then, neither does your original.

wizzwizz4
  • 6,140
  • 2
  • 26
  • 62