为什么除法然后乘以一些大整数 return 奇怪的结果是 Python?

Why does dividing then multiplying some large integers return strange results in Python?

我正在帮助一位朋友完成家庭作业,该作业要求用户输入任意秒数并显示一个字符串,该字符串以周、天、小时、分钟和秒表示该时间量。

我有一个 TimeUnit class 继承自 int 并且不允许创建负时间单位。然后我有一个 TimePeriod class 由显示字符串的 TimeUnits 组成。

具体来说,就是这个现象让我很困惑:

class TimeUnit(int):
    """A class that defines the semantics of a unit of time i.e. seconds, minutes, hours etc."""

    def __new__(cls, x):
        """Ensure no negative units are created."""
        if x < 0:
            raise ValueError(f'Must be greater than zero')
        return super().__new__(cls, x)

    def __eq__(self, other):
        if isinstance(other, TimeUnit):
            return int(self.to_seconds()) == other.to_seconds()
        return super().__eq__(other)

    @classmethod
    def from_seconds(cls, seconds):
        raise NotImplementedError

    def to_seconds(self):
        raise NotImplementedError


class Seconds(TimeUnit):
    @classmethod
    def from_seconds(cls, seconds):
        return cls(seconds)

    def to_seconds(self):
        return self


class Weeks(TimeUnit):
    @classmethod
    def from_seconds(cls, seconds):
        return cls(seconds / 60 / 60 / 24 / 7)

    def to_seconds(self):
        return Seconds(self * 60 * 60 * 24 * 7)

x = 249129847219749821374782498

# Wat?
x - (Weeks.from_seconds(x).to_seconds()) # -> -2491687902

249129847219749821374782498 - (Weeks.from_seconds(249129847219749821374782498).to_seconds()) == -2491687902怎么样?当我尝试用我的 TimePeriod class.

以字符串格式表示该秒数时,它最终会导致错误
class TimePeriod:
    def __init__(self, *units):
        self.seconds = Seconds(sum(unit.to_seconds() for unit in units))

    def __repr__(self):
        seconds = self.seconds

        weeks = Weeks.from_seconds(seconds)
        seconds -= weeks.to_seconds()

        days = Days.from_seconds(seconds)
        seconds -= days.to_seconds()

        hours = Hours.from_seconds(seconds)
        seconds -= hours.to_seconds()

        minutes = Minutes.from_seconds(seconds)
        seconds -= minutes.to_seconds()

        seconds = Seconds(seconds)

        return ' '.join(f'{unit} {unit.__class__.__name__}' for unit in (weeks, days, hours, minutes, seconds) if unit)

    def __str__(self):
        return repr(self)

问题在于您除以 from_seconds,这会将整数转换为浮点值。浮点数的精度有限,因此您 可能 会丢失一些有效数字。但是,因为您将 int 子类化,您只存储整数(而不是浮点数)并且小数部分被简单地丢弃(例如参见 [​​=14=] returns),即使整数值是没那么大 - 最终在 to_seconds 中相乘的只是除法的组成部分。

让我们逐步完成此操作:

>>> 249129847219749821374782498 / (60*60*24*7)
4.119210436834488e+20

>>> int(_)
411921043683448782848

>>> _ * (60*60*24*7)
249129847219749823866470400

要使其正常工作,例如,您可以子类化或简单地使用 fractions.Fraction。此转换工作正常:

>>> from fractions import Fraction
>>> Fraction(x, 60*60*24*7) * 60 * 60 * 24 * 7
Fraction(249129847219749821374782498, 1)
>>> int(_)
249129847219749821374782498

你的划分是"true division"那returns一个floatint 是任意精度(在您正在使用的 Python 3 中),但 float 不是,并且它不能保留那么多数字(并且舍入到不同的数字) int).