Skip to content

timedelta.total_seconds needlessly inaccurate, especially for negative timedeltas #52890

Closed

Description

@mdickinson
BPO 8644
Nosy @mdickinson, @abalkin, @pitrou
Files
  • issue8644-py3k.diff
  • issue8644.diff: Backport to trunk
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = <Date 2010-05-09.09:36:28.892>
    created_at = <Date 2010-05-07.12:24:53.881>
    labels = ['extension-modules', 'type-bug']
    title = 'timedelta.total_seconds needlessly inaccurate, especially for negative timedeltas'
    updated_at = <Date 2010-05-09.09:36:28.891>
    user = 'https://github.com/mdickinson'

    bugs.python.org fields:

    activity = <Date 2010-05-09.09:36:28.891>
    actor = 'mark.dickinson'
    assignee = 'none'
    closed = True
    closed_date = <Date 2010-05-09.09:36:28.892>
    closer = 'mark.dickinson'
    components = ['Extension Modules']
    creation = <Date 2010-05-07.12:24:53.881>
    creator = 'mark.dickinson'
    dependencies = []
    files = ['17255', '17267']
    hgrepos = []
    issue_num = 8644
    keywords = ['patch']
    message_count = 11.0
    messages = ['105197', '105198', '105209', '105232', '105233', '105251', '105290', '105291', '105294', '105342', '105378']
    nosy_count = 3.0
    nosy_names = ['mark.dickinson', 'belopolsky', 'pitrou']
    pr_nums = []
    priority = 'normal'
    resolution = 'fixed'
    stage = 'resolved'
    status = 'closed'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue8644'
    versions = ['Python 2.7']

    Activity

    1. mdickinson commented on May 7, 2010

      @mdickinson
      MemberAuthor

      I just noticed (while responding to bpo-8643) that timedelta.total_seconds() has some accuracy problems, especially for negative timedeltas:

      Python 3.2a0 (py3k:80840:80842, May  7 2010, 12:29:35) 
      [GCC 4.2.1 (Apple Inc. build 5659)] on darwin
      Type "help", "copyright", "credits" or "license" for more information.
      >>> from datetime import timedelta
      >>> td = timedelta(microseconds = -123)
      >>> td.total_seconds() # expect -0.000123
      -0.0001230000052601099

      This could be easily fixed by using integer arithmetic internally instead of float arithmetic:

      >>> 1e-6 * td.microseconds + td.seconds + 86400 * td.days
      -0.0001230000052601099
      >>> (td.microseconds + 1000000 * (td.seconds + 86400 * td.days)) / 1000000
      -0.000123

      This works especially nicely in combination with the new float repr and the improved accuracy of true division in 2.7 / 3.2, to give a total_seconds() whose repr is free of noise digits. (Well, for small timedeltas, anyway.)

      Since total_seconds is new in 2.7 and 3.2, I think it would be good to fix this before the 2.7 release.

    2. mdickinson commented on May 7, 2010

      @mdickinson
      MemberAuthor

      P.S. This change would also make total_seconds() consistent with division by timedelta(seconds=1):

      >>> td / timedelta(seconds=1)
      -0.000123

      In fact, this might even be a good way to re-implement total_seconds internally.

    3. abalkin commented on May 7, 2010

      @abalkin
      Member

      I am attaching a patch for py3k branch. I am +1 for backporting to 2.7 and I avoided relying on py3k timedelta/timedelta in the patch. The tests and docs will need to be modified for the backport.

      Technically speaking, this is a change in documented behavior for 2.7 because according to current docs, td.total_seconds() is equivalent to td.microseconds / 1000000 + td.seconds + td.days * 24 * 3600. Therefore I would appreciate if reviewer made a decision on backport.

    4. mdickinson commented on May 7, 2010

      @mdickinson
      MemberAuthor

      Thanks for the patch! Comments, in increasing order of fussiness:

      (1) there should be a Py_DECREF(total_microseconds) in the case that the "PyLong_FromLong(1000000L)" call fails

      (2) in the docstring, replace 'loose' by 'lose'

      (3) also in the docstring, I suggest replacing "280 years" by "270 years": "td == timedelta(seconds = td.total_seconds())" starts failing at 2**33 seconds (around 272.2 Julian years) rather than 2**53 microseconds.

    5. mdickinson commented on May 7, 2010

      @mdickinson
      MemberAuthor

      Grr. s/docstring/docs/

    6. abalkin commented on May 8, 2010

      @abalkin
      Member

      (1) there should be a Py_DECREF(total_microseconds) ...

      Yes, regrtest -R is not a substitute for thinking.

      (2) in the docstring, replace 'loose' by 'lose'

      Neither is spellcheck. :-)

      (3) also in the docstring, I suggest replacing "280 years" by "270 years" ...

      Yes, and precision is not accuracy.

    7. mdickinson commented on May 8, 2010

      @mdickinson
      MemberAuthor

      Looks good to me.

      I definitely think this should go into 2.7 as well; total_seconds has only seen the light of day in the beta releases so far, so it's unlikely we'd break anyone's code with this change. (And IMO this is a bugfix, not a feature.)

      It might be polite to wait a day or two for the 2.7 backport, though: 2.7 beta 2 is supposed to be being released today.

    8. mdickinson commented on May 8, 2010

      @mdickinson
      MemberAuthor

      If you want a second (third?) opinion, we could ask Antoine.

    9. mdickinson commented on May 8, 2010

      @mdickinson
      MemberAuthor

      [Antoine declines to offer an opinion.]

      Committed to py3k in r80979. I moved the Misc/NEWS entry from the 'Library' section to the 'Extension Modules' section.

      Leaving open for the 2.7 backport.

    10. abalkin commented on May 8, 2010

      @abalkin
      Member

      On Sat, May 8, 2010 at 10:07 AM, Mark Dickinson <report@bugs.python.org> wrote:
      ..

      I definitely think this should go into 2.7 as well;  total_seconds has only seen the light of day
      in the beta releases so far, so it's unlikely we'd break anyone's code with this change.
       (And IMO this is a bugfix, not a feature.)

      Tracker web site seems to be down, so I am trying my luck attaching a
      patch to an e-mail reply. It did work in the past.

      The true division by 10**6 could probably be replaced with division by
      1e6 in the tests and docs, but I am not sure if the two are always
      equivalent.

    11. mdickinson commented on May 9, 2010

      @mdickinson
      MemberAuthor

      Perfect! Applied in r81020.

      You're correct that n/10**6 and n/1e6 aren't the same thing, at least for n large enough:

      Python 2.7b2+ (trunk:81019:81020, May  9 2010, 10:33:17) 
      [GCC 4.2.1 (Apple Inc. build 5659)] on darwin
      Type "help", "copyright", "credits" or "license" for more information.
      >>> from __future__ import division
      [35402 refs]
      >>> (2**53+1)/10**6
      9007199254.740993
      [35404 refs]
      >>> (2**53+1)/1e6
      9007199254.740992
      [35404 refs]

      In the second case, 2**53+1 first gets converted to a float, and then the division is performed, so there are two points at which a rounding error can be introduced. The first case only involves one rounding error.

    12. transferred this issue fromon Apr 10, 2022
    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      extension-modulesC modules in the Modules dirtype-bugAn unexpected behavior, bug, or error

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions