Округление на удивление трудно сделать правильно , потому что вы должны очень тщательно обрабатывать вычисления с плавающей запятой. Если вы ищете элегантное решение (несколько строк кода, легко проверяемое на корректность), то то, что у вас есть, кажется довольно хорошим. Вы можете просто заменить 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)