Python 3.x округляет половину - PullRequest
5 голосов
/ 08 ноября 2019

Я знаю, что вопросы о округлении в python задавались уже несколько раз, но ответы мне не помогли. Я ищу метод, который округляет число с плавающей точкой до половины и возвращает число с плавающей точкой. Метод также должен принимать параметр, который определяет десятичное место для округления. Я написал метод, который реализует этот вид округления. Тем не менее, я думаю, что это не выглядит элегантно на всех.

def round_half_up(number, dec_places):
    s = str(number)

    d = decimal.Decimal(s).quantize(
        decimal.Decimal(10) ** -dec_places,
        rounding=decimal.ROUND_HALF_UP)

    return float(d)

Мне не нравится, что мне нужно преобразовать число с плавающей точкой в ​​строку (чтобы избежать неточностей с плавающей запятой), а затем работать с модулем decimal . Есть ли у вас лучшие решения?

Ответы [ 2 ]

5 голосов
/ 12 ноября 2019

Округление на удивление трудно сделать правильно , потому что вы должны очень тщательно обрабатывать вычисления с плавающей запятой. Если вы ищете элегантное решение (несколько строк кода, легко проверяемое на корректность), то то, что у вас есть, кажется довольно хорошим. Вы можете просто заменить decimal.Decimal(str(number)) на создание десятичной дроби из самого числа, что даст вам десятичную версию ее точного представления:

d = Decimal(number).quantize(...)...

Если вы хотите избежать десятичных дробей, вам все равно потребуется какой-то способ реализовать точную арифметику, необходимую для правильного округления. Например, вы можете использовать дроби :

import fractions, math

def round_half_up(number, dec_places):
    sign = math.copysign(1, number)
    number_exact = abs(fractions.Fraction(number))
    shifted = number_exact * 10**dec_places
    shifted_rounded = round(shifted)
    if shifted - shifted_rounded >= fractions.Fraction(1, 2):
        result = (shifted_rounded + 1) / 10**dec_places
    else:
        result = shifted_rounded / 10**dec_places
    return sign * float(result)

Обратите внимание, что единственная сложная часть в приведенном выше коде - точное преобразование с плавающей запятой в дробь, и это может бытьвыгружается в метод с плавающей запятой as_integer_ratio(), который выполняет как внутренние, так и десятичные дроби. Поэтому, если вы действительно хотите удалить зависимость от fractions, вы можете уменьшить дробную арифметику до чисто целочисленной арифметики;Вы остаетесь в пределах той же строки, количество которых примерно одинаково, за счет некоторой разборчивости:

def round_half_up(number, dec_places):
    sign = math.copysign(1, number)
    exact = abs(number).as_integer_ratio()
    shifted = (exact[0] * 10**dec_places), exact[1]
    shifted_rounded = shifted[0] // shifted[1]
    difference = (shifted[0] - shifted_rounded * shifted[1]), shifted[1]
    if difference[0] * 2 >= difference[1]:  # difference >= 1/2
        shifted_rounded += 1
    return sign * (shifted_rounded / 10**dec_places)
0 голосов
/ 11 ноября 2019

После очень долгой попытки создать элегантную однострочную функцию я получил нечто, сопоставимое по размеру со словарем.

Я бы сказал, что самый простой способ сделать это - просто

def round_half_up(inp,dec_places):
    return round(inp+0.0000001,dec_places)

. Я бы признал, что это не точно в каждом случае, но он должен работать, если вы просто хотите простой быстрый обходной путь. .

...