2017-09-18 1 views
1

J'aide un ami dans une tâche de devoirs qui demande à un utilisateur d'entrer un nombre arbitraire de secondes et d'afficher une chaîne qui représente cette durée en termes de semaines, de jours, d'heures, de minutes et de secondes.Pourquoi diviser puis multiplier de grands entiers retourne des résultats étranges en Python?

J'ai une classe TimeUnit qui hérite de int et n'autorise pas la création d'unités de temps négatives. J'ai alors une classe TimePeriod composée de TimeUnits qui affiche la chaîne.

Plus précisément, il est ce phénomène qui me confond:

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 

Comment est 249129847219749821374782498 - (Weeks.from_seconds(249129847219749821374782498).to_seconds()) == -2491687902? Il finit par provoquer des erreurs lorsque j'essaie de représenter ce nombre de secondes en format de chaîne avec ma classe TimePeriod.

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) 
+1

Cela ressemble à un débordement d'époque. –

Répondre

1

Le problème est le fait que vous divisez en from_seconds, cela tournera l'entier à une valeur en virgule flottante. Les flotteurs ont une précision limitée, donc peut perdre quelques chiffres significatifs juste là. Cependant, comme vous sous-classez int vous ne stockez que des entiers (pas de flottants) et la partie décimale est simplement défaussée (voir par exemple ce qui renvoie Weeks.from_seconds(x)) ce qui posera des problèmes même si la valeur entière n'est pas si grande - to_seconds est juste la partie intégrante de la division.

Parcourons cette étape par étape:

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

>>> int(_) 
411921043683448782848 

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

Pour faire ce travail correctement, vous pouvez, par exemple, sous-classe ou simplement utiliser fractions.Fraction. Il fonctionne correctement cette conversion:

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

Votre division est "true division" qui retourne une float. int est arbitraire-précision (en Python 3, que vous utilisez), mais float ne l'est pas, et il ne peut pas conserver autant de chiffres (et arrondi à un int différent).