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

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!

There is no difference between the float values 2, 2.0, 2.00000000000000000, and 200e-2. If you want to present a float in a certain way, that's best done at formatting time, with any of (in order of preference):
>>> pi = 3.14159
>>> f"{pi:.2f}" # f-strings, introduced in Python 3.6.
'3.14'
>>> "{:.2f}".format(pi) # str.format, use f-strings in preference.
'3.14'
>>> "%.2f" % (pi) # VERY old method, avoid if possible.
'3.14'
Provided you're using a recent enough version of Python, f-strings are the way to go. Unlike % and str.format(), they keep the data item localised to where it will be printed in the string so you don't have to go searching for it in the argument list. Contrast the following:
f"Hello, {name}, today is {dayOfWeek}"
"Hello, {}, today is {}".format(name, dayOfWeek)

You seem to have some confusion between what a float is and how it is displayed. A float is a binary number with 53 bits of decimal precision that can hold a wide variety of values. You can display a float however you want. For example:
float('{:.2f}'.format(weight * 14.25))
Here you take a float value weight * 14.25, convert it to a string with two decimal places using the format method, and then back with the float function. This may or may not truncate the digits after the second decimal place, since most decimal fractions are not representable exactly in binary.
You display your values without the same consideration, however:
print(ground_shipping(4.8))
If you want to print this as a two-digit number, you should format it the same way as you did previously:
print(f'{ground_shipping(4.8):0.2f}')
OR
print('{:0.2f}'.format(ground_shipping(4.8)))

Related

Calculate a number to the power of a large integer

I'm trying to help someone calculate the value of 4^3e9.
The problem is that most software don't support numbers this large. Is there an alternative way to calculate this?
My first attempt is to try to divide the number as it is being calculated by looping from 1 to 3e9. If the intermediate result is > 10 then I divide it by it's power of 10, then add this to a variable. In the end I will have a floating point number and the power of 10.
import math
powerof10 = 0
powerof = int(3e9)
# print('powerof', powerof)
initial_value = 4
float_value = initial_value
for i in range(1,powerof): #start from 1 to get correct number of operations
float_value *= initial_value
# print('float value', float_value)
print(i)
if (float_value > 10):
powerof10increment = math.floor(math.log10(float_value))
# print('powerof10', powerof10increment)
powerof10 += powerof10increment
float_value /= 10**powerof10increment
# print('reduced float value', float_value)
print(float_value, ' x 10^', powerof10)
This based on this question here: I want to know what is the value of 4 to the power of 3000000000 (3e+9)
According to the question the number should be in the format 1 x 10^x, so I think only x is required.
Thanks to #NickODell for his comment!
import math
powerof10 = (3e9)*math.log(4)/math.log(10)
print(powerof10)
significand = 10**(powerof10 - math.floor(powerof10))
print(significand, 'x 10^', math.floor(powerof10))
# 9.63578180503111 x 10^ 1806179973
Which matches with the result I get from Wolfram Alpha.

Added second value to return and i don't know what's missing in the function's paramater to pass?

Need help with this assignment, python newbie here, 30 minutes till deadline and i couldn't figure out what's missing. my teacher said it won't work like this.
updated,
if 0 < amount <= 20:
return 0.005, 0
if 20 < amount <= 50:
return 0.893, 0
elif 50 <= amount <= 100:
return 1.000, 0
elif 100 <= amount <= 200:
return 1.900, 0
elif 200 <= amount <= 300:
return 2.800, 0
elif 300 <= amount <= 500:
return 3.500, 0
elif 500 <= amount <= 700:
return 4.000, 0
elif 700 <= amount <= 1000:
return 4.750, 0
else:
return 5.500, 0
I think your problem is here
def has_enough_balance(balance, amount, transaction_type):
return balance >= calculate_fee(amount, transaction_type) + amount
calculate_fee() returns a tuple , in your has_enough_balance() function, you add the amount to it, you cant add a numeric and a tuple
index calculate_fee(amount, transaction_type) to choose which variable you want to use there and your code should be fine
"depends on how and where the function should be used later, it's an abstract function "
you could edit your has_enough_balance() function to
def has_enough_balance(balance, amount, transaction_type, fee_type):
index_dict = {"sender":0, "receiver":1}
return balance >= (calculate_fee(amount, transaction_type)[index_dict[fee_type]]) + amount
basically since you have calculate_fee() returning a tuple, you only want to use one of those values at a time.
you can follow the tuple with [*index of the value you want to use*] to grab the correct value. so I added fee_type as a argument for has_enough_balance() and a dictionary inside to let you say whether you want to use your function for the sender fee or the receiver fee, and the dictionary will match it to its corresponding index, and [index_dict[fee_type]] grabs the correct slice out of calculate_fee()
Your calculate_fee method returns two variables.
In python, when you return multiple variables that's a tuple (example: (0, 0))
But in this block of code
# Checking if client has enough balance before request is sent
def has_enough_balance(balance, amount, transaction_type):
return balance >= calculate_fee(amount, transaction_type) + amount
You try to compare it to a single variable balance and you also try to add it to a single variable amount. These operations are not supported between a tuple, and an int.
From your comment i saw that calculate_fee returns the fee for the sender and the fee for the receiver.
You could edit your has_enough_balance function with a boolean parameter forSender to know if you're calculating the amount for the sender or for the receiver.
You could then use the value of this parameter to know which of the two fees you need to add in your calculation.
# Checking if client has enough balance before request is sent
def has_enough_balance(balance, amount, transaction_type, forSender):
senderFee, receiverFee = calculate_fee(amount, transaction_type)
fee = senderfEE if forSender else receiverFee
return balance >= fee + amount

