proposed patch to difftime.c and Makefile for tz code

Paul Eggert eggert at CS.UCLA.EDU
Wed Aug 11 20:37:54 UTC 2004


Here's a proposed patch to difftime.c and Makefile to reflect all the
recent back-and-forth between Clive D.W. Feather and myself.  The main
practical import of this patch is the avoidance of a double-rounding
bug on hosts with 64-bit time_t and 64-bit 'long double', but it also
fixes some much-bigger errors on unusual hosts that have holes in
their integer representations.  Thanks, Clive!

*** Makefile	2004/08/11 15:59:05	2004.3
--- Makefile	2004/08/11 20:30:07	2004.3.0.1
*************** LDLIBS=
*** 87,105 ****
  
  # Add the following to the end of the "CFLAGS=" line as needed.
  #  -Dconst= if `const' does not work (SunOS 4.x cc, OSF1 V5.0 cc)
  #  -DHAVE_ADJTIME=0 if `adjtime' does not exist (SVR0?)
  #  -DHAVE_GETTEXT=1 if `gettext' works (GNU, Linux, Solaris); also see LDLIBS
  #  -DHAVE_INCOMPATIBLE_CTIME_R=1 if your system's time.h declares
  #	ctime_r and asctime_r incompatibly with the POSIX standard (Solaris 8).
! #  -DHAVE_LONG_DOUBLE=1 if your compiler supports the `long double' type
  #  -DHAVE_SETTIMEOFDAY=0 if settimeofday does not exist (SVR0?)
  #  -DHAVE_SETTIMEOFDAY=1 if settimeofday has just 1 arg (SVR4)
  #  -DHAVE_SETTIMEOFDAY=2 if settimeofday uses 2nd arg (4.3BSD)
  #  -DHAVE_SETTIMEOFDAY=3 if settimeofday ignores 2nd arg (4.4BSD)
  #  -DHAVE_STRERROR=0 if your system lacks the strerror function
  #  -DHAVE_SYMLINK=0 if your system lacks the symlink function
  #  -DHAVE_SYS_STAT_H=0 if your compiler lacks a "sys/stat.h"
  #  -DHAVE_SYS_WAIT_H=0 if your compiler lacks a "sys/wait.h"
  #  -DLOCALE_HOME=\"path\" if locales are in "path", not "/usr/lib/locale"
  #  -DHAVE_UNISTD_H=0 if your compiler lacks a "unistd.h" (Microsoft C++ 7?)
  #  -DHAVE_UTMPX_H=1 if your compiler has a "utmpx.h"
--- 87,110 ----
  
  # Add the following to the end of the "CFLAGS=" line as needed.
  #  -Dconst= if `const' does not work (SunOS 4.x cc, OSF1 V5.0 cc)
+ #  -Duintmax='unsigned long long int' if your pre-C99 compiler has such a type
  #  -DHAVE_ADJTIME=0 if `adjtime' does not exist (SVR0?)
  #  -DHAVE_GETTEXT=1 if `gettext' works (GNU, Linux, Solaris); also see LDLIBS
  #  -DHAVE_INCOMPATIBLE_CTIME_R=1 if your system's time.h declares
  #	ctime_r and asctime_r incompatibly with the POSIX standard (Solaris 8).
! #  -DHAVE_LONG_DOUBLE=1 if your pre-C89 compiler has the `long double' type
  #  -DHAVE_SETTIMEOFDAY=0 if settimeofday does not exist (SVR0?)
  #  -DHAVE_SETTIMEOFDAY=1 if settimeofday has just 1 arg (SVR4)
  #  -DHAVE_SETTIMEOFDAY=2 if settimeofday uses 2nd arg (4.3BSD)
  #  -DHAVE_SETTIMEOFDAY=3 if settimeofday ignores 2nd arg (4.4BSD)
+ #  -DHAVE_STDINT_H=1 if your pre-C99 compiler has a "stdint.h"
  #  -DHAVE_STRERROR=0 if your system lacks the strerror function
  #  -DHAVE_SYMLINK=0 if your system lacks the symlink function
  #  -DHAVE_SYS_STAT_H=0 if your compiler lacks a "sys/stat.h"
  #  -DHAVE_SYS_WAIT_H=0 if your compiler lacks a "sys/wait.h"
+ #  -DINTMAX_MAX=9223372036854775807 (or to the greatest signed integer,
+ #	whatever it is) if your compiler lacks INTMAX_MAX, and if LLONG_MAX
+ #	(or LONG_MAX, if LLONG_MAX is not defined) is not the greatest
  #  -DLOCALE_HOME=\"path\" if locales are in "path", not "/usr/lib/locale"
  #  -DHAVE_UNISTD_H=0 if your compiler lacks a "unistd.h" (Microsoft C++ 7?)
  #  -DHAVE_UTMPX_H=1 if your compiler has a "utmpx.h"
