First note this problem (unexpected rounding down) only sometimes occurs when the digit immediately inferior (to the left of) the digit you're rounding to has a 5
.
i.e.
>>> round(1.0005,3)
1.0
>>> round(2.0005,3)
2.001
>>> round(3.0005,3)
3.001
>>> round(4.0005,3)
4.0
>>> round(1.005,2)
1.0
>>> round(5.005,2)
5.0
>>> round(6.005,2)
6.0
>>> round(7.005,2)
7.0
>>> round(3.005,2)
3.0
>>> round(8.005,2)
8.01
But there's an easy solution, I've found that seems to always work, and which doesn't rely upon the import
of additional libraries. The solution is to add a 1e-X
where X
is the length of the number string you're trying to use round
on plus 1
.
>>> round(0.075,2)
0.07
>>> round(0.075+10**(-2*6),2)
0.08
Aha! So based on this we can make a handy wrapper function, which is standalone and does not need additional import
calls...
def roundTraditional(val,digits):
return round(val+10**(-len(str(val))-1))
Basically this adds a value guaranteed to be smaller than the least given digit of the string you're trying to use round
on. By adding that small quantity it preserve's round
's behavior in most cases, while now ensuring if the digit inferior to the one being rounded to is 5
it rounds up, and if it is 4
it rounds down.
The approach of using 10**(-len(val)-1)
was deliberate, as it the largest small number you can add to force the shift, while also ensuring that the value you add never changes the rounding even if the decimal .
is missing. I could use just 10**(-len(val))
with a condiditional if (val>1)
to subtract 1
more... but it's simpler to just always subtract the 1
as that won't change much the applicable range of decimal numbers this workaround can properly handle. This approach will fail if your values reaches the limits of the type, this will fail, but for nearly the entire range of valid decimal values it should work.
You can also use the decimal library to accomplish this, but the wrapper I propose is simpler and may be preferred in some cases.
Edit: Thanks Blckknght for pointing out that the 5
fringe case occurs only for certain values here.