Python format percentages

I use the following snippet for converting a ratio into a percentage:
"{:2.1f}%".format(value * 100)
This works as you would expect. I want to extend this to be more informative in edge cases, where the rounded ratio is 0 or 1, but not exactly.
Is there a more pythonic way, perhaps using the format function, to do this? Alternatively I would add a clause similar to:
if math.isclose(value, 0) and value != 0:
return "< 0.1"
I'd recommend running round to figure out if the string formatting is going to round the ratio to 0 or 1. This function also has the option to choose how many decimal places to round to:
def get_rounded(value, decimal=1):
percent = value*100
almost_one = (round(percent, decimal) == 100) and percent < 100
almost_zero = (round(percent, decimal) == 0) and percent > 0
if almost_one:
return "< 100.0%"
elif almost_zero:
return "> 0.0%"
else:
return "{:2.{decimal}f}%".format(percent, decimal=decimal)
for val in [0, 0.0001, 0.001, 0.5, 0.999, 0.9999, 1]:
print(get_rounded(val, 1))
Which outputs:
0.0%
> 0.0%
0.1%
50.0%
99.9%
< 100.0%
100.0%
I don't believe there is a shorter way to do it. I also wouldn't recommend using math.isclose, as you'd have to use abs_tol and it wouldn't be as readable.
Assuming Python 3.6+, marking exactly zero or exactly 100% could be done with:
>>> for value in (0.0,0.0001,.9999,1.0):
... f"{value:6.1%}{'*' if value == 0.0 or value == 1.0 else ' '}"
...
' 0.0%*'
' 0.0% '
'100.0% '
'100.0%*'

Python internal metric unit conversion function

