【问题标题】:Python rounding with Decimal module to specified decimal places使用 Decimal 模块将 Python 四舍五入到指定的小数位
【发布时间】:2021-03-21 03:45:10
【问题描述】:

问题:在计算算术运算时,如何让 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 函数?那么小数模块的优点是它会在算术评估(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 似乎仍然是一个更简单的解决方案,因为它可以放在输出周围,但随着函数复杂性的增加,它变得越来越不可行。在用户输入函数的情况下,传统舍入的可行性实际上为零,而使用小数模块就像function_str.replace("x", "(+d.Decimal(x))") 一样简单。

请注意,quantize 方法将不是一个可行的选项,因为它只对当前数字进行舍入,而不是对所有值进行舍入(这是设置上下文精度所做的)。

【问题讨论】:

  • 听起来你想做定点运算,而decimal 是一个浮点库。如果你想做定点数学,你最好只使用整数并自己处理缩放。
  • 我不确定我理解你的意思。我认为十进制模块用于定点算术“十进制定点和浮点算术”(docs.python.org/3/library/decimal.html)。你介意给我举个例子吗?
  • 文档是这么说的,但实际上,decimal 模块所做的一切都是浮点数。如果您检查faq section,您会发现模拟定点基本上涉及在任何地方粘贴一堆quantize 调用,这是您已经丢弃的不可行的选项。
  • (即使使用quantize,抽象也会泄漏——例如,如果操作的结果太大,可能会丢失精度,使用quantize无法恢复。有些结果也可能由于双舍入问题导致舍入不正确,除非您对舍入处理非常小心)
  • 那么你的意思是decimal模块不支持这个功能,如果我想这样做我需要构建自己的定点库?

标签: python math decimal rounding fixed-point


【解决方案1】:

为了解决这个问题,我最终只制作了自己的定点算术库。为了帮助以后遇到这个问题的其他人,我在下面发布了我的定点算术库的代码。

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)

如果您发现任何错误或问题,请在 cmets 中告诉我,我一定会更新我的答案。

用法

通过将数字传递给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) 将返回四舍五入到指定小数位数的固定数字(除非四舍五入为无)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-09-09
    • 2018-05-06
    • 2014-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多