We have a partially working code and 2 examples with different types of custom steps. The example 2 (Int) is working, while the example 1 is not, as it is rounding up instead of down.
import math
def step_size_to_precision(ss):
return ss.find('1') - 1
def format_value(val, step_size_str):
precision = step_size_to_precision(step_size_str)
if precision > 0:
return "{:0.0{}f}".format(val, precision)
return math.floor(int(val))
###########################
# # example 1
step_size = "0.00000100"
quantity = 0.00725562
print(quantity)
print(format_value(quantity, step_size))
# 0.00725562
# 0.007256 <= Is rounding up instead of down. Should be 0.007255
###########################
# # example 2
# step_size = "1"
# quantity = 3.00725562
# print(quantity)
# print(format_value(quantity, step_size))
# returns 3 <= This is correct
###########################
How do we fix it?
You'll want to use Decimal objects to for precise decimal numbers to begin with.
Then, use Decimal.quantize() in the ROUND_DOWN mode.
from decimal import Decimal, ROUND_DOWN
quantity = 0.00725562
step_size = Decimal("0.000001")
print(Decimal(quantity).quantize(step_size, ROUND_DOWN))
prints out
0.007255
Another approach is outlined in this SO answer:
If you want to round down always (instead of rounding to the nearest
precision), then do so, explicitly, with the math.floor()
function:
from math import floor
def floored_percentage(val, digits):
val *= 10 ** (digits + 2)
return '{1:.{0}f}%'.format(digits, floor(val) / 10 ** digits)
print floored_percentage(0.995, 1)
Demo:
>>> from math import floor
>>> def floored_percentage(val, digits):
... val *= 10 ** (digits + 2)
... return '{1:.{0}f}%'.format(digits, floor(val) / 10 ** digits)
...
>>> floored_percentage(0.995, 1)
'99.5%'
>>> floored_percentage(0.995, 2)
'99.50%'
>>> floored_percentage(0.99987, 2)
'99.98%'
For your example:
import math
def step_size_to_precision(ss):
return max(ss.find('1'), 1) - 1
def format_value(val, step_size):
digits = step_size_to_precision(step_size)
val *= 10 ** digits
return '{1:.{0}f}'.format(digits, math.floor(val) / 10 ** digits)
step_size = "0.00000100"
quantity = 0.00725562
print(quantity)
print(format_value(quantity, step_size))
# prints out: 0.007255
A more general approach which allows to round down for step_size which is not only power of 10:
from decimal import Decimal
def floor_step_size(quantity, step_size):
step_size_dec = Decimal(str(step_size))
return float(int(Decimal(str(quantity)) / step_size_dec) * step_size_dec)
Usage:
>>> floor_step_size(0.00725562, "0.00000100")
0.007255
>>> floor_step_size(3.00725562, "1")
3.0
>>> floor_step_size(2.6, "0.25")
2.5
>>> floor_step_size(0.9, "0.2")
0.8
Related
In the following 2 examples:
Example 1:
from decimal import Decimal, getcontext
getcontext().prec = 1000
d = Decimal(1+10**(-24))
1/d.ln()
Example 2:
from mpmath import *
mp.dps = 1000
mp.pretty=True
1/(ln(1+10**(-24)))
I get the ZeroDivisionError. Python 3.7(64-bit) takes it as 1/ln(1) or 1/0.
How I can make Python read it as 1/ln(1+10^(-24)) not 1/ln(1)?
For example #1, make everything a Decimal resulting from operations between Decimal:
d = Decimal(1) + Decimal(10) ** Decimal(-24) # Decimal('1.000000000000000000000001')
Decimal(1) / d.ln()
otherwise you'd get a primitive type float precision first, and the number rounded to 1.0:
1+10**(-24) # 1.0
Decimal(1+10**(-24)) # 1
Similarly, for example #2 (I just have read now the docs for mpmath, so take it with a grain of salt):
from mpmath import mp, mpf, ln
mp.dps = 1000
mp.pretty = True
mpf(1) / ln(mpf(1) + mpf(10) ** mpf(-24))
I have a beginner problem. How can I round up to 2 decimal?
Here is what I tried and what I want to achieve:
import math
var_1 = 14.063 # expected = 14.06
var_2 = 10.625 # expected = 10.63
print(round(14.063, 2))
print(round(10.625, 2))
print('===========================')
def round_up(n, decimals=0):
multiplier = 10 ** decimals
return math.ceil(n * multiplier) / multiplier
print(round_up(var_1, 2))
print(round_up(var_2, 2))
And the Output is:
14.06
10.62
===========================
14.07
10.63
So neither of those wroks for me...
The Decimal class, quantize() method, and ROUND_HALF_UP rule from the decimal module can handle this:
from decimal import Decimal, ROUND_HALF_UP
var_1 = 14.063 # expected = 14.06
var_2 = 10.625 # expected = 10.63
# a Decimal object with an explicit exponent attribute/property (to be interpreted by quantize)
Two_places = Decimal("1e-2")
for var in [var_1, var_2]:
rounded = Decimal(var).quantize(Two_places, rounding=ROUND_HALF_UP)
print(f"decimal: {rounded}")
print(f"float: {float(rounded)}")
and I get:
decimal: 14.06
float: 14.06
decimal: 10.63
float: 10.63
Keep in mind that when you're dealing with floats, you're always manipulating a less-than-precise representation of what you probably (naturally) have in mind:
Decimal(1.65) # Decimal('1.649999999999999911182158029987476766109466552734375')
Decimal('1.65') # Decimal('1.65')
In the first case, 1.65 was first turned into an IEEE-754 float, which has precision errors going from base-10 to base-2, then passed to Decimal. In the second case, Decimal interpreted the number as "one, and 65 100-ths" which equates to "165 times 10 raised to the minus 2", or 165e-2.
Try this. This finds the nearest one and if not, then round up -
import math
v1 = 14.063
v2 = 10.625
def round_up(n, decimals=0):
multiplier = 10 ** decimals
var_down = round(n, 2)
var_up = math.ceil(n * multiplier) / multiplier
if n - var_down >= var_up - n:
return var_up
else:
return var_down
v1_round = round_up(v1, 2)
v2_round = round_up(v2, 2)
print (v1_round) # 14.06
print (v2_round) # 10.63
this should work, although there is probebly a more efficient way of doing it. I just took your code and determined which one was closer, and if they are the same to round up.
Edit: It seems that PCM has made such version.
import math
decimals = 2
var_1 = 14.063
var_2 = 10.625
var_1down = round(var_1, decimals)
var_2down = round(var_2, decimals)
def round_up(n, decimals=0):
multiplier = 10 ** decimals
return math.ceil(n * multiplier) / multiplier
var_1up = round_up(var_1, decimals)
var_2up = round_up(var_2, decimals)
if var_1 - var_1down >= var_1up - var_1:
var_1round = var_1up
else:
var_1round = var_1down
if var_2 - var_2down >= var_2up - var_2:
var_2round = var_2up
else:
var_2round = var_2down
print (var_1round)
print (var_2round)
If you check the docs you will see that "values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done toward the even choice (so, for example, both round(0.5) and round(-0.5) are 0, and round(1.5) is 2)".
So 10.625 rounds to 10.62. You may try adding a very small value, e.g. 0.00001, but even so, since the way float numbers work, you may have some surprise in a few cases.
I've been having trouble trying to round my answer for a problem where I find the area of a regular polygon and then square the perimeter. My final answer should be the area plus the perimeter(squared), rounded to 4 decimal places. My math seems to be correct, however, no matter what numbers I use for inputs, there are only zeros after the decimal. I have included a screen shot of my code and incorrect message from the checker that I use.
import math
def polysum(n, s):
a = ((0.25 * n * s ** 2) / (math.tan(math.pi / 2)))
p = ((n * s) ** 2)
total = a + p
return '%.4f' % round(total)
print polysum(8, 8)
Of course you're only getting zeroes after the decimal point, because you are using the round() function to chop off all digits after the decimal point. If that's not what you want, don't do it. Just do:
return "%.4f" % total
Or possibly:
return round(total, 4)
There are two issues:
Change return '%.4f' % round(total) to return round(total,4) or else you are returning a str round to the nearest integer. It looks like the expected output is a float.
The factor of math.tan(math.pi / 2) is incorrect. This should evaluate to infinity (if not for floating point approximations) and is clearly not what you want. It should be math.tan(math.pi / 2 / n).
import math
def polysum(n, s):
a = (0.25 * n * s ** 2) / (math.tan(math.pi / n))
p = ((n * s) ** 2)
total = a + p
ans = round(total, 4)
return ans
print polysum(8,8)
print polysum(4, 89)
from math import *
def polysum(n, s):
lst = [(0.25 * n * s **2) / tan(pi / n), ((n * s) ** 2)]
return round(sum(lst), 4)
I have tried both the test cases. The output is matching.
>>> a = 0.3135
>>> print("%.3f" % a)
0.314
>>> a = 0.3125
>>> print("%.3f" % a)
0.312
>>>
I am expecting 0.313 instead of 0.312
Any thought on why is this, and is there alternative way I can use to get 0.313?
Thanks
Python 3 rounds according to the IEEE 754 standard, using a round-to-even approach.
If you want to round in a different way then simply implement it by hand:
import math
def my_round(n, ndigits):
part = n * 10 ** ndigits
delta = part - int(part)
# always round "away from 0"
if delta >= 0.5 or -0.5 < delta <= 0:
part = math.ceil(part)
else:
part = math.floor(part)
return part / (10 ** ndigits) if ndigits >= 0 else part * 10 ** abs(ndigits)
Example usage:
In [12]: my_round(0.3125, 3)
Out[12]: 0.313
Note: in python2 rounding is always away from zero, while in python3 it rounds to even. (see, for example, the difference in the documentation for the round function between 2.7 and 3.3).
If you need accuracy don't use float, use Decimal
>>> from decimal import *
>>> d = Decimal(0.3125)
>>> getcontext().rounding = ROUND_UP
>>> round(d, 3)
Decimal('0.313')
or even Fraction
try
print '%.3f' % round(.3125,3)
I had the same incorrect rounding
round(0.573175, 5) = 0.57317
My solution
def to_round(val, precision=5):
prec = 10 ** precision
return str(round(val * prec) / prec)
to_round(0.573175) = '0.57318'
I have this portion of a class that takes a whole number and a fraction and add them together.
def __add__(self, g):
whole_add=self.whole_number + g.whole_number
numerator = (self.fraction.numerator * g.fraction.denominator ) + (g.fraction.numerator * self.fraction.denominator)
denominator = self.fraction.denominator * g.fraction.denominator
f=Fraction(numerator,denominator)
return '{} and {}'.format(whole_add,f)
fraction_1 = Fraction(3, 4)
fraction_2 = Fraction(2, 3)
mixed_num_1 = MixedNumber(2, fraction_1)
mixed_num_2 = MixedNumber(1, fraction_2)
print(mixed_num_1 + mixed_num_2)
The outcome of this is 3 and 17/12, when it should be 4 and 5/12, I am not sure how to do this, I assume with an if the fraction is >= 1. Any help would be much appreciated
If you are using the fractions library, ou can just sum everything, then take the int() portion of the fraction:
def __add__(self, g):
summed = sum((self.whole_number, g.whole_number, self.fraction, g.fraction))
whole = int(summed)
remainder = summed - whole
return '{} and {}'.format(whole, remainder)
The Fraction() class implements __add__ for you, you can just sum up integers and Fraction() objects and it all works as it should.
Demo using constants:
>>> from fractions import Fraction
>>> summed = sum((2, 1, Fraction(3, 4), Fraction(2, 3)))
>>> whole = int(summed)
>>> remainder = summed - whole
>>> '{} and {}'.format(whole, remainder)
'4 and 5/12'
One little-known but handy factoid is that Python the int() type has both .numerator and .denominator attributes, that the fractions.Fraction() class makes use of. If you are not using the fractions library, you can make use of that yourself:
def __add__(self, g):
summed = 0
for v in (self.whole_number, g.whole_number, self.fraction, g.fraction):
summed = Fraction(summed.numerator * v.denominator +
v.numerator * summed.denominator,
summed.denominator * v.denominator)
whole = summed._numerator // summed._denominator
remainder = Fraction(summed.numerator * whole.denominator -
whole.numerator * summed.denominator,
summed.denominator * whole.denominator)
return '{} and {}'.format(whole, remainder)
One way to "fix" your version would be to deal with the improper fraction directly:
whole_add=self.whole_number + g.whole_number
numerator = (self.fraction.numerator * g.fraction.denominator ) + (g.fraction.numerator * self.fraction.denominator)
denominator = self.fraction.denominator * g.fraction.denominator
whole_add += numerator // denominator
numerator -= numerator % denominator
f=Fraction(numerator,denominator)
return '{} and {}'.format(whole_add,f)
Although there are more efficient ways of doing this addition more directly.