Why does using float() remove my formatted decimal places?

I am trying to write a simple program that compares shipping costs. I have a default float value that is premium and two functions that check against it and gives the user the cheapest value based on the weight of their product.

My code is below:

premium_shipping = 125.00

def ground_shipping(weight):
  if weight <= 2.0 and weight >= 0:
    return float('{:.2f}'.format((weight * 1.50) + 20))
  elif weight > 2.0 and weight <= 6.0:
    return float('{:.2f}'.format((weight * 3.00) + 20))
  elif weight > 6.0 and weight <= 10.0:
    return float('{:.2f}'.format((weight * 4.00) + 20))
  elif weight > 10:
    return float('{:.2f}'.format((weight * 4.75) + 20))
  else:
    return "Your package doesn't weigh anything!"

def drone_shipping(weight):
  if weight <= 2.0 and weight >= 0:
    return float('{:.2f}'.format(weight * 4.50))
  elif weight > 2.0 and weight <= 6.0:
    return float('{:.2f}'.format(weight * 9.00))
  elif weight > 6.0 and weight <= 10.0:
    return float('{:.2f}'.format(weight * 12.00))
  elif weight > 10:
    return float('{:.2f}'.format(weight * 14.25))
  else:
    return "Your package doesn't weigh anything!"

def cheapest_shipping(weight):
  if ground_shipping(weight) < drone_shipping(weight) and ground_shipping(weight) < premium_shipping:
    return f'The cheapest shipping method is ground shipping. It would cost {ground_shipping(weight)} to ship your item.'
  elif drone_shipping(weight)  < ground_shipping(weight) and drone_shipping(weight) < premium_shipping:
    return f'The cheapest shipping method is drone shipping. It would cost {drone_shipping(weight)} to ship your item.'
  elif premium_shipping < ground_shipping(weight) and premium_shipping < drone_shipping(weight):
    return f'The cheapest shipping method is premium shipping. It would cost {premium_shipping} to ship your item.'
  else:
    return "Error. You have input an invalid weight."

print(ground_shipping(4.8))
# 34.4
print(cheapest_shipping(4.8))
# The cheapest shipping method is ground shipping. It would cost 34.4 to ship your item.
print(cheapest_shipping(41.5))

When I do this, I technically get my answer however I want it to be at 2 decimal places
When I remove the float() from the two functions, the value I get back is to 2 decimal places but is a str. When I include the float() it returns my number as a float with 1 decimal place and I am unsure on how to change it to include 2 decimal points.

Thanks in advance!

Is it really necessary to cast a float? Why not leave it as a string?

We cannot manipulate float objects. String formatting accommodates floats very nicely, and even rounds correctly.

>>> weight = 16
>>> print (f'{(weight * 14.25):.2f}')
228.00
>>> 
1 Like

Because when the program gets to this code (assuming I leave the premium_shipping as a float) and the values in ground_shipping and drone_shipping without the float() are type str and you cannot compare floats with strings.

def cheapest_shipping(weight):
  if ground_shipping(weight) < drone_shipping(weight) and ground_shipping(weight) < premium_shipping:
    return f'The cheapest shipping method is ground shipping. It would cost {ground_shipping(weight)} to ship your item.'
  elif drone_shipping(weight)  < ground_shipping(weight) and drone_shipping(weight) < premium_shipping:
    return f'The cheapest shipping method is drone shipping. It would cost {drone_shipping(weight)} to ship your item.'
  elif premium_shipping < ground_shipping(weight) and premium_shipping < drone_shipping(weight):
    return f'The cheapest shipping method is premium shipping. It would cost {premium_shipping} to ship your item.'
  else:
    return "Error. You have input an invalid weight."

If I change them to both be type string (with the correct decimal placement) for both scenarios:

print(type (premium_shipping))
print(type(ground_shipping(4.38)))

print(cheapest_shipping(4.8))
print(cheapest_shipping(41.5))

I get the incorrect answer:

<class 'str'>
<class 'str'>
The cheapest shipping method is premium shipping. It would cost 125.00 to ship your item.
The cheapest shipping method is premium shipping. It would cost 125.00 to ship your item.

Ah, yes. I would still return the formatted string, then cast a copy to float. Then you can still display the currency format.

I am trying to assign the value of my 2 functions into a variable which I can then turn into a float but I am getting errors when I try this:

