Python Rounding Down to Custom Step

Question:

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?

Asked By: FrankC

||

Answers:

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
Answered By: AKX

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
Answered By: tahesse

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
Answered By: Sergei Bondarenko
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.