Python 使用 Decimal 模数四舍五入到指定的小数位
Python rounding with Decimal module to specified decimal places
问题:如何让Python的Decimal模块四舍五入到指定的小数位而不是四舍五入到指定的精度(有效数字)算术运算?
信息
我一直在使用 Python 中的 Decimal 模块,使用 setcontext
方法将值四舍五入到指定的精度。在我们开始交叉整数和小数之前,这很有效,因为有效数字不会区分两者。
import decimal as d
from math import pi
decimal_places = 0
d.setcontext(d.Context(prec=decimal_places+1, rounding=d.ROUND_HALF_UP))
# This works fine
num = pi
print(f"Rounding {num} to {decimal_places} decimal places:")
print(f"Traditional rounding (correct): {round(num, decimal_places)}")
print(f"Decimal rounding (correct): {+d.Decimal(num)}")
# This is were issues start to arise
num = pi/10
print(f"\nRounding {num} to {decimal_places} decimal places:")
print(f"Traditional rounding (correct): {round(num, decimal_places)}")
print(f"Decimal rounding (incorrect): {+d.Decimal(num)}")
Rounding 3.141592653589793 to 0 decimal places:
Traditional rounding (correct): 3.0
Decimal rounding (correct): 3
Rounding 0.3141592653589793 to 0 decimal places:
Traditional rounding (correct): 0.0
Decimal rounding (incorrect): 0.3
用例
为什么还要在 Python 的 round 函数上使用 decimal 模块?那么 decimal 模块的优点是它将在算术评估 (PEMDAS) 的所有步骤中应用该精度上限。
例如,如果我想在函数中计算 x 时对其进行舍入,我可以这样做:
function_str = "0.5 * (3*x) ** 2 + 3"
eval(function_str.replace("x", "(+d.Decimal(x))"))
一个更完整(也更简单)的例子:
import decimal as d
decimal_places = 0
d.setcontext(d.Context(prec=decimal_places+1, rounding=d.ROUND_HALF_UP))
numerator = 5
denominator = 1.1
num_err = 0.5
new_num = numerator + num_err
print(f"Rounding {numerator}/{denominator} to {decimal_places} decimal places:")
print(f"Traditional rounding (incorrect): {round(new_num, decimal_places)/denominator}")
print(f"Decimal rounding (correct): {+d.Decimal(new_num) / d.Decimal(denominator)}")
Rounding 5/1.1 to 0 decimal places:
Traditional rounding (incorrect): 5.454545454545454
Decimal rounding (correct): 5
这里看起来 round 可能仍然是一个更简单的解决方案,因为它可以放在输出周围,但是随着函数的复杂性增加,它变得越来越不可行。在用户输入函数的情况下,传统舍入的可行性几乎为零,而使用 decimal 模块就像 function_str.replace("x", "(+d.Decimal(x))")
.
一样简单
请注意,quantize
方法将不是一个可行的选项,因为它只舍入当前数字而不是所有内容(这是设置上下文精度的作用)。
为了解决这个问题,我最终只是制作了自己的定点算法库。为了帮助以后遇到此问题的其他人,我在下面发布了我的定点算法库的代码。
import math
PREC = 0
def no_rounding(x, *args, **kwargs):
return x
def ceil(x, prec=0):
mult = 10 ** prec
return round(math.ceil(x * mult) / mult, prec)
def floor(x, prec=0):
mult = 10 ** prec
return round(math.floor(x * mult) / mult, prec)
rounding = {
None: no_rounding,
"round": round,
"ceil": ceil,
"floor": floor,
}
class Fixed:
def __init__(self, number, round_function="round", custom_prec=None):
self.val = float(number)
self.round_str = round_function
self.round_func = rounding[round_function]
self.custom_prec = custom_prec
def _dup_fixed(self, number):
return Fixed(number, self.round_str, self.custom_prec)
def _operation(self, op):
return self._dup_fixed(self.round_func(op, self.prec))
@property
def prec(self):
return int(self.custom_prec if self.custom_prec is not None else PREC)
@property
def num(self):
return self.round_func(self.val, self.prec)
@property
def real(self):
return self
@property
def imag(self):
return Fixed(0)
def __setattr__(self, name, value):
if name == "val":
value = float(value)
self.__dict__[name] = value
def __hash__(self):
return hash(self.num)
def __str__(self):
return str(self.num)
__repr__ = __str__
def __format__(self, spec):
if spec == "":
return str(self)
else:
return spec % self.num
def __reduce__(self):
return (self.__class__, (self.val,))
def __copy__(self):
return self.__class__(self.val)
def __deepcopy__(self, memo):
return self.__copy__()
def __pos__(self):
return self
def __neg__(self):
return self._dup_fixed(-self.val)
def __abs__(self):
return self._dup_fixed(abs(self.val))
def __round__(self, n=None):
return self._dup_fixed(round(self.val, n))
def __floor__(self):
return self._dup_fixed(math.floor(self.val))
def __ceil__(self):
return self._dup_fixed(math.ceil(self.val))
def __int__(self):
return int(self.num)
def __trunc__(self):
return math.trunc(self.num)
def __float__(self):
return float(self.num)
def __complex__(self):
return complex(self.num)
def conjugate(self):
return self
def __eq__(self, other):
return self.num == float(other)
def __ne__(self, other):
return not self == float(other)
def __gt__(self, other):
return self.num > float(other)
def __ge__(self, other):
return self.num >= float(other)
def __lt__(self, other):
return self.num < float(other)
def __le__(self, other):
return self.num <= float(other)
def __bool__(self):
return self.num != 0
def __add__(self, other):
return self._operation(self.num + float(other))
__radd__ = __add__
def __sub__(self, other):
return self + -other
def __rsub__(self, other):
return -self + other
def __mul__(self, other):
return self._operation(self.num * float(other))
__rmul__ = __mul__
def __truediv__(self, other):
return self._operation(self.num / float(other))
def __rtruediv__(self, other):
return self._operation(float(other) / self.num)
def __floordiv__(self, other):
return self._operation(self.num // float(other))
def __rfloordiv__(self, other):
return self._operation(float(other) // self.num)
def __mod__(self, other):
return self._operation(self.num % float(other))
def __rmod__(self, other):
return self._operation(float(other) % self.num)
def __divmod__(self, other):
result = divmod(self.num, float(other))
return (self._operation(result[0]), self._operation(result[1]))
def __rdivmod__(self, other):
result = divmod(float(other), self.num)
return (self._operation(result[0]), self._operation(result[1]))
def __pow__(self, other):
return self._operation(self.num ** float(other))
def __rpow__(self, other):
return self._operation(float(other) ** self.num)
如果您发现任何错误或问题,请在评论中告诉我,我一定会更新我的答案。
用法
通过将数字传递给 Fixed
函数来创建固定数字。然后可以像对待普通数字一样对待这个固定数字。
import fixed_point as fp # Import file
num = 1.6
fixed_num = fp.Fixed(num) # Default precision is 0
print("Original number:", num)
print("Fixed number:", fixed_num)
print("Fixed number value multiplied by original number:", fixed_num.val * num)
print("Fixed number multiplied by original number:", fixed_num * num)
print("Fixed number multiplied by itself:", fixed_num * fixed_num)
Original number: 1.6
Fixed number: 2.0
Fixed number value multiplied by original number: 2.56
Fixed number multiplied by original number: 3.0
Fixed number multiplied by itself: 4.0
要设置全局精度,可以修改 PREC
变量,这不仅会改变所有新固定精度数字的精度(小数位数),还会改变现有数字的精度。具体固定数字的精度也可以在创建时设置。
num = 3.14159
fixed_num = fp.Fixed(num)
custom_prec_num = fp.Fixed(num, custom_prec=4)
print("Original number:", num)
print("Fixed number (default precision):", fixed_num)
print("Custom precision fixed number (4 decimals):", custom_prec_num)
fp.PREC = 2 # Update global precision
print("\nGlobal precision updated to", fp.PREC)
print("Fixed number (new precision):", fixed_num)
print("Custom precision fixed number (4 decimals):", custom_prec_num)
Original number: 3.14159
Fixed number (default precision): 3.0
Custom precision fixed number (4 decimals): 3.1416
Global precision updated to 2
Fixed number (new precision): 3.14
Custom precision fixed number (4 decimals): 3.1416
注意获取固定数的原始值只能通过fixed_num.val
使用float(fixed_num)
将return固定数四舍五入到指定的小数位数(除非四舍五入是 none).
问题:如何让Python的Decimal模块四舍五入到指定的小数位而不是四舍五入到指定的精度(有效数字)算术运算?
信息
我一直在使用 Python 中的 Decimal 模块,使用 setcontext
方法将值四舍五入到指定的精度。在我们开始交叉整数和小数之前,这很有效,因为有效数字不会区分两者。
import decimal as d
from math import pi
decimal_places = 0
d.setcontext(d.Context(prec=decimal_places+1, rounding=d.ROUND_HALF_UP))
# This works fine
num = pi
print(f"Rounding {num} to {decimal_places} decimal places:")
print(f"Traditional rounding (correct): {round(num, decimal_places)}")
print(f"Decimal rounding (correct): {+d.Decimal(num)}")
# This is were issues start to arise
num = pi/10
print(f"\nRounding {num} to {decimal_places} decimal places:")
print(f"Traditional rounding (correct): {round(num, decimal_places)}")
print(f"Decimal rounding (incorrect): {+d.Decimal(num)}")
Rounding 3.141592653589793 to 0 decimal places:
Traditional rounding (correct): 3.0
Decimal rounding (correct): 3
Rounding 0.3141592653589793 to 0 decimal places:
Traditional rounding (correct): 0.0
Decimal rounding (incorrect): 0.3
用例
为什么还要在 Python 的 round 函数上使用 decimal 模块?那么 decimal 模块的优点是它将在算术评估 (PEMDAS) 的所有步骤中应用该精度上限。
例如,如果我想在函数中计算 x 时对其进行舍入,我可以这样做:
function_str = "0.5 * (3*x) ** 2 + 3"
eval(function_str.replace("x", "(+d.Decimal(x))"))
一个更完整(也更简单)的例子:
import decimal as d
decimal_places = 0
d.setcontext(d.Context(prec=decimal_places+1, rounding=d.ROUND_HALF_UP))
numerator = 5
denominator = 1.1
num_err = 0.5
new_num = numerator + num_err
print(f"Rounding {numerator}/{denominator} to {decimal_places} decimal places:")
print(f"Traditional rounding (incorrect): {round(new_num, decimal_places)/denominator}")
print(f"Decimal rounding (correct): {+d.Decimal(new_num) / d.Decimal(denominator)}")
Rounding 5/1.1 to 0 decimal places:
Traditional rounding (incorrect): 5.454545454545454
Decimal rounding (correct): 5
这里看起来 round 可能仍然是一个更简单的解决方案,因为它可以放在输出周围,但是随着函数的复杂性增加,它变得越来越不可行。在用户输入函数的情况下,传统舍入的可行性几乎为零,而使用 decimal 模块就像 function_str.replace("x", "(+d.Decimal(x))")
.
请注意,quantize
方法将不是一个可行的选项,因为它只舍入当前数字而不是所有内容(这是设置上下文精度的作用)。
为了解决这个问题,我最终只是制作了自己的定点算法库。为了帮助以后遇到此问题的其他人,我在下面发布了我的定点算法库的代码。
import math
PREC = 0
def no_rounding(x, *args, **kwargs):
return x
def ceil(x, prec=0):
mult = 10 ** prec
return round(math.ceil(x * mult) / mult, prec)
def floor(x, prec=0):
mult = 10 ** prec
return round(math.floor(x * mult) / mult, prec)
rounding = {
None: no_rounding,
"round": round,
"ceil": ceil,
"floor": floor,
}
class Fixed:
def __init__(self, number, round_function="round", custom_prec=None):
self.val = float(number)
self.round_str = round_function
self.round_func = rounding[round_function]
self.custom_prec = custom_prec
def _dup_fixed(self, number):
return Fixed(number, self.round_str, self.custom_prec)
def _operation(self, op):
return self._dup_fixed(self.round_func(op, self.prec))
@property
def prec(self):
return int(self.custom_prec if self.custom_prec is not None else PREC)
@property
def num(self):
return self.round_func(self.val, self.prec)
@property
def real(self):
return self
@property
def imag(self):
return Fixed(0)
def __setattr__(self, name, value):
if name == "val":
value = float(value)
self.__dict__[name] = value
def __hash__(self):
return hash(self.num)
def __str__(self):
return str(self.num)
__repr__ = __str__
def __format__(self, spec):
if spec == "":
return str(self)
else:
return spec % self.num
def __reduce__(self):
return (self.__class__, (self.val,))
def __copy__(self):
return self.__class__(self.val)
def __deepcopy__(self, memo):
return self.__copy__()
def __pos__(self):
return self
def __neg__(self):
return self._dup_fixed(-self.val)
def __abs__(self):
return self._dup_fixed(abs(self.val))
def __round__(self, n=None):
return self._dup_fixed(round(self.val, n))
def __floor__(self):
return self._dup_fixed(math.floor(self.val))
def __ceil__(self):
return self._dup_fixed(math.ceil(self.val))
def __int__(self):
return int(self.num)
def __trunc__(self):
return math.trunc(self.num)
def __float__(self):
return float(self.num)
def __complex__(self):
return complex(self.num)
def conjugate(self):
return self
def __eq__(self, other):
return self.num == float(other)
def __ne__(self, other):
return not self == float(other)
def __gt__(self, other):
return self.num > float(other)
def __ge__(self, other):
return self.num >= float(other)
def __lt__(self, other):
return self.num < float(other)
def __le__(self, other):
return self.num <= float(other)
def __bool__(self):
return self.num != 0
def __add__(self, other):
return self._operation(self.num + float(other))
__radd__ = __add__
def __sub__(self, other):
return self + -other
def __rsub__(self, other):
return -self + other
def __mul__(self, other):
return self._operation(self.num * float(other))
__rmul__ = __mul__
def __truediv__(self, other):
return self._operation(self.num / float(other))
def __rtruediv__(self, other):
return self._operation(float(other) / self.num)
def __floordiv__(self, other):
return self._operation(self.num // float(other))
def __rfloordiv__(self, other):
return self._operation(float(other) // self.num)
def __mod__(self, other):
return self._operation(self.num % float(other))
def __rmod__(self, other):
return self._operation(float(other) % self.num)
def __divmod__(self, other):
result = divmod(self.num, float(other))
return (self._operation(result[0]), self._operation(result[1]))
def __rdivmod__(self, other):
result = divmod(float(other), self.num)
return (self._operation(result[0]), self._operation(result[1]))
def __pow__(self, other):
return self._operation(self.num ** float(other))
def __rpow__(self, other):
return self._operation(float(other) ** self.num)
如果您发现任何错误或问题,请在评论中告诉我,我一定会更新我的答案。
用法
通过将数字传递给 Fixed
函数来创建固定数字。然后可以像对待普通数字一样对待这个固定数字。
import fixed_point as fp # Import file
num = 1.6
fixed_num = fp.Fixed(num) # Default precision is 0
print("Original number:", num)
print("Fixed number:", fixed_num)
print("Fixed number value multiplied by original number:", fixed_num.val * num)
print("Fixed number multiplied by original number:", fixed_num * num)
print("Fixed number multiplied by itself:", fixed_num * fixed_num)
Original number: 1.6
Fixed number: 2.0
Fixed number value multiplied by original number: 2.56
Fixed number multiplied by original number: 3.0
Fixed number multiplied by itself: 4.0
要设置全局精度,可以修改 PREC
变量,这不仅会改变所有新固定精度数字的精度(小数位数),还会改变现有数字的精度。具体固定数字的精度也可以在创建时设置。
num = 3.14159
fixed_num = fp.Fixed(num)
custom_prec_num = fp.Fixed(num, custom_prec=4)
print("Original number:", num)
print("Fixed number (default precision):", fixed_num)
print("Custom precision fixed number (4 decimals):", custom_prec_num)
fp.PREC = 2 # Update global precision
print("\nGlobal precision updated to", fp.PREC)
print("Fixed number (new precision):", fixed_num)
print("Custom precision fixed number (4 decimals):", custom_prec_num)
Original number: 3.14159
Fixed number (default precision): 3.0
Custom precision fixed number (4 decimals): 3.1416
Global precision updated to 2
Fixed number (new precision): 3.14
Custom precision fixed number (4 decimals): 3.1416
注意获取固定数的原始值只能通过fixed_num.val
使用float(fixed_num)
将return固定数四舍五入到指定的小数位数(除非四舍五入是 none).