final_ground_shipping = float(ground_shipping(weight))
final_drone_shipping = float(drone_shipping(weight))

Is this what you meant? If so, how would I get this to work without getting a NameError?

Not sure how the NameError arises. Can we have another look at your revised code?

Here is my updated code:

premium_shipping = 125.00

def ground_shipping(weight):
  if weight <= 2.0 and weight >= 0:
    return '{:.2f}'.format((weight * 1.50) + 20)
  elif weight > 2.0 and weight <= 6.0:
    return '{:.2f}'.format((weight * 3.00) + 20)
  elif weight > 6.0 and weight <= 10.0:
    return '{:.2f}'.format((weight * 4.00) + 20)
  elif weight > 10:
    return '{:.2f}'.format((weight * 4.75) + 20)
  else:
    return "Your package doesn't weigh anything!"

def drone_shipping(weight):
  if weight <= 2.0 and weight >= 0:
    return '{:.2f}'.format(weight * 4.50)
  elif weight > 2.0 and weight <= 6.0:
    return '{:.2f}'.format(weight * 9.00)
  elif weight > 6.0 and weight <= 10.0:
    return '{:.2f}'.format(weight * 12.00)
  elif weight > 10:
    return '{:.2f}'.format(weight * 14.25)
  else:
    return "Your package doesn't weigh anything!"

def cheapest_shipping(weight):
  if float(ground_shipping(weight)) < float(drone_shipping(weight)) and float(ground_shipping(weight)) < premium_shipping:
    return f'The cheapest shipping method is ground shipping. It would cost ${ground_shipping(weight)} to ship your item.'
  elif float(drone_shipping(weight)) < float(ground_shipping(weight)) and float(drone_shipping(weight)) < premium_shipping:
    return f'The cheapest shipping method is drone shipping. It would cost ${drone_shipping(weight)} to ship your item.'
  elif premium_shipping < float(ground_shipping(weight))  and premium_shipping < float(drone_shipping(weight)):
    return f'The cheapest shipping method is premium shipping. It would cost ${premium_shipping} to ship your item.'
  else:
    return "Error. You have input an invalid weight."


print(cheapest_shipping(4.8))
print(cheapest_shipping(41.5))

updates: I added float() in the if statements for cheapest_shipping() which got the correct values to display for the first instance.

The cheapest shipping method is ground shipping. It would cost $34.40 to ship your item.

However, now for the second instance its displaying this with the incorrect decimal places:

The cheapest shipping method is premium shipping. It would cost $125.0 to ship your item.

So now I’m back to trying to figure out how to get premium_shipping to display correctly.

One way would be to format it in advance.

 prem_form = f'{premium_shipping:.2f}'
1 Like

Ok, finally figured it out:

I formatted the float after its been compared in the elif statement:

 return f'The cheapest shipping method is premium shipping. It would cost ${"%2.2f" % premium_shipping} to ship your item.'

Thus returning:

The cheapest shipping method is premium shipping. It would cost $125.00 to ship your item.

:slight_smile:

1 Like

Once you have everything working, look through your code and see if there are any ways to compact it by paring out repetition and lightening the code overhead.


Edit 2100hzMDT

We could switch from string to float and deal with the string at the endpoint, rather than in the delivery chain.

Compare one to the other…

def ground_shipping(weight):
  if weight <= 2.0 and weight >= 0:
    return f'{(weight * 1.50 + 20):.2f}'
  elif weight > 2.0 and weight <= 6.0:
    return f'{(weight * 3.00 + 20):.2f}'
  elif weight > 6.0 and weight <= 10.0:
    return f'{(weight * 4.00 + 20):.2f}'
  elif weight > 10:
    return f'{(weight * 4.75 + 20):.2f}'
  else:
    return "Your package doesn't weigh anything!"

pft = drone_shipping(16)
print (pft)
d = float(pft)

def drone_shipping(weight):
  if weight <= 2.0 and weight >= 0:
    return f'{(weight * 4.50):.2f}'
  elif weight > 2.0 and weight <= 6.0:
    return f'{(weight * 9.00):.2f}'
  elif weight > 6.0 and weight <= 10.0:
    return f'{(weight * 12.00):.2f}'
  elif weight > 10:
    return f'{(weight * 14.25):.2f}'
  else:
    return "Your package doesn't weigh anything!"

