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