So...can we apply our hard-earned knowledge of modular arithmetic to the task of getting difftime to do the right thing in the face of the various types that time_t is allowed to assume? A possibility would be to add to private.h... ------- private.h ------- *** /tmp/geta29733 Thu Oct 21 10:29:48 2004 --- /tmp/getb29733 Thu Oct 21 10:29:48 2004 *************** *** 21,27 **** #ifndef lint #ifndef NOID ! static char privatehid[] = "@(#)private.h 7.54"; #endif /* !defined NOID */ #endif /* !defined lint */ --- 21,27 ---- #ifndef lint #ifndef NOID ! static char privatehid[] = "@(#)private.h 7.55"; #endif /* !defined NOID */ #endif /* !defined lint */ *************** *** 238,243 **** --- 238,247 ---- #define TYPE_SIGNED(type) (((type) -1) < 0) #endif /* !defined TYPE_SIGNED */ + #ifndef TYPE_INTEGRAL + #define TYPE_INTEGRAL(type) (((type) 0.4) == 0) + #endif /* !defined TYPE_INTEGRAL */ + #ifndef INT_STRLEN_MAXIMUM /* ** 302 / 1000 is log10(2.0) rounded up. ...and then use the difftime.c that appears below. --ado /* ** This file is in the public domain, so clarified as of ** June 5, 1996 by Arthur David Olson (arthur_david_olson@nih.gov). */ #ifndef lint #ifndef NOID static char elsieid[] = "@(#)difftime.c 7.11"; #endif /* !defined NOID */ #endif /* !defined lint */ /*LINTLIBRARY*/ #include "sys/types.h" /* for time_t */ #include "private.h" /* for TYPE_INTEGRAL and TYPE_SIGNED */ double difftime(time1, time0) const time_t time1; const time_t time0; { if (!TYPE_INTEGRAL(time_t)) { /* ** time_t is floating. ** We can't apply % to floats. ** Do the math in whichever of time_t or double is wider. */ if (sizeof (time_t) >= sizeof (double)) return time1 - time0; else return (double) time1 - (double) time0; } else { /* ** time_t is integral. ** As elsewhere in the time zone package, ** use modular arithmetic to avoid overflow. */ register time_t lead; register time_t trail; lead = time1 / 2 - time0 / 2; trail = time1 % 2 - time0 % 2; return 2 * ((double) lead) + trail; } }
"Olson, Arthur David (NIH/NCI)" <olsona@dc37a.nci.nih.gov> writes:
+ #ifndef TYPE_INTEGRAL + #define TYPE_INTEGRAL(type) (((type) 0.4) == 0) + #endif /* !defined TYPE_INTEGRAL */
A couple of nits: C99 no longer calls these "integral types"; they're just "integer types". Also, this test is incorrect if "type" is the C99 type "bool", since ((bool) 0.4) yields 1. Perhaps a better test would be: #define TYPE_IS_INTEGER(type) (((type) 0.5) != 0.5) or something like that.
#include "private.h" /* for TYPE_INTEGRAL and TYPE_SIGNED */
The proposed code would no longer used TYPE_SIGNED, so that part of the comment isn't needed.
if (sizeof (time_t) >= sizeof (double)) return time1 - time0; else return (double) time1 - (double) time0;
You might want to add a comment here saying that we assume that more-precise representations require more size. The C Standard doesn't require this, but it's a pretty-safe assumption in practice.
} else { /* ** time_t is integral. ** As elsewhere in the time zone package, ** use modular arithmetic to avoid overflow. */ register time_t lead; register time_t trail;
lead = time1 / 2 - time0 / 2;
This won't work if time_t is unsigned and if time1/2 < time0/2. In that case "lead" should be negative, but the above code will compute a positive value.
trail = time1 % 2 - time0 % 2;
If time_t is floating, the compiler must issue a diagnostic here. Most compilers will reject the program.
return 2 * ((double) lead) + trail;
A minor point: it's a bit more elegant to write "return 2.0 * lead + trail;". However, there is a more important problem with the last line: it suffers from a double-rounding problem. In general, converting "lead" to double will lose information, and will cause a rounding error. Multiplying by 2 is exact (on all hosts of practical interest), but adding "trail" will cause another rounding error. I don't see any easy way to work around this problem. The code that I proposed (with Clive Feather's advice about padding bits) also suffers from a double-rounding error, but it will be far less of a practical problem, as it can occur only on very weird hosts with padding bits where UINTMAX_MAX / 2 < INTMAX_MAX. In contrast, I think the double-rounding problem above can occur on ordinary hosts with IEEE-754 floating point and 64-bit signed time_t. There's also a efficiency problem with the current code: it uses the integer-arithmetic approach even when it's not needed. For example, in the common case when time_t is a 32-bit integer and "double" is IEEE-754 double, it's faster and simpler to convert the time_t to double and subtract the doubles, whereas the proposed approach has some extra integer bit-twiddling and an extra floating-point multiplication.
participants (2)
-
Olson, Arthur David (NIH/NCI) -
Paul Eggert