]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/ntp/clockstuff/chutest.c
Upgrade NTP to 4.2.8p4.
[FreeBSD/releng/10.2.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 #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
49 struct 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
59 char const *progname;
60
61 int dofilter = 0;       /* set to 1 when we should run filter algorithm */
62 int showtimes = 0;      /* set to 1 when we should show char arrival times */
63 int doprocess = 0;      /* set to 1 when we do processing analogous to driver */
64 #ifdef CHULDISC
65 int usechuldisc = 0;    /* set to 1 when CHU line discipline should be used */
66 #endif
67 #ifdef STREAM
68 int usechuldisc = 0;    /* set to 1 when CHU line discipline should be used */
69 #endif
70
71 struct timeval lasttv;
72 struct chucode chudata;
73
74 void    error(char *fmt, char *s1, char *s2);
75 void    init_chu(void);
76 int     openterm(char *dev);
77 int     process_raw(int s);
78 int     process_ldisc(int s);
79 void    raw_filter(unsigned int c, struct timeval *tv);
80 void    chufilter(struct chucode *chuc, l_fp *rtime);
81
82
83 /*
84  * main - parse arguments and handle options
85  */
86 int
87 main(
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  */
170 int
171 openterm(
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  */
238 int
239 process_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  */
277 void
278 raw_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  */
343 int
344 process_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  */
399 void
400 error(
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
477 static 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  */
494 static l_fp propagation_delay;
495 static l_fp fudgefactor;
496 static 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  */
503 static u_long yearstart;
504
505 /*
506  * Imported from the timer module
507  */
508 extern u_long current_time;
509 extern struct event timerqueue[];
510
511 /*
512  * init_chu - initialize internal chu driver data
513  */
514 void
515 init_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
532 void
533 chufilter(
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 }