chutest.c revision 290001
1/* chutest.c,v 3.1 1993/07/06 01:05:21 jbj Exp
2 * chutest - test the CHU clock
3 */
4
5#ifdef HAVE_CONFIG_H
6# include <config.h>
7#endif
8#include <stdio.h>
9#include <fcntl.h>
10#ifdef HAVE_UNISTD_H
11# include <unistd.h>
12#endif
13#ifdef HAVE_STROPTS_H
14# include <stropts.h>
15#else
16# ifdef HAVE_SYS_STROPTS_H
17#  include <sys/stropts.h>
18# endif
19#endif
20#include <sys/types.h>
21#include <sys/socket.h>
22#include <netinet/in.h>
23#include <sys/ioctl.h>
24#include <sys/time.h>
25#include <sys/file.h>
26#ifdef HAVE_TERMIOS_H
27# include <termios.h>
28#else
29# ifdef HAVE_SGTTY_H
30#  include <sgtty.h>
31# endif
32#endif
33
34#include "ntp_fp.h"
35#include "ntp.h"
36#include "ntp_unixtime.h"
37#include "ntp_calendar.h"
38
39#ifdef CHULDISC
40# ifdef HAVE_SYS_CHUDEFS_H
41#  include <sys/chudefs.h>
42# endif
43#endif
44
45
46#ifndef CHULDISC
47#define	NCHUCHARS	(10)
48
49struct chucode {
50	u_char codechars[NCHUCHARS];	/* code characters */
51	u_char ncodechars;		/* number of code characters */
52	u_char chustatus;		/* not used currently */
53	struct timeval codetimes[NCHUCHARS];	/* arrival times */
54};
55#endif
56
57#define	STREQ(a, b)	(*(a) == *(b) && strcmp((a), (b)) == 0)
58
59char const *progname;
60
61int dofilter = 0;	/* set to 1 when we should run filter algorithm */
62int showtimes = 0;	/* set to 1 when we should show char arrival times */
63int doprocess = 0;	/* set to 1 when we do processing analogous to driver */
64#ifdef CHULDISC
65int usechuldisc = 0;	/* set to 1 when CHU line discipline should be used */
66#endif
67#ifdef STREAM
68int usechuldisc = 0;	/* set to 1 when CHU line discipline should be used */
69#endif
70
71struct timeval lasttv;
72struct chucode chudata;
73
74void	error(char *fmt, char *s1, char *s2);
75void	init_chu(void);
76int	openterm(char *dev);
77int	process_raw(int s);
78int	process_ldisc(int s);
79void	raw_filter(unsigned int c, struct timeval *tv);
80void	chufilter(struct chucode *chuc,	l_fp *rtime);
81
82
83/*
84 * main - parse arguments and handle options
85 */
86int
87main(
88	int argc,
89	char *argv[]
90	)
91{
92	int c;
93	int errflg = 0;
94	extern int ntp_optind;
95
96	progname = argv[0];
97	while ((c = ntp_getopt(argc, argv, "cdfpt")) != EOF)
98	    switch (c) {
99		case 'c':
100#ifdef STREAM
101		    usechuldisc = 1;
102		    break;
103#endif
104#ifdef CHULDISC
105		    usechuldisc = 1;
106		    break;
107#endif
108#ifndef STREAM
109#ifndef CHULDISC
110		    (void) fprintf(stderr,
111				   "%s: CHU line discipline not available on this machine\n",
112				   progname);
113		    exit(2);
114#endif
115#endif
116		case 'd':
117		    ++debug;
118		    break;
119		case 'f':
120		    dofilter = 1;
121		    break;
122		case 'p':
123		    doprocess = 1;
124		case 't':
125		    showtimes = 1;
126		    break;
127		default:
128		    errflg++;
129		    break;
130	    }
131	if (errflg || ntp_optind+1 != argc) {
132#ifdef STREAM
133		(void) fprintf(stderr, "usage: %s [-dft] tty_device\n",
134			       progname);
135#endif
136#ifdef CHULDISC
137		(void) fprintf(stderr, "usage: %s [-dft] tty_device\n",
138			       progname);
139#endif
140#ifndef STREAM
141#ifndef CHULDISC
142		(void) fprintf(stderr, "usage: %s [-cdft] tty_device\n",
143			       progname);
144#endif
145#endif
146		exit(2);
147	}
148
149	(void) gettimeofday(&lasttv, (struct timezone *)0);
150	c = openterm(argv[ntp_optind]);
151	init_chu();
152#ifdef STREAM
153	if (usechuldisc)
154	    process_ldisc(c);
155	else
156#endif
157#ifdef CHULDISC
158	    if (usechuldisc)
159		process_ldisc(c);
160	    else
161#endif
162		process_raw(c);
163	/*NOTREACHED*/
164}
165
166
167/*
168 * openterm - open a port to the CHU clock
169 */
170int
171openterm(
172	char *dev
173	)
174{
175	int s;
176	struct sgttyb ttyb;
177
178	if (debug)
179	    (void) fprintf(stderr, "Doing open...");
180	if ((s = open(dev, O_RDONLY, 0777)) < 0)
181	    error("open(%s)", dev, "");
182	if (debug)
183	    (void) fprintf(stderr, "open okay\n");
184
185	if (debug)
186	    (void) fprintf(stderr, "Setting exclusive use...");
187	if (ioctl(s, TIOCEXCL, (char *)0) < 0)
188	    error("ioctl(TIOCEXCL)", "", "");
189	if (debug)
190	    (void) fprintf(stderr, "done\n");
191
192	ttyb.sg_ispeed = ttyb.sg_ospeed = B300;
193	ttyb.sg_erase = ttyb.sg_kill = 0;
194	ttyb.sg_flags = EVENP|ODDP|RAW;
195	if (debug)
196	    (void) fprintf(stderr, "Setting baud rate et al...");
197	if (ioctl(s, TIOCSETP, (char *)&ttyb) < 0)
198	    error("ioctl(TIOCSETP, raw)", "", "");
199	if (debug)
200	    (void) fprintf(stderr, "done\n");
201
202#ifdef CHULDISC
203	if (usechuldisc) {
204		int ldisc;
205
206		if (debug)
207		    (void) fprintf(stderr, "Switching to CHU ldisc...");
208		ldisc = CHULDISC;
209		if (ioctl(s, TIOCSETD, (char *)&ldisc) < 0)
210		    error("ioctl(TIOCSETD, CHULDISC)", "", "");
211		if (debug)
212		    (void) fprintf(stderr, "okay\n");
213	}
214#endif
215#ifdef STREAM
216	if (usechuldisc) {
217
218		if (debug)
219		    (void) fprintf(stderr, "Poping off streams...");
220		while (ioctl(s, I_POP, 0) >=0) ;
221		if (debug)
222		    (void) fprintf(stderr, "okay\n");
223		if (debug)
224		    (void) fprintf(stderr, "Pushing CHU stream...");
225		if (ioctl(s, I_PUSH, "chu") < 0)
226		    error("ioctl(I_PUSH, \"chu\")", "", "");
227		if (debug)
228		    (void) fprintf(stderr, "okay\n");
229	}
230#endif
231	return s;
232}
233
234
235/*
236 * process_raw - process characters in raw mode
237 */
238int
239process_raw(
240	int s
241	)
242{
243	u_char c;
244	int n;
245	struct timeval tv;
246	struct timeval difftv;
247
248	while ((n = read(s, &c, sizeof(char))) > 0) {
249		(void) gettimeofday(&tv, (struct timezone *)0);
250		if (dofilter)
251		    raw_filter((unsigned int)c, &tv);
252		else {
253			difftv.tv_sec = tv.tv_sec - lasttv.tv_sec;
254			difftv.tv_usec = tv.tv_usec - lasttv.tv_usec;
255			if (difftv.tv_usec < 0) {
256				difftv.tv_sec--;
257				difftv.tv_usec += 1000000;
258			}
259			(void) printf("%02x\t%lu.%06lu\t%lu.%06lu\n",
260				      c, tv.tv_sec, tv.tv_usec, difftv.tv_sec,
261				      difftv.tv_usec);
262			lasttv = tv;
263		}
264	}
265
266	if (n == 0) {
267		(void) fprintf(stderr, "%s: zero returned on read\n", progname);
268		exit(1);
269	} else
270	    error("read()", "", "");
271}
272
273
274/*
275 * raw_filter - run the line discipline filter over raw data
276 */
277void
278raw_filter(
279	unsigned int c,
280	struct timeval *tv
281	)
282{
283	static struct timeval diffs[10];
284	struct timeval diff;
285	l_fp ts;
286
287	if ((c & 0xf) > 9 || ((c>>4)&0xf) > 9) {
288		if (debug)
289		    (void) fprintf(stderr,
290				   "character %02x failed BCD test\n", c);
291		chudata.ncodechars = 0;
292		return;
293	}
294
295	if (chudata.ncodechars > 0) {
296		diff.tv_sec = tv->tv_sec
297			- chudata.codetimes[chudata.ncodechars].tv_sec;
298		diff.tv_usec = tv->tv_usec
299			- chudata.codetimes[chudata.ncodechars].tv_usec;
300		if (diff.tv_usec < 0) {
301			diff.tv_sec--;
302			diff.tv_usec += 1000000;
303		} /*
304		    if (diff.tv_sec != 0 || diff.tv_usec > 900000) {
305		    if (debug)
306		    (void) fprintf(stderr,
307		    "character %02x failed time test\n");
308		    chudata.ncodechars = 0;
309		    return;
310		    } */
311	}
312
313	chudata.codechars[chudata.ncodechars] = c;
314	chudata.codetimes[chudata.ncodechars] = *tv;
315	if (chudata.ncodechars > 0)
316	    diffs[chudata.ncodechars] = diff;
317	if (++chudata.ncodechars == 10) {
318		if (doprocess) {
319			TVTOTS(&chudata.codetimes[NCHUCHARS-1], &ts);
320			ts.l_ui += JAN_1970;
321			chufilter(&chudata, &chudata.codetimes[NCHUCHARS-1]);
322		} else {
323			register int i;
324
325			for (i = 0; i < chudata.ncodechars; i++) {
326				(void) printf("%x%x\t%lu.%06lu\t%lu.%06lu\n",
327					      chudata.codechars[i] & 0xf,
328					      (chudata.codechars[i] >>4 ) & 0xf,
329					      chudata.codetimes[i].tv_sec,
330					      chudata.codetimes[i].tv_usec,
331					      diffs[i].tv_sec, diffs[i].tv_usec);
332			}
333		}
334		chudata.ncodechars = 0;
335	}
336}
337
338
339/* #ifdef CHULDISC*/
340/*
341 * process_ldisc - process line discipline
342 */
343int
344process_ldisc(
345	int s
346	)
347{
348	struct chucode chu;
349	int n;
350	register int i;
351	struct timeval diff;
352	l_fp ts;
353	void chufilter();
354
355	while ((n = read(s, (char *)&chu, sizeof chu)) > 0) {
356		if (n != sizeof chu) {
357			(void) fprintf(stderr, "Expected %d, got %d\n",
358				       sizeof chu, n);
359			continue;
360		}
361
362		if (doprocess) {
363			TVTOTS(&chu.codetimes[NCHUCHARS-1], &ts);
364			ts.l_ui += JAN_1970;
365			chufilter(&chu, &ts);
366		} else {
367			for (i = 0; i < NCHUCHARS; i++) {
368				if (i == 0)
369				    diff.tv_sec = diff.tv_usec = 0;
370				else {
371					diff.tv_sec = chu.codetimes[i].tv_sec
372						- chu.codetimes[i-1].tv_sec;
373					diff.tv_usec = chu.codetimes[i].tv_usec
374						- chu.codetimes[i-1].tv_usec;
375					if (diff.tv_usec < 0) {
376						diff.tv_sec--;
377						diff.tv_usec += 1000000;
378					}
379				}
380				(void) printf("%x%x\t%lu.%06lu\t%lu.%06lu\n",
381					      chu.codechars[i] & 0xf, (chu.codechars[i]>>4)&0xf,
382					      chu.codetimes[i].tv_sec, chu.codetimes[i].tv_usec,
383					      diff.tv_sec, diff.tv_usec);
384			}
385		}
386	}
387	if (n == 0) {
388		(void) fprintf(stderr, "%s: zero returned on read\n", progname);
389		exit(1);
390	} else
391	    error("read()", "", "");
392}
393/*#endif*/
394
395
396/*
397 * error - print an error message
398 */
399void
400error(
401	char *fmt,
402	char *s1,
403	char *s2
404	)
405{
406	(void) fprintf(stderr, "%s: ", progname);
407	(void) fprintf(stderr, fmt, s1, s2);
408	(void) fprintf(stderr, ": ");
409	perror("");
410	exit(1);
411}
412
413/*
414 * Definitions
415 */
416#define	MAXUNITS	4	/* maximum number of CHU units permitted */
417#define	CHUDEV	"/dev/chu%d"	/* device we open.  %d is unit number */
418#define	NCHUCODES	9	/* expect 9 CHU codes per minute */
419
420/*
421 * When CHU is operating optimally we want the primary clock distance
422 * to come out at 300 ms.  Thus, peer.distance in the CHU peer structure
423 * is set to 290 ms and we compute delays which are at least 10 ms long.
424 * The following are 290 ms and 10 ms expressed in u_fp format
425 */
426#define	CHUDISTANCE	0x00004a3d
427#define	CHUBASEDELAY	0x0000028f
428
429/*
430 * To compute a quality for the estimate (a pseudo delay) we add a
431 * fixed 10 ms for each missing code in the minute and add to this
432 * the sum of the differences between the remaining offsets and the
433 * estimated sample offset.
434 */
435#define	CHUDELAYPENALTY	0x0000028f
436
437/*
438 * Other constant stuff
439 */
440#define	CHUPRECISION	(-9)		/* what the heck */
441#define	CHUREFID	"CHU\0"
442
443/*
444 * Default fudge factors
445 */
446#define	DEFPROPDELAY	0x00624dd3	/* 0.0015 seconds, 1.5 ms */
447#define	DEFFILTFUDGE	0x000d1b71	/* 0.0002 seconds, 200 us */
448
449/*
450 * Hacks to avoid excercising the multiplier.  I have no pride.
451 */
452#define	MULBY10(x)	(((x)<<3) + ((x)<<1))
453#define	MULBY60(x)	(((x)<<6) - ((x)<<2))	/* watch overflow */
454#define	MULBY24(x)	(((x)<<4) + ((x)<<3))
455
456/*
457 * Constants for use when multiplying by 0.1.  ZEROPTONE is 0.1
458 * as an l_fp fraction, NZPOBITS is the number of significant bits
459 * in ZEROPTONE.
460 */
461#define	ZEROPTONE	0x1999999a
462#define	NZPOBITS	29
463
464/*
465 * The CHU table.  This gives the expected time of arrival of each
466 * character after the on-time second and is computed as follows:
467 * The CHU time code is sent at 300 bps.  Your average UART will
468 * synchronize at the edge of the start bit and will consider the
469 * character complete at the center of the first stop bit, i.e.
470 * 0.031667 ms later.  Thus the expected time of each interrupt
471 * is the start bit time plus 0.031667 seconds.  These times are
472 * in chutable[].  To this we add such things as propagation delay
473 * and delay fudge factor.
474 */
475#define	CHARDELAY	0x081b4e80
476
477static u_long chutable[NCHUCHARS] = {
478	0x2147ae14 + CHARDELAY,		/* 0.130 (exactly) */
479	0x2ac08312 + CHARDELAY,		/* 0.167 (exactly) */
480	0x34395810 + CHARDELAY,		/* 0.204 (exactly) */
481	0x3db22d0e + CHARDELAY,		/* 0.241 (exactly) */
482	0x472b020c + CHARDELAY,		/* 0.278 (exactly) */
483	0x50a3d70a + CHARDELAY,		/* 0.315 (exactly) */
484	0x5a1cac08 + CHARDELAY,		/* 0.352 (exactly) */
485	0x63958106 + CHARDELAY,		/* 0.389 (exactly) */
486	0x6d0e5604 + CHARDELAY,		/* 0.426 (exactly) */
487	0x76872b02 + CHARDELAY,		/* 0.463 (exactly) */
488};
489
490/*
491 * Keep the fudge factors separately so they can be set even
492 * when no clock is configured.
493 */
494static l_fp propagation_delay;
495static l_fp fudgefactor;
496static l_fp offset_fudge;
497
498/*
499 * We keep track of the start of the year, watching for changes.
500 * We also keep track of whether the year is a leap year or not.
501 * All because stupid CHU doesn't include the year in the time code.
502 */
503static u_long yearstart;
504
505/*
506 * Imported from the timer module
507 */
508extern u_long current_time;
509extern struct event timerqueue[];
510
511/*
512 * init_chu - initialize internal chu driver data
513 */
514void
515init_chu(void)
516{
517
518	/*
519	 * Initialize fudge factors to default.
520	 */
521	propagation_delay.l_ui = 0;
522	propagation_delay.l_uf = DEFPROPDELAY;
523	fudgefactor.l_ui = 0;
524	fudgefactor.l_uf = DEFFILTFUDGE;
525	offset_fudge = propagation_delay;
526	L_ADD(&offset_fudge, &fudgefactor);
527
528	yearstart = 0;
529}
530
531
532void
533chufilter(
534	struct chucode *chuc,
535	l_fp *rtime
536	)
537{
538	register int i;
539	register u_long date_ui;
540	register u_long tmp;
541	register u_char *code;
542	int isneg;
543	int imin;
544	int imax;
545	u_long reftime;
546	l_fp off[NCHUCHARS];
547	l_fp ts;
548	int day, hour, minute, second;
549	static u_char lastcode[NCHUCHARS];
550
551	/*
552	 * We'll skip the checks made in the kernel, but assume they've
553	 * been done.  This means that all characters are BCD and
554	 * the intercharacter spacing isn't unreasonable.
555	 */
556
557	/*
558	 * print the code
559	 */
560	for (i = 0; i < NCHUCHARS; i++)
561	    printf("%c%c", (chuc->codechars[i] & 0xf) + '0',
562		   ((chuc->codechars[i]>>4) & 0xf) + '0');
563	printf("\n");
564
565	/*
566	 * Format check.  Make sure the two halves match.
567	 */
568	for (i = 0; i < NCHUCHARS/2; i++)
569	    if (chuc->codechars[i] != chuc->codechars[i+(NCHUCHARS/2)]) {
570		    (void) printf("Bad format, halves don't match\n");
571		    return;
572	    }
573
574	/*
575	 * Break out the code into the BCD nibbles.  Only need to fiddle
576	 * with the first half since both are identical.  Note the first
577	 * BCD character is the low order nibble, the second the high order.
578	 */
579	code = lastcode;
580	for (i = 0; i < NCHUCHARS/2; i++) {
581		*code++ = chuc->codechars[i] & 0xf;
582		*code++ = (chuc->codechars[i] >> 4) & 0xf;
583	}
584
585	/*
586	 * If the first nibble isn't a 6, we're up the creek
587	 */
588	code = lastcode;
589	if (*code++ != 6) {
590		(void) printf("Bad format, no 6 at start\n");
591		return;
592	}
593
594	/*
595	 * Collect the day, the hour, the minute and the second.
596	 */
597	day = *code++;
598	day = MULBY10(day) + *code++;
599	day = MULBY10(day) + *code++;
600	hour = *code++;
601	hour = MULBY10(hour) + *code++;
602	minute = *code++;
603	minute = MULBY10(minute) + *code++;
604	second = *code++;
605	second = MULBY10(second) + *code++;
606
607	/*
608	 * Sanity check the day and time.  Note that this
609	 * only occurs on the 31st through the 39th second
610	 * of the minute.
611	 */
612	if (day < 1 || day > 366
613	    || hour > 23 || minute > 59
614	    || second < 31 || second > 39) {
615		(void) printf("Failed date sanity check: %d %d %d %d\n",
616			      day, hour, minute, second);
617		return;
618	}
619
620	/*
621	 * Compute seconds into the year.
622	 */
623	tmp = (u_long)(MULBY24((day-1)) + hour);	/* hours */
624	tmp = MULBY60(tmp) + (u_long)minute;		/* minutes */
625	tmp = MULBY60(tmp) + (u_long)second;		/* seconds */
626
627	/*
628	 * Now the fun begins.  We demand that the received time code
629	 * be within CLOCK_WAYTOOBIG of the receive timestamp, but
630	 * there is uncertainty about the year the timestamp is in.
631	 * Use the current year start for the first check, this should
632	 * work most of the time.
633	 */
634	date_ui = tmp + yearstart;
635#define CLOCK_WAYTOOBIG 1000 /* revived from ancient sources */
636	if (date_ui < (rtime->l_ui + CLOCK_WAYTOOBIG)
637	    && date_ui > (rtime->l_ui - CLOCK_WAYTOOBIG))
638	    goto codeokay;	/* looks good */
639
640	/*
641	 * Trouble.  Next check is to see if the year rolled over and, if
642	 * so, try again with the new year's start.
643	 */
644	date_ui = calyearstart(rtime->l_ui, NULL);
645	if (date_ui != yearstart) {
646		yearstart = date_ui;
647		date_ui += tmp;
648		(void) printf("time %u, code %u, difference %d\n",
649			      date_ui, rtime->l_ui, (long)date_ui-(long)rtime->l_ui);
650		if (date_ui < (rtime->l_ui + CLOCK_WAYTOOBIG)
651		    && date_ui > (rtime->l_ui - CLOCK_WAYTOOBIG))
652		    goto codeokay;	/* okay this time */
653	}
654
655	ts.l_uf = 0;
656	ts.l_ui = yearstart;
657	printf("yearstart %s\n", prettydate(&ts));
658	printf("received %s\n", prettydate(rtime));
659	ts.l_ui = date_ui;
660	printf("date_ui %s\n", prettydate(&ts));
661
662	/*
663	 * Here we know the year start matches the current system
664	 * time.  One remaining possibility is that the time code
665	 * is in the year previous to that of the system time.  This
666	 * is only worth checking if the receive timestamp is less
667	 * than CLOCK_WAYTOOBIG seconds into the new year.
668	 */
669	if ((rtime->l_ui - yearstart) < CLOCK_WAYTOOBIG) {
670		date_ui = tmp;
671		date_ui += calyearstart(yearstart - CLOCK_WAYTOOBIG,
672					NULL);
673		if ((rtime->l_ui - date_ui) < CLOCK_WAYTOOBIG)
674		    goto codeokay;
675	}
676
677	/*
678	 * One last possibility is that the time stamp is in the year
679	 * following the year the system is in.  Try this one before
680	 * giving up.
681	 */
682	date_ui = tmp;
683	date_ui += calyearstart(yearstart + (400 * SECSPERDAY),
684				NULL);
685	if ((date_ui - rtime->l_ui) >= CLOCK_WAYTOOBIG) {
686		printf("Date hopelessly off\n");
687		return;		/* hopeless, let it sync to other peers */
688	}
689
690    codeokay:
691	reftime = date_ui;
692	/*
693	 * We've now got the integral seconds part of the time code (we hope).
694	 * The fractional part comes from the table.  We next compute
695	 * the offsets for each character.
696	 */
697	for (i = 0; i < NCHUCHARS; i++) {
698		register u_long tmp2;
699
700		off[i].l_ui = date_ui;
701		off[i].l_uf = chutable[i];
702		tmp = chuc->codetimes[i].tv_sec + JAN_1970;
703		TVUTOTSF(chuc->codetimes[i].tv_usec, tmp2);
704		M_SUB(off[i].l_ui, off[i].l_uf, tmp, tmp2);
705	}
706
707	/*
708	 * Here is a *big* problem.  What one would normally
709	 * do here on a machine with lots of clock bits (say
710	 * a Vax or the gizmo board) is pick the most positive
711	 * offset and the estimate, since this is the one that
712	 * is most likely suffered the smallest interrupt delay.
713	 * The trouble is that the low order clock bit on an IBM
714	 * RT, which is the machine I had in mind when doing this,
715	 * ticks at just under the millisecond mark.  This isn't
716	 * precise enough.  What we can do to improve this is to
717	 * average all 10 samples and rely on the second level
718	 * filtering to pick the least delayed estimate.  Trouble
719	 * is, this means we have to divide a 64 bit fixed point
720	 * number by 10, a procedure which really sucks.  Oh, well.
721	 * First compute the sum.
722	 */
723	date_ui = 0;
724	tmp = 0;
725	for (i = 0; i < NCHUCHARS; i++)
726	    M_ADD(date_ui, tmp, off[i].l_ui, off[i].l_uf);
727	if (M_ISNEG(date_ui, tmp))
728	    isneg = 1;
729	else
730	    isneg = 0;
731
732	/*
733	 * Here is a multiply-by-0.1 optimization that should apply
734	 * just about everywhere.  If the magnitude of the sum
735	 * is less than 9 we don't have to worry about overflow
736	 * out of a 64 bit product, even after rounding.
737	 */
738	if (date_ui < 9 || date_ui > 0xfffffff7) {
739		register u_long prod_ui;
740		register u_long prod_uf;
741
742		prod_ui = prod_uf = 0;
743		/*
744		 * This code knows the low order bit in 0.1 is zero
745		 */
746		for (i = 1; i < NZPOBITS; i++) {
747			M_LSHIFT(date_ui, tmp);
748			if (ZEROPTONE & (1<<i))
749			    M_ADD(prod_ui, prod_uf, date_ui, tmp);
750		}
751
752		/*
753		 * Done, round it correctly.  Prod_ui contains the
754		 * fraction.
755		 */
756		if (prod_uf & 0x80000000)
757		    prod_ui++;
758		if (isneg)
759		    date_ui = 0xffffffff;
760		else
761		    date_ui = 0;
762		tmp = prod_ui;
763		/*
764		 * date_ui is integral part, tmp is fraction.
765		 */
766	} else {
767		register u_long prod_ovr;
768		register u_long prod_ui;
769		register u_long prod_uf;
770		register u_long highbits;
771
772		prod_ovr = prod_ui = prod_uf = 0;
773		if (isneg)
774		    highbits = 0xffffffff;	/* sign extend */
775		else
776		    highbits = 0;
777		/*
778		 * This code knows the low order bit in 0.1 is zero
779		 */
780		for (i = 1; i < NZPOBITS; i++) {
781			M_LSHIFT3(highbits, date_ui, tmp);
782			if (ZEROPTONE & (1<<i))
783			    M_ADD3(prod_ovr, prod_uf, prod_ui,
784				   highbits, date_ui, tmp);
785		}
786
787		if (prod_uf & 0x80000000)
788		    M_ADDUF(prod_ovr, prod_ui, (u_long)1);
789		date_ui = prod_ovr;
790		tmp = prod_ui;
791	}
792
793	/*
794	 * At this point we have the mean offset, with the integral
795	 * part in date_ui and the fractional part in tmp.  Store
796	 * it in the structure.
797	 */
798	/*
799	 * Add in fudge factor.
800	 */
801	M_ADD(date_ui, tmp, offset_fudge.l_ui, offset_fudge.l_uf);
802
803	/*
804	 * Find the minimun and maximum offset
805	 */
806	imin = imax = 0;
807	for (i = 1; i < NCHUCHARS; i++) {
808		if (L_ISGEQ(&off[i], &off[imax])) {
809			imax = i;
810		} else if (L_ISGEQ(&off[imin], &off[i])) {
811			imin = i;
812		}
813	}
814
815	L_ADD(&off[imin], &offset_fudge);
816	if (imin != imax)
817	    L_ADD(&off[imax], &offset_fudge);
818	(void) printf("mean %s, min %s, max %s\n",
819		      mfptoa(date_ui, tmp, 8), lfptoa(&off[imin], 8),
820		      lfptoa(&off[imax], 8));
821}
822