pft = drone_shipping(16)
print (pft)
d = float(pft)
def ground_shipping(weight):
  if weight <= 2.0 and weight >= 0:
    return weight * 1.50 + 20
  elif weight > 2.0 and weight <= 6.0:
    return weight * 3.00 + 20
  elif weight > 6.0 and weight <= 10.0:
    return weight * 4.00 + 20
  elif weight > 10:
    return weight * 4.75 + 20
  else:
    return "Your package doesn't weigh anything!"

g = ground_shipping(16)
pft = f'{g:.2f}'
print (pft)

def drone_shipping(weight):
  if weight <= 2.0 and weight >= 0:
    return weight * 4.50
  elif weight > 2.0 and weight <= 6.0:
    return weight * 9.00
  elif weight > 6.0 and weight <= 10.0:
    return weight * 12.00
  elif weight > 10:
    return weight * 14.25
  else:
    return "Your package doesn't weigh anything!"

d = drone_shipping(16)
pft = f'{d:.2f}'
print (pft)
1 Like

Any re-examination is going to reverse engineer the logic. One thing that came to mind immediately upon seeing, elif weight > 10: is the order. This is an outside case. There isn’t another one after it. Thinking it through we can apply it first, rather than last. It’s either greater than ten or it isn’t.

x > 10
x > 6
x > 2
x > 0

No ANDs about it. Nothing between. A natural progression.

Can you elaborate more on this? Are you saying there should be no AND statements? Even if we reverse the order, wouldn’t doing something like x = 7 trigger all the conditions from x >6 and under since it would satisfy all the conditions.

x = 7 is greater than x >6 and also x >2 and x >0?

Not as such. AND expressions fit a logical need which in some cases can be simplified. We cannot think in terms of should or shouldn’t. Let the need dictate.

There won’t be any overlap if each branch is a return…

def ground_shipping(weight):
  if weight > 10:
    return weight * 4.75 + 20
  elif weight > 6:
    return weight * 4.00 + 20
  elif weight > 2:
    return weight * 3.00 + 20
  elif weight > 0:
    return weight * 1.50 + 20
  else:
    return "Your package doesn't weigh anything!"

g = ground_shipping(16)
pft = f'{g:.2f}'
print (pft)

def drone_shipping(weight):
  if weight > 10:
    return weight * 14.25
  elif weight > 6:
    return weight * 12.00
  elif weight > 2:
    return weight * 9.00
  elif weight > 0:
    return weight * 4.50
  else:
    return "Your package doesn't weigh anything!"

d = drone_shipping(16)
pft = f'{d:.2f}'
print (pft)

The one function we haven’t examined yet is cheapest_shipping(). Notice how much repetition there is? We can simplify this by resolving the three prices up front, then compare, then return a single response.

Consider,

premium_shipping = 125.0

def cheapest_shipping_method(weight):
    if weight <= 0:
        return f'Error {weight} is an invalid weight.'
    g = ground_shipping(weight)
    d = drone_shipping(weight)
    p = premium_shipping
    if p < g and p < d:
        c = p; m = 'premium'
    elif d < g and d < p:
        c = d; m = 'drone'
    else:
        c = g; m = 'ground'
    return f'The cheapest shipping method for {weight} lb is {m} shipping.\n\
It will cost ${c:.2f} to ship your item.'

print (cheapest_shipping_method(0))
print (cheapest_shipping_method(1))
print (cheapest_shipping_method(2))
print (cheapest_shipping_method(6))
print (cheapest_shipping_method(16))
print (cheapest_shipping_method(26))
>>> 
= RESTART: C:/Users/../Scripts/shipping_method.py
96.00
228.00
Error 0 is an invalid weight.
The cheapest shipping method for 1 lb is drone shipping.
It will cost $4.50 to ship your item.
The cheapest shipping method for 2 lb is drone shipping.
It will cost $9.00 to ship your item.
The cheapest shipping method for 6 lb is ground shipping.
It will cost $38.00 to ship your item.
The cheapest shipping method for 16 lb is ground shipping.
It will cost $96.00 to ship your item.
The cheapest shipping method for 26 lb is premium shipping.
It will cost $125.00 to ship your item.
>>> 

Technically this function checks the weight so the other two do not need to. They are only called if the weight is valid. Later on we will learn about exception handling as a means of trapping invalid inputs.


Edited 2020-08-18