Difftime code
Clive D.W. Feather
clive at demon.net
Fri Aug 6 07:35:53 UTC 2004
Being in a hurry this morning, I wrote:
>> || (TYPE_FLOATING (time_t) && sizeof (time_t) != sizeof (long double)))
> Okay, this proves that time_t isn't long double. Clever.
That is, if this is true then time_t isn't long double. Still clever.
> The problem occurs when time_t is signed and the maximum value of time_t
> is the same as the maximum value of uintmax_t.
On C99, this can only happen if UINTMAX_MAX == INTMAX_MAX. On C89 it can
only happen if ULONG_MAX == LONG_MAX. If you're willing to ensure these
symbols are present on non-Standard systems, you can filter out many cases
at compile time.
> For example, a C89 system
> where long is 60 bits including sign and unsigned long is 59 bits.
> On such systems, the maximum possible difference is greater than the
> maximum value of uintmax_t, and your subtract will get it wrong.
The worst case on such a system (using ** for powers) is:
time1 == 2**59 - 1
time0 == -2**59
UINTMAX_MAX == 2**59 - 1
time1-time0 == 2**60 - 1, which will overflow and come out as 2**59 - 1
> I gate this case by looking for time1 >=0 and time0 < 0. In fact, you can
> be safer than that:
>
> #define HALFMAX ((uintmax_t)-1 >> 1)
>
> if (time1 <= HALFMAX && (time0 >= 0 || (uintmax_t) time0 >= -HALFMAX))
> return (uintmax_t) time1 - (uintmax_t) time0;
There are in fact many ways to filter out easy cases:
* If time1 and time0 have the same sign (treating 0 as positive) then you
can't have an overflow (original posting).
* If time1 and -time0 both have the top bit zero, you can't have an
overflow (posting quoted just above).
* If time1/2 - time0/2 is less than UINTMAX_MAX/2, you can't have an
overflow.
Take your pick.
Attempting to summarize yet again:
* If time_t is a floating point type, we can just do the calculation in
long double. Furthermore, unless it's long double we can do it in double.
[Note, however, that C99 left open the possibility of more floating
point types in future standards.]
* If time_t is an integer type, we can nearly always convert to uintmax_t,
subtract, and convert to double.
* There are a few implementations where this subtraction could overflow.
There are compile- and run-time tests which can identify whether a
particular call to difftime is "at risk"; if it is, it then becomes
necessary to either:
- determine whether the simple subtraction is correct or is too low by
UINTMAX_MAX + 1, and adjust the result in the latter case;
- do the calculation in floating point, risking a loss of precision.
Any code which fails to do all of this is sometimes going to get the wrong
answer.
Any code which does all of this is going to look "pretty complicated".
--
Clive D.W. Feather | Work: <clive at demon.net> | Tel: +44 20 8495 6138
Internet Expert | Home: <clive at davros.org> | Fax: +44 870 051 9937
Demon Internet | WWW: http://www.davros.org | Mobile: +44 7973 377646
Thus plc | |
More information about the tz
mailing list