Быстрый и точный потолок / покрытие с плавающей точкой ближайшего шага без Decimal.quantize - PullRequest
0 голосов
/ 06 апреля 2020

Предполагается, что следующая функция преобразует данное число до ближайшего шага:

def ceil_step(x, step):
    return math.ceil(x / step) * step

Это работает хорошо ... до тех пор, пока это не произойдет:

print(ceil_step(1000.365, 0.01)) # 1000.37
print(ceil_step(1000.369, 0.01)) # 1000.37
print(ceil_step(1000.370, 0.01)) # 1000.37
print(ceil_step(1000.371, 0.01)) # 1000.38

print(ceil_step(10000.365, 0.01)) # 10000.37
print(ceil_step(10000.369, 0.01)) # 10000.37
print(ceil_step(10000.370, 0.01)) # 10000.380000000001
print(ceil_step(10000.371, 0.01)) # 10000.380000000001

Работающая реализация было бы:

def ceil_step(x, step):
    return Decimal(str(x)).quantize(Decimal(str(step)), rounding=ROUND_CEILING)

К сожалению, я не могу использовать его, потому что пишу код с ускорением Numba Python. Поэтому мой вопрос: существует ли способ реализовать функцию ceil_step (и аналогично floor_step) так, чтобы она использовала только базовые операции c с плавающей запятой или вызовы функций с поддержкой Numba и давала надежные результаты?

1 Ответ

1 голос
/ 07 апреля 2020

Пока я остановился на следующей функции:

def round_step(x, step, method=-1):
    eps = np.finfo(np.float64).eps
    digits = math.ceil(-np.log10(step))
    return round(math.ceil(x * (1 - eps) / step) * step, digits)

Умножение на (1 - eps) гарантирует (надеюсь!), Что math.ceil не переходит. round необходим, чтобы избавиться от таких вещей, как ...0000000001.

...