*** difftime.c	2002/01/28 15:36:25	2002.2
--- difftime.c	2004/08/11 20:13:16	2002.2.0.3
*************** static char	elsieid[] = "@(#)difftime.c	
*** 14,83 ****
  #include "private.h"
  
  /*
! ** Algorithm courtesy Paul Eggert (eggert at twinsun.com).
  */
  
! #ifdef HAVE_LONG_DOUBLE
! #define long_double	long double
! #endif /* defined HAVE_LONG_DOUBLE */
  #ifndef HAVE_LONG_DOUBLE
! #define long_double	double
  #endif /* !defined HAVE_LONG_DOUBLE */
  
  double
  difftime(time1, time0)
! const time_t	time1;
! const time_t	time0;
  {
! 	time_t	delta;
! 	time_t	hibit;
  
- 	{
- 		time_t		tt;
- 		double		d;
- 		long_double	ld;
- 
- 		if (sizeof tt < sizeof d)
- 			return (double) time1 - (double) time0;
- 		if (sizeof tt < sizeof ld)
- 			return (long_double) time1 - (long_double) time0;
- 	}
- 	if (time1 < time0)
- 		return -difftime(time0, time1);
- 	/*
- 	** As much as possible, avoid loss of precision
- 	** by computing the difference before converting to double.
- 	*/
- 	delta = time1 - time0;
- 	if (delta >= 0)
- 		return delta;
  	/*
! 	** Repair delta overflow.
  	*/
! 	hibit = (~ (time_t) 0) << (TYPE_BIT(time_t) - 1);
  	/*
! 	** The following expression rounds twice, which means
! 	** the result may not be the closest to the true answer.
! 	** For example, suppose time_t is 64-bit signed int,
! 	** long_double is IEEE 754 double with default rounding,
! 	** time1 = 9223372036854775807 and time0 = -1536.
! 	** Then the true difference is 9223372036854777343,
! 	** which rounds to 9223372036854777856
! 	** with a total error of 513.
! 	** But delta overflows to -9223372036854774273,
! 	** which rounds to -9223372036854774784, and correcting
! 	** this by subtracting 2 * (long_double) hibit
! 	** (i.e. by adding 2**64 = 18446744073709551616)
! 	** yields 9223372036854776832, which
! 	** rounds to 9223372036854775808
! 	** with a total error of 1535 instead.
! 	** This problem occurs only with very large differences.
! 	** It's too painful to fix this portably.
! 	** We are not alone in this problem;
! 	** some C compilers round twice when converting
! 	** large unsigned types to small floating types,
! 	** so if time_t is unsigned the "return delta" above
! 	** has the same double-rounding problem with those compilers.
  	*/
! 	return delta - 2 * (long_double) hibit;
  }
--- 14,175 ----
  #include "private.h"
  
  /*
! ** Algorithm courtesy Paul Eggert (eggert at cs.ucla.edu).
! **
! ** Most other code assumes that time_t is an integer type without
! ** padding bits, and that integer arithmetic is modular two's
! ** complement without overflow traps, but (just for fun) this works
! ** even if time_t is an integer type with padding bits, or a real
! ** floating type, and it works even if signed integer overflow
! ** has undefined behavior.
  */
  
! #include <float.h>
! 
! #define TYPE_FLOATING(type) ((type) 0.4 != 0)
! 
! #if !defined HAVE_LONG_DOUBLE && defined __STDC__
! #define HAVE_LONG_DOUBLE	1
! #endif /* !defined HAVE_LONG_DOUBLE && defined __STDC__ */
  #ifndef HAVE_LONG_DOUBLE
! #define HAVE_LONG_DOUBLE	0
  #endif /* !defined HAVE_LONG_DOUBLE */
  
+ #if HAVE_LONG_DOUBLE
+ #define long_double	long double
+ #endif /* HAVE_LONG_DOUBLE */
+ #if !HAVE_LONG_DOUBLE
+ #define long_double	double
+ #endif /* !HAVE_LONG_DOUBLE */
+ 
+ #ifndef HAVE_STDINT_H
+ #define HAVE_STDINT_H		(199901L <= __STDC_VERSION__)
+ #endif /* !defined HAVE_STDINT_H */
+ 
+ #if HAVE_STDINT_H
+ #include <stdint.h>
+ #define uintmax uintmax_t
+ #endif /* HAVE_STDINT_H */
+ #if !defined uintmax && defined ULLONG_MAX
+ #define uintmax unsigned long long int
+ #endif /* !defined uintmax && defined ULLONG_MAX */
+ #ifndef uintmax
+ #define uintmax unsigned long int
+ #endif /* defined uintmax */
+ 
+ #ifndef UINTMAX_MAX
+ #define UINTMAX_MAX ((uintmax) -1)
+ #endif /* !defined UINTMAX_MAX */
+ 
+ #if !defined INTMAX_MAX && defined LLONG_MAX
+ #define INTMAX_MAX LLONG_MAX
+ #endif /* !defined INTMAX_MAX && defined LLONG_MAX */
+ #ifndef INTMAX_MAX
+ #define INTMAX_MAX LONG_MAX
+ #endif /* !defined INTMAX_MAX */
+ 
  double
  difftime(time1, time0)
