Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Decimal Rounding error
#1
Hi,

I've got a code written where the purpose is to take a raw price value and apply 10% discount to it. If the user agrees, then a 2nd discount of 5% is added as an additional discount to the previous discount price. If not, the program exits.

The code works, but I'm having difficulties with rounding the prices to 2 decimals. In the first function, the price correctly displays 2 decimals, e.g. if I enter 85.49, the function will display $76.94 as the 10$ discount price.

However, the returned value from this function will be 76.941. When this is then passed into teh 2nd function for applying the 5% discount, the function displays $73.09394999999999, and returns $73.09394999999999

Any ideas?

from decimal import Decimal


#func 1: student discount which discounts the current price to 10%.
def student_discount(price):
    s_discount = price * .9 # represents 10% discount
    display_price = s_discount
    num = Decimal(s_discount)
    display_price = round(num,2)
    display_price = "${fprice}".format(fprice=display_price) # takes data type in format and auto-coverts the return as a string
    print(">> Your new price = ", display_price)
    return s_discount

#func 2: additional discount for regular buyers which discounts an additional 5% on the current student discounted price
def additional_discount(s_discount):
    total_discount = s_discount * .95 #represents a 5% discount on the already reduced 10% discount
    display_price = total_discount
    num = Decimal(total_discount)
    display_price = round(num,2)
    display_price = "${fprice}".format(fprice=total_discount)  # takes data type in format and auto-coverts the return as a string
    print(">> Total discount = ", display_price)
    return total_discount

#enter retail value of a product
price = float(input("Enter price: "))

#pass raw price into func 1 for 10% reduction
s_discount = student_discount(price)
print("s_discount = ",s_discount)

#ask user if further 5% should be included
user_input = input("\nInclude another 5% discount? Press 'y' or 'n': ")
if user_input == 'y':
    # pass the 10% price reduction outcome into func 2 for further 5% reduction
    total_discount = additional_discount(s_discount)
    print("Total_discount = ", total_discount)

else:
    print("No further discounts, final price was student-only discount: ", "${fprice}".format(fprice=s_discount))
Reply
#2
The reason for your problem is you don't round discount. You go to a lot of trouble rounding display_price, but discount is price * .9.

You should not be using Decimal to display 2 digits. Specify you want two digits of precision when printing the values, but leave the values as float numbers.
print(f'>> Total discount = ${s_discount:1.2f}')
The reason you are getting funny numbers with 14 decimal places is that binary floating point < real numbers. When Python creates a floating point number it has to stuff the result in a 64 bit bucket (size of a float number). 64 bits can hold a lot of different values but the number of real numbers is infinite. Odds are that Python cannot store exactly most float values, so it picks the closest number it can store. All those decimal places getting printed is a result of this. Don't worry about the decimals and fix the "problem" when you print the values.
Reply
#3
Thanks for your help. I fixed the issue by rounding s_discount in a separate function, and used a similar method in the additional discount function. I was finally able to separate the raw data from the display data.

"""**********
Functions
"""
#func 1: student discount which discounts the current price to 10%.
def student_discount(price):
    s_discount = price * .9  # represents 10% discount
    return s_discount #returns raw price


# used for display only, receives raw student discount
def student_rounding_display(display_price):
    display_price = round(display_price, 2) #round to 2 decimals
    display_price = "${fprice}".format(fprice=display_price)  # takes data type in format and auto-coverts the return as a string, joins with manual inserted $
    return display_price


#func 2: additional discount for regular buyers which discounts an additional 5% on the current student discounted price
# receives raw student discount, and prints display price
def additional_discount(s_discount):
    total_discount = s_discount * .95 #represents a 5% discount on the already reduced 10% discount
    display_price = total_discount
    display_price = round(display_price,2) #round to 2 decimals
    display_price = "${fprice}".format(fprice=display_price)  # takes data type in format and auto-coverts the return as a string
    print(">> Total discount = ", display_price)
    #return total_discount   <--uncomment only if you'd need to use and return the non-rounded varaible, which you don't in this problem

"""**********
Main code
"""

#enter retail value of a product
price = float(input("Enter price: "))

#pass raw price into func 1 for 10% reduction
s_discount = student_discount(price)
print(">> Student discount price = ", student_rounding_display(s_discount))

#ask user if further 5% should be included
user_input = input("\nInclude another 5% discount? Press 'y' or 'n': ")

if user_input == 'y':
    # pass the 10% price reduction outcome into func 2 for further 5% reduction
    additional_discount(s_discount)
else:
    print(">> No further discounts, final price was student-only discount: ", student_rounding_display(s_discount))
Reply
#4
Your code should look like this:
"""Calculate discounts for input price"""

def student_discount(price):
    """Take 10% discount off price"""
    return price * 0.9

def additional_discount(price):
    """Take 5% discount off price"""
    return price * 0.95
 
price = float(input("Enter price: "))

# Automatically apply student discount
final_price = student_discount(price)

# Ask if addition discount should be applied
if (input("\nInclude another 5% discount? Press 'y' or 'n': ")) == 'y':
    final_price = additional_discount(final_price)
print(f'Final price after discounts ${final_price:1.2f}')
The price should always be a float, and you round the price display in the print command.

Use docstrings to comment your function instead of the line comment character. This makes the function comments stand out more, makes them easier to write because you don't need a # at the start of each line, and works with Python help and other documentation tools to automatically create documentation.

Do not use docstrings the way you did in your code, as a wrapper around some section heading. Docstrings should only wrap text that you would expect to appear in a manual. Each module (file) should have a docstring at the top to describe the purpose of the module, a docstring at the start of each class (you'll learn about those eventually) and a docstring at the start of each function.
Lines in a Python program should never exceed 79 characters. I found your code difficult to work with because multiple lines ran of the right edge of my editor.
Reply
#5
Wow, that really took out a lot of my extra code. I get it now, thanks for your help!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  need help rounding joseph202020 7 1,257 Feb-21-2023, 08:13 PM
Last Post: joseph202020
  from numpy array to csv - rounding SchroedingersLion 6 2,063 Nov-14-2022, 09:09 PM
Last Post: deanhystad
  Random data generation sum to 1 by rounding juniorcoder 9 3,347 Oct-20-2021, 03:36 PM
Last Post: deanhystad
  Rounding issue kmll 1 1,379 Oct-08-2021, 10:35 AM
Last Post: Yoriz
  Not rounding to desired decimal places? pprod 2 2,503 Mar-05-2021, 11:11 AM
Last Post: pprod
  rounding and floats Than999 2 3,046 Oct-26-2020, 09:36 PM
Last Post: deanhystad
  Rounding to the nearest eight wallgraffiti 2 2,018 Jul-15-2020, 06:05 PM
Last Post: wallgraffiti
  rounding question DPaul 16 5,433 Apr-12-2020, 02:30 PM
Last Post: DPaul
  price + tax rounding mlieqo 11 6,333 Sep-21-2019, 04:53 PM
Last Post: mlieqo
  rounding floats to a number of bits Skaperen 2 2,271 Sep-13-2019, 04:37 AM
Last Post: Skaperen

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020