I'm trying to build a function to do internal metric conversion on a wavelength to frequency conversion program and have been having a hard time getting it to behave properly. It is super slow and will not assign the correct labels to the output. If anyone can help with either a different method of computing this or a reason on why this is happening and any fixes that I cond do that would be amazing!
def convert_SI_l(n):
if n in range( int(1e-12),int(9e-11)):
return n/0.000000000001, 'pm'
else:
if n in range(int(1e-10),int(9e-8)):
return n/0.000000001 , 'nm'
else:
if n in range(int(1e-7),int(9e-5)):
return n/0.000001, 'um'
else:
if n in range(int(1e-4),int(9e-3)):
return n/0.001, 'mm'
else:
if n in range(int(0.01), int(0.99)):
return n/0.01, 'cm'
else:
if n in range(1,999):
return n/1000, 'm'
else:
if n in range(1000,299792459):
return n/1000, 'km'
else:
return n , 'm'
def convert_SI_f(n):
if n in range( 1,999):
return n, 'Hz'
else:
if n in range(1000,999999):
return n/1000 , 'kHz'
else:
if n in range(int(1e6),999999999):
return n/1e6, 'MHz'
else:
if n in range(int(1e9),int(1e13)):
return n/1e9, 'GHz'
else:
return n, 'Hz'
c=299792458
i=input("Are we starting with a frequency or a wavelength? ( F / L ): ")
#Error statements
if i.lower() == ("f"):
True
else:
if not i.lower() == ("l"):
print ("Error invalid input")
#Cases
if i.lower() == ("f"):
f = float(input("Please input frequency (in Hz): "))
size_l = c/f
print(convert_SI_l(size_l))
if i.lower() == ("l"):
l = float(input("Please input wavelength (in meters): "))
size_f = ( l/c)
print(convert_SI_f(size_f))
You are using range() in a way that is close to how it is used in natural language, to express a contiguous segment of the real number line, as in in the range 4.5 to 5.25. But range() doesn't mean that in Python. It means a bunch of integers. So your floating-point values, even if they are in the range you specify, will not occur in the bunch of integers that the range() function generates.
Your first test is
if n in range( int(1e-12),int(9e-11)):
and I am guessing you wrote it like this because what you actually wanted was range(1e-12, 9e-11) but you got TypeError: 'float' object cannot be interpreted as an integer.
But if you do this at the interpreter prompt
>>> range(int(1e-12),int(9e-11))
range(0, 0)
>>> list(range(int(1e-12),int(9e-11)))
[]
you will see it means something quite different to what you obviously expect.
To test if a floating-point number falls in a given range do
if lower-bound <= mynumber <= upper-bound:
You don't need ranges and your logic will be more robust if you base it on fixed threshold points that delimit the unit magnitude. This would typically be a unit of one in the given scale.
Here's a generalized approach to all unit scale determination:
SI_Length = [ (1/1000000000000,"pm"),
(1/1000000000, "nm"),
(1/1000000, "um"),
(1/1000, "mm"),
(1/100, "cm"),
(1, "m"),
(1000, "km") ]
SI_Frequency = [ (1, "Hz"), (1000,"kHz"), (1000000,"MHz"), (1000000000,"GHz")]
def convert(n,units):
useFactor,useName = units[0]
for factor,name in units:
if n >= factor : useFactor,useName = factor,name
return (n/useFactor,useName)
print(convert(0.0035,SI_Length)) # 3.5 mm
print(convert(12332.55,SI_Frequency)) # 12.33255 kHz
Each unit array must be in order of smallest to largest multiplier.
EDIT: Actually, range is a function which is generally used in itaration to generate numbers. So, when you write if n in range(min_value, max_value), this function generates all integers until it finds a match or reach the max_value.
The range type represents an immutable sequence of numbers and is commonly used for looping a specific number of times in for loops.
Instead of writing:
if n in range(int(1e-10),int(9e-8)):
return n/0.000000001 , 'nm'
you should write:
if 1e-10 <= n < 9e-8:
return n/0.000000001 , 'nm'
Also keep in mind that range only works on integers, not float.
More EDIT:
For your specific use case, you can define dictionary of *(value, symbol) pairs, like below:
import collections
symbols = collections.OrderedDict(
[(1e-12, u'p'),
(1e-9, u'n'),
(1e-6, u'μ'),
(1e-3, u'm'),
(1e-2, u'c'),
(1e-1, u'd'),
(1e0, u''),
(1e1, u'da'),
(1e2, u'h'),
(1e3, u'k'),
(1e6, u'M'),
(1e9, u'G'),
(1e12, u'T')])
The use the bisect.bisect function to find the "insertion" point of your value in that ordered collection. This insertion point can be used to get the simplified value and the SI symbol to use.
For instance:
import bisect
def convert_to_si(value):
if value < 0:
value, symbol = convert_to_si(-value)
return -value, symbol
elif value > 0:
orders = list(symbols.keys())
order_index = bisect.bisect(orders, value / 10.0)
order = orders[min(order_index, len(orders) - 1)]
return value / order, symbols[order]
else:
return value, u""
Demonstration:
for value in [1e-12, 3.14e-11, 0, 2, 20, 3e+9]:
print(*convert_to_si(value), sep="")
You get:
1.0p
0.0314n
0
2.0
2.0da
3.0G
You can adapt this function to your needs…

Any better way to write this small code?

I'm processing a.CSV file in python which has a floating type field.
This field has to be modified such that it has at least 4 decimal points and max 8 decimal points of precision.
Example:
input: 5.15
output: 5.1500
input: -12.129999998
output: -12.12999999
What I'm currently doing:
#The field to be modifed is present at index 3 in list temp
dotIndex = temp[3].find('.') + 1
latLen = len(temp[3])-1
if (latLen) - (dotIndex) > 8:
temp[3] = temp[3][0:dotIndex+4]
elif (latLen) - (dotIndex) < 4:
temp[3] = temp[3][0:latLen] + (4 - (latLen - (dotIndex))) * '0'
Is there a better way to write this code to improve performance ?
this should work:
temp[3] = "{:.4f}".format(float(temp[3]))
Considering your comment and the fact you want it truncated, here you go:
n = len(temp[3].split('.')[1])
if n < 4:
temp[3] = "{:.4f}".format(float(temp[3]))
elif n > 8:
parts = temp[3].split('.')
temp[3] = parts[0]+"."+parts[1][:4]
If you're truncating, not rounding, you could use something like this:
def truncate_to_eight(val):
return '{:.8f}'.format((int(val * 10**8))/(10.0**8))
Multiplying by 10 to the power of 8, taking the integer part, and then dividing by 10 to the power 8 gets you the truncation required. Note however, this will always return a value with 8 decimal places - so 5.15 becomes 5.15000000.
You would use this by saying, for example:
rounded = truncate_to_eight(temp[3])

Categories