! time_t	time1;
! time_t	time0;
  {
! 	int time1_is_smaller;
! 	double delta;
  
  	/*
! 	** Use floating point if there should be no double-rounding error.
! 	** However, avoid long double if it must be wider than needed,
! 	** as it's sometimes much more expensive in these cases
! 	** (e.g., 64-bit sparc).
  	*/
! 	if (TYPE_BIT(time_t) <= DBL_MANT_DIG
! 	    || (TYPE_FLOATING(time_t)
! 		&& sizeof(time_t) < sizeof(long_double))) {
! 		double t1 = time1;
! 		double t0 = time0;
! 		return t1 - t0;
! 	}
! 	if ((TYPE_BIT(time_t) <= LDBL_MANT_DIG
! 	     && (TYPE_BIT(time_t) == LDBL_MANT_DIG
! 		 || (TYPE_SIGNED(time_t) && UINTMAX_MAX / 2 < INTMAX_MAX)))
! 	    || TYPE_FLOATING(time_t)) {
! 		long_double t1 = time1;
! 		long_double t0 = time0;
! 		return t1 - t0;
! 	}
! 
! 	time1_is_smaller = time1 < time0;
! 	if (time1_is_smaller) {
! 		time_t t = time1;
! 		time0 = time1;
! 		time1 = t;
! 	}
! 
  	/*
! 	** Now time0 <= time1, and time_t is an integer type.
! 	** Optimize the common special cases where time_t is unsigned,
! 	** or can be converted to uintmax without losing information.
  	*/
! 	if (! TYPE_SIGNED(time_t))
! 		delta = time1 - time0;
! 	else {
! 		uintmax t1 = time1;
! 		uintmax t0 = time0;
! 		uintmax dt = t1 - t0;
! 		delta = dt;
! 		if (UINTMAX_MAX / 2 < INTMAX_MAX) {
! 			/*
! 			** uintmax has padding bits, and time_t is signed.
! 			** Check for overflow: compare dt/2 to (time1/2 -
! 			** time0/2).  Overflow occurred if they differ by
! 			** more than a small slop.
! 			**
! 			** Thanks to Clive D.W. Feather for detailed technical
! 			** advice about hosts with padding bits.
! 			*/
! 			uintmax hdt = dt / 2;
! 			time_t ht1 = time1 / 2;
! 			time_t ht0 = time0 / 2;
! 			time_t dht = ht1 - ht0;
! 			/*
! 			** "h" here means half.  By range analysis, we have:
! 			**	-0.5 <= ht1 - time1/2 <= 0.5
! 			**	-0.5 <= ht0 - time0/2 <= 0.5
! 			**	-1.0 <= dht - (time1 - time0)/2 <= 1.0
! 			** If overflow has not occurred, we also have:
! 			**	-0.5 <= hdt - (time1 - time0)/2 <= 0
! 			**	-1.0 <= dht - hdt <= 1.5
! 			** and since dht - hdt is an integer, we also have:
! 			**	-1 <= dht - hdt <= 1
! 			** or equivalently:
! 			**	0 <= dht - hdt + 1 <= 2
! 			** In the above analysis, all the operators have
! 			** their exact mathematical semantics, not C semantics.
! 			** However, dht - hdt + 1 is unsigned in C,
! 			** so it need not be compared to zero.
! 			*/
! 			if (2 < dht - hdt + 1) {
! 				/*
! 				** Repair delta overflow.
! 				**
! 				** The following expression contains a second
! 				** rounding, so the result may not be the
! 				** closest to the true answer.  This problem
! 				** occurs only with very large differences,
! 				** It's too painful to fix this portably.
! 				** We are not alone in this problem; some C
! 				** compilers round twice when converting
! 				** large unsigned types to small floating
! 				** types, so if time_t is unsigned the
! 				** "delta = dt" above has the same
! 				** double-rounding problem with those
! 				** compilers.
! 				*/
! 				long_double hibit = ~(UINTMAX_MAX / 2);
! 				delta = dt + 2 * hibit;
! 			}
! 		}
! 	}
! 
! 	return time1_is_smaller ? -delta : delta;
  }



More information about the tz mailing list