by Stephen R. Ferg

It's not hard to find a date arithmetic routine when you need one. They are available in journal articles, in books, and on electronic bulletin boards.But if you are building a business application, you have a remarkably slim chance of finding a routine that you can actually use.

The reason is that most date arithmetic routines use time-concepts drawn
from astronomy, not from the business calendar. For these routines, a day is
the time it takes the earth to make one complete rotation on its axis; a year
is the time it takes the earth to make one complete revolution around the sun.
For such routines, the number of days in a year is not built into the concept
*year*, but is something to be discovered through scientific
investigation. As we all know, astronomers have discovered that a year is equal
to approximately 365 and 1/4 days.

Astronomy-based routines typically rely on hard-coded "magic numbers": a
year is 365.2422 days, a month is 30.6001 days, and so on. They also involve
repeated multiplication and division operations designed to force truncation of
numbers that are stored internally in a particular format: you might find, for
example, a number being multiplied by 12 and then immediately being divided by
12. (The great-grand-daddy of these routines is a FORTRAN algorithm published
in 1968 by two astronomers at the U.S. Naval Observatory. See "A Machine
Algorithm for Processing Calendar Dates", by Henry F. Fliegel and Thomas C. Van
Flandern, *Communications of the ACM,* Volume 11, Number 10, October
1968.)

These routines have two major drawbacks. First, because they depend on
numbers being stored internally in a format determined by a particular language
as implemented by a particular compiler for a particular hardware platform,
they are often not portable to other languages or platforms. Second, because
their magic numbers and algorithms are based on the behavior of the platform
they were designed to run on, not on problem-domain concepts such as
*year* and *leap year*, their algorithms are virtually
incomprehensible, and hence unmodifiable.

In order to overcome these problems, I developed a date routine called TRUEDATE that is designed to be both portable and modifiable.

TRUEDATE is portable because its algorithms are independent of any particular language, compiler, or hardware platform. It requires only support for an INTEGER data type of at least seven significant digits, and normal arithmetic operations on integers: addition, subtraction, multiplication, integer division, and division remainder (modulo).

TRUEDATE is comprehensible, and hence modifiable, because its algorithms are based on application-domain date concepts. For TRUEDATE:

- An
*ordinary year*is a year that contains 365 days; in an ordinary year February contains 28 days. - A
*leap year*is a year that contains 366 days; in a leap year February contains 29 days.

The algorithm that TRUEDATE uses for determining which years are leap years and which are not is clearly represented in its source code. TRUEDATE's leapyear scheme is:

Every year is an ordinary year

- EXCEPT THAT every year evenly divisible by 4 is a leap year
- EXCEPT THAT every year evenly divisible by 100 is not a leap year
- EXCEPT THAT every year evenly divisible by 400 is a leap year.

- EXCEPT THAT every year evenly divisible by 100 is not a leap year

According to this scheme:

- 1983 is not a leap year
- 1984 is a leap year
- 1900 is not a leap year
- 2000 is a leap year

The source code for TRUEDATE is shown in Listing 1. It is expressed in a Pascal-like pseudo-code; the pseudo-code can be transformed into compilable Turbo Pascal by using a case-sensitive text editor to change "then" to "THEN BEGIN", "elseif" to "END ELSE IF", "else" to "END ELSE BEGIN", and "endif" to "END;". Translation into C, COBOL, Fortran, REXX, or other languages should not be difficult. Some obvious opportunities for optimization have been ignored in order to leave the pattern of the algorithm clear.

TRUEDATE is intended for business applications, not historical ones. It knows nothing about historical changes in the calendar such as the 10-day jump that occurred when Britain moved from the Julian to the Gregorian calendar in the 18th century. As far as TRUEDATE is concerned, the calendar has followed the same pattern, unchanged, since January 1, 0001.

Central to all date arithmetic routines is the concept of an "absolute" or "true Julian" date: a date expressed as the number of days from some day in the distant past. The apparently arbitrary base date for the Fliegel/Van Flandern algorithm --January 1, 4713 BCE -- reflects its basis in implementation, rather than application-domain, considerations. TRUEDATE's base date is January 1, 0001 (i.e. day 1 of month 1 of year 1). For this base, the absolute date for January 1, 1992 is 727198.

At the heart of TRUEDATE are two routines that convert a calendar date to, and from, an absolute date. With such routines available, doing date arithmetic is easy. For example, to determine an invoice date that is 60 days in the future, one simply translates today's calendar date into an absolute date, adds 60 to the absolute date, and translates the resulting absolute date back into a calendar date. When absolute dates are available, day-of-week determination is similarly easy. Dividing an absolute date by 7 will produce a remainder in the range 0..6 that will tell you the number of the day of the week.

Note that both of TRUEDATE's conversion routines calculate numbers for the leapyear flag and the Julian date (the number of days elapsed since the beginning of the year), and so can make those numbers available to a program that calls them.

When you implement TRUEDATE in a particular language, it is important to
remember that TRUEDATE uses *integer* division. Integer division is an
operation that always returns a whole number. In normal division, 7 divided by
2 produces the result 3.5; in integer division the result is 3. In many
declarative languages, integer division can be performed simply by declaring
all variables to be of type INTEGER. In other languages, however, you will need
to be sure that all division operations are being done using the language's
integer-division operator. In REXX, for example, the integer division operator
is the percent sign (as opposed to the slash, the normal division operatior).
In Turbo Pascal, the integer division operator is "div".

Note that these date routines do the minimum amount of data validation necessary to insure that they can execute successfully. The Cal2Absdate routine does not check that its input date is completely valid. It can and will accept and successfuly process, for example, a date for the 50th day of the month, or for the 29th of February in a non-leap year. A simple way to check to see if a calendar date is valid is to convert it to an absolute date, and then convert it back again. If you end up with the same calendar date that you started with, then the date is valid.

These are internal routines. They use variables from the surrounding environment, rather than declaring their own variables and getting input and output via parameter passing.

- The Abs2CalDate routine uses AbsDate as its input. That is, it expects AbsDate to have been set before it is called.
- The Cal2AbsDate uses CalDay, CalMonth, and CalYear as its inputs.

UNIT Truedate; {================================================================} {THIS SOURCE CODE REQUIRES PREPROCESSING BEFORE THE TURBO PASCAL } {COMPILER CAN COMPILE IT. } {================================================================} {If porting program to COBOL, replace LongInt with PIC S9(9) COMP } INTERFACE TYPE MonthNameType = string; TYPE TDoWNameType = string; TYPE TErrMsgType = string; {parameter variables} VAR AbsDate : LongInt; VAR CalYear : LongInt; VAR CalMonth : LongInt; VAR MonthName : MonthNameType; VAR CalDay : LongInt; VAR JulianDate : LongInt; VAR DayOfWeekNum : LongInt; VAR LeapYearFlag : LongInt; {N.B. flag is also used as a number} VAR TDoWname : TDoWNameType; VAR TErrMsg : TErrMsgType; Function DoWnum (AbsDate:longint) :longint; Function DoWname_US (DayOfWeekNum:longint):TDoWNameType; Function DoWname_FR (DayOfWeekNum:longint):TDoWNameType; Function DoWname_SP (DayOfWeekNum:longint):TDoWNameType; Function DoWname_GR (DayOfWeekNum:longint):TDoWNameType; Function DoWname_DK (DayOfWeekNum:longint):TDoWNameType; Function MonthName_US (CalMonth:longint) :MonthNameType; Function MonthName_FR (CalMonth:longint) :MonthNameType; Function MonthName_SP (CalMonth:longint) :MonthNameType; Function Monthname_GR (CalMonth:longint) :MonthNameType; Function Monthname_DK (CalMonth:longint) :MonthNameType; Procedure CalDate2AbsDate ; Procedure AbsDate2CalDate ; IMPLEMENTATION CONST MinAbsDate : LongInt = 1 ; { JANUARY 1, 0001 } CONST DaysInOrdinaryYr : LongInt = 365 ; CONST DaysIn004YrGroup : LongInt = 1461; {(DaysInOrdinaryYr * 4) + 1} CONST DaysIn100YrGroup : LongInt = 36524; {(DaysIn004YrGroup * 25) - 1} CONST DaysIn400YrGroup : LongInt = 146097; {(DaysIn100YrGroup * 4) + 1} CONST MaxAbsDate : LongInt = 3652059;{ DECEMBER 31, 9999 } CONST JANdays : LongInt = 31; CONST FEBshort : LongInt = 28; VAR FEBdays : LongInt; CONST MARdays : LongInt = 31; CONST APRdays : LongInt = 30; CONST MAYdays : LongInt = 31; CONST JUNdays : LongInt = 30; CONST JULdays : LongInt = 31; CONST AUGdays : LongInt = 31; CONST SEPdays : LongInt = 30; CONST OCTdays : LongInt = 31; CONST NOVdays : LongInt = 30; CONST DECdays : LongInt = 31; VAR Num400YrGroups , Num400YrModYrs : LongInt; VAR Num100YrGroups , Num100YrModYrs : LongInt; VAR Num004YrGroups , Num004YrModYrs : LongInt; VAR Num001YrGroups , Num001YrModYrs : LongInt; {} {==============================================================} Function Is_LeapYr(CalYear:longint):longint; {==============================================================} VAR Mod400 : LongInt; VAR Mod100 : LongInt; VAR Mod004 : LongInt; VAR Mod001 : LongInt; begin {proc/func} Mod400 := CalYear mod 400; if Mod400 = 0 then Is_LeapYr := 1; else Mod100 := Mod400 mod 100; if Mod100 = 0 then Is_LeapYr := 0; else Mod004 := Mod100 mod 4 ; if Mod004 = 0 then Is_LeapYr := 1; else Is_LeapYr := 0; endif endif endif end; {proc/func} {===============================================================} Function DoWnum (AbsDate:longint):longint; {Calculate the day of the week from the absolute date} {===============================================================} begin {proc/func} {add 1, so that DoWnum is in range 1..7 rather than 0..6} {DoWnum 1 is Sunday, DoWnum 2 is Monday ... DoWnum 7 is Saturday} DoWnum := (AbsDate mod 7) + 1; end; {proc/func} {===============================================================} Procedure BumpMonth(Monthdays:longint); {===============================================================} begin {proc/func} CalMonth := CalMonth + 1; CalDay := CalDay - Monthdays; end; {proc/func} {} {==============================================================} Function MonthName_US (CalMonth:longint):MonthNameType; {===============================================================} begin {proc/func} if CalMonth = 1 then MonthName_US := 'January' elseif CalMonth = 2 then MonthName_US := 'February' elseif CalMonth = 3 then MonthName_US := 'March' elseif CalMonth = 4 then MonthName_US := 'April' elseif CalMonth = 5 then MonthName_US := 'May' elseif CalMonth = 6 then MonthName_US := 'June' elseif CalMonth = 7 then MonthName_US := 'July' elseif CalMonth = 8 then MonthName_US := 'August' elseif CalMonth = 9 then MonthName_US := 'September' elseif CalMonth = 10 then MonthName_US := 'October' elseif CalMonth = 11 then MonthName_US := 'November' elseif CalMonth = 12 then MonthName_US := 'December' endif end; {proc/func} {===============================================================} Function DoWname_US (DayOfWeekNum:longint):TDoWNameType; {===============================================================} begin {proc/func} if DayOfWeekNum = 1 then DoWname_US := 'Sunday' elseif DayOfWeekNum = 2 then DoWname_US := 'Monday' elseif DayOfWeekNum = 3 then DoWname_US := 'Tuesday' elseif DayOfWeekNum = 4 then DoWname_US := 'Wednesday' elseif DayOfWeekNum = 5 then DoWname_US := 'Thursday' elseif DayOfWeekNum = 6 then DoWname_US := 'Friday' elseif DayOfWeekNum = 7 then DoWname_US := 'Saturday' endif end; {proc/func} {} {==============================================================} Function MonthName_FR (CalMonth:longint):MonthNameType; {FRENCH LANGUAGE: name of day of month } {===============================================================} begin {proc/func} if CalMonth = 1 then MonthName_FR := 'janvier' elseif CalMonth = 2 then MonthName_FR := 'fevrier' elseif CalMonth = 3 then MonthName_FR := 'mars' elseif CalMonth = 4 then MonthName_FR := 'avril' elseif CalMonth = 5 then MonthName_FR := 'mai' elseif CalMonth = 6 then MonthName_FR := 'juin' elseif CalMonth = 7 then MonthName_FR := 'juillet' elseif CalMonth = 8 then MonthName_FR := 'aout' elseif CalMonth = 9 then MonthName_FR := 'septembre' elseif CalMonth = 10 then MonthName_FR := 'octobre' elseif CalMonth = 11 then MonthName_FR := 'novembre' elseif CalMonth = 12 then MonthName_FR := 'decembre' endif end; {proc/func} {===============================================================} Function DoWname_FR (DayOfWeekNum:longint):TDoWNameType; {FRENCH LANGUAGE: name of day of week } {===============================================================} begin {proc/func} if DayOfWeekNum = 1 then DoWname_FR := 'dimanche' elseif DayOfWeekNum = 2 then DoWname_FR := 'lundi' elseif DayOfWeekNum = 3 then DoWname_FR := 'mardi' elseif DayOfWeekNum = 4 then DoWname_FR := 'mercredi' elseif DayOfWeekNum = 5 then DoWname_FR := 'jeudi' elseif DayOfWeekNum = 6 then DoWname_FR := 'vendredi' elseif DayOfWeekNum = 7 then DoWname_FR := 'samedi' endif end; {proc/func} {} {==============================================================} Function Monthname_GR (CalMonth:longint):MonthNameType; {GERMAN LANGUAGE: name of day of month } {===============================================================} begin {proc/func} if CalMonth = 1 then Monthname_GR := 'Januar' elseif CalMonth = 2 then Monthname_GR := 'Februar' elseif CalMonth = 3 then Monthname_GR := 'M„rz' elseif CalMonth = 4 then Monthname_GR := 'April' elseif CalMonth = 5 then Monthname_GR := 'Mai' elseif CalMonth = 6 then Monthname_GR := 'Juni' elseif CalMonth = 7 then Monthname_GR := 'Juli' elseif CalMonth = 8 then Monthname_GR := 'August' elseif CalMonth = 9 then Monthname_GR := 'September' elseif CalMonth = 10 then Monthname_GR := 'Oktober' elseif CalMonth = 11 then Monthname_GR := 'November' elseif CalMonth = 12 then Monthname_GR := 'Dezember' endif end; {proc/func} {===============================================================} Function DoWname_GR (DayOfWeekNum:longint):TDoWNameType; {GERMAN LANGUAGE: name of day of week } {===============================================================} begin {proc/func} if DayOfWeekNum = 1 then DoWname_GR := 'Sonntag' elseif DayOfWeekNum = 2 then DoWname_GR := 'Montag' elseif DayOfWeekNum = 3 then DoWname_GR := 'Dienstag' elseif DayOfWeekNum = 4 then DoWname_GR := 'Mittwoch' elseif DayOfWeekNum = 5 then DoWname_GR := 'Donnerstag' elseif DayOfWeekNum = 6 then DoWname_GR := 'Freitag' elseif DayOfWeekNum = 7 then DoWname_GR := 'Samstag' endif end; {proc/func} {} {==============================================================} Function Monthname_DK (CalMonth:longint):MonthNameType; {DANISH LANGUAGE: name of day of month } {===============================================================} {Note that in Danish, the names of the weekdays and the months are NOT capitalized.} begin {proc/func} if calmonth = 1 then monthname_dk := 'januar' elseif calmonth = 2 then monthname_dk := 'februar' elseif calmonth = 3 then monthname_dk := 'marts' elseif calmonth = 4 then monthname_dk := 'april' elseif calmonth = 5 then monthname_dk := 'maj' elseif calmonth = 6 then monthname_dk := 'juni' elseif calmonth = 7 then monthname_dk := 'juli' elseif calmonth = 8 then monthname_dk := 'august' elseif calmonth = 9 then monthname_dk := 'september' elseif calmonth = 10 then monthname_dk := 'oktober' elseif calmonth = 11 then monthname_dk := 'november' elseif calmonth = 12 then monthname_dk := 'december' endif end; {proc/func} {===============================================================} Function DoWname_DK (DayOfWeekNum:longint):TDoWNameType; {DANISH LANGUAGE: name of day of week } {===============================================================} {Note that in Danish, the names of the weekdays and the months are NOT capitalized.} {In Danish, the names for Saturday and Sunday are spelled with a special Danish character O_slash. This character looks like 'o' with '/' overlaid on it. In Denmark, codepage 850 is widely used, and in this codepage, the lowercase O_Slash character has ASCII code 155.} CONST Danish_lower_o_slash : string[1] = #155; begin {proc/func} if DayOfWeekNum = 1 then downame_dk := 's'+ Danish_lower_o_slash+ 'ndag' elseif DayOfWeekNum = 2 then downame_dk := 'mandag' elseif DayOfWeekNum = 3 then downame_dk := 'tirsdag' elseif DayOfWeekNum = 4 then downame_dk := 'onsdag' elseif DayOfWeekNum = 5 then downame_dk := 'torsdag' elseif DayOfWeekNum = 6 then downame_dk := 'fredag' elseif DayOfWeekNum = 7 then downame_dk := 'l'+Danish_lower_o_slash+'rdag' endif end; {proc/func} {==============================================================} Function MonthName_SP (CalMonth:longint):MonthNameType; {SPANISH LANGUAGE: name of day of month } {===============================================================} begin {proc/func} if CalMonth = 1 then MonthName_SP := 'enero' elseif CalMonth = 2 then MonthName_SP := 'febrero' elseif CalMonth = 3 then MonthName_SP := 'marzo' elseif CalMonth = 4 then MonthName_SP := 'abril' elseif CalMonth = 5 then MonthName_SP := 'mayo' elseif CalMonth = 6 then MonthName_SP := 'junio' elseif CalMonth = 7 then MonthName_SP := 'julio' elseif CalMonth = 8 then MonthName_SP := 'agosto' elseif CalMonth = 9 then MonthName_SP := 'septiembre' elseif CalMonth = 10 then MonthName_SP := 'octubre' elseif CalMonth = 11 then MonthName_SP := 'noviembre' elseif CalMonth = 12 then MonthName_SP := 'diciembre' endif end; {proc/func} {===============================================================} Function DoWname_SP (DayOfWeekNum:longint):TDoWNameType; {SPANISH LANGUAGE: name of day of week } {===============================================================} begin {proc/func} if DayOfWeekNum = 1 then DoWname_SP := 'domingo' elseif DayOfWeekNum = 2 then DoWname_SP := 'lunes' elseif DayOfWeekNum = 3 then DoWname_SP := 'martes' elseif DayOfWeekNum = 4 then DoWname_SP := 'mi‚rcoles' elseif DayOfWeekNum = 5 then DoWname_SP := 'jueves' elseif DayOfWeekNum = 6 then DoWname_SP := 'viernes' elseif DayOfWeekNum = 7 then DoWname_SP := 'sbado' endif end; {proc/func} {} {==============================================================} Procedure AbsDate2CalDate; { Convert an absolute date into a calendar date } {===============================================================} begin {proc/func} TErrMsg := ''; Num400YrGroups := AbsDate div DaysIn400YrGroup; Num400YrModYrs := AbsDate mod DaysIn400YrGroup; if Num400YrModYrs = 0 then {absolute date fits exactly into a 400-year group} JulianDate := 366; CalYear := (400 * Num400YrGroups) ; {end assignment statement} else Num100YrGroups := Num400YrModYrs div DaysIn100YrGroup; Num100YrModYrs := Num400YrModYrs mod DaysIn100YrGroup; if Num100YrModYrs = 0 then {absolute date fits exactly into a 100-year group} JulianDate := 365; CalYear := (400 * Num400YrGroups) + (100 * Num100YrGroups) ; {end assignment statement} else Num004YrGroups := Num100YrModYrs div DaysIn004YrGroup ; Num004YrModYrs := Num100YrModYrs mod DaysIn004YrGroup ; if Num004YrModYrs = 0 then {absolute date fits exactly into a 4-year group} JulianDate := 366; CalYear := (400 * Num400YrGroups) + (100 * Num100YrGroups) + ( 4 * Num004YrGroups) ; {end assignment statement} else Num001YrGroups := Num004YrModYrs div DaysInOrdinaryYr ; Num001YrModYrs := Num004YrModYrs mod DaysInOrdinaryYr ; if Num001YrModYrs = 0 then {absolute date fits exactly into a 1-year group} JulianDate:= 365; CalYear := (400 * Num400YrGroups) + (100 * Num100YrGroups) + ( 4 * Num004YrGroups) + ( 1 * Num001YrGroups) ; {end assignment statement} else {absolute date doesn't fit exactly into any group} JulianDate:= Num001YrModYrs; {Add 1 to convert a year count into an ordinal year} {E.g. Absolute day 5 is Jan. 5 of year 1, not year 0} CalYear := (400 * Num400YrGroups) + (100 * Num100YrGroups) + ( 4 * Num004YrGroups) + ( 1 * Num001YrGroups) + 1 ; {end assignment statement} endif endif endif endif {====================================================} { determine number of days in February in this year } {====================================================} LeapYearFlag := Is_LeapYr(CalYear); FEBdays := FEBshort + LeapYearFlag; {Initialize month number to month #1 } CalMonth := 1; {Initialize day-of-month to Julian date} CalDay := JulianDate; {Subtract days of elapsed months from day-of-month to get final day-of-month. At the same time, increment month-number for each elapsed month.} if CalDay > JANdays then BumpMonth(JANdays) ; if CalDay > FEBdays then BumpMonth(FEBdays) ; if CalDay > MARdays then BumpMonth(MARdays) ; if CalDay > APRdays then BumpMonth(APRdays) ; if CalDay > MAYdays then BumpMonth(MAYdays) ; if CalDay > JUNdays then BumpMonth(JUNdays) ; if CalDay > JULdays then BumpMonth(JULdays) ; if CalDay > AUGdays then BumpMonth(AUGdays) ; if CalDay > SEPdays then BumpMonth(SEPdays) ; if CalDay > OCTdays then BumpMonth(OCTdays) ; if CalDay > NOVdays then BumpMonth(NOVdays) ; endif endif endif endif endif endif endif endif endif endif endif end; {proc/func} {

} {==============================================================} Procedure CalDate2AbsDate; { Convert a calendar date into an absolute date } {===============================================================} begin; {proc/func} TErrMsg := ''; if CalMonth < 1 then TErrMsg := 'Month number is less than 01'; exit; elseif CalMonth > 12 then TErrMsg := 'Month number greater than 12'; exit; endif {=============================================================== Subtract 1 to convert an ordinal year number into a count of years elapsed since "the start of time". Examples: During year ONE, ZERO years have actually elapsed from day one. During year TWO, ONE year has actually elapsed from day one. ===============================================================} Num400YrGroups := (CalYear - 1) div 400; Num400YrModYrs := (CalYear - 1) mod 400; Num100YrGroups := Num400YrModYrs div 100; Num100YrModYrs := Num400YrModYrs mod 100; Num004YrGroups := Num100YrModYrs div 4; Num004YrModYrs := Num100YrModYrs mod 4; Num001YrGroups := Num004YrModYrs div 1; {Initialize absolute date to number of days elapsed in previous years} AbsDate := ( Num400YrGroups * DaysIn400YrGroup ) + ( Num100YrGroups * DaysIn100YrGroup ) + ( Num004YrGroups * DaysIn004YrGroup ) + ( Num001YrGroups * DaysInOrdinaryYr ) ; {determine number of days in February in this year} LeapYearFlag := Is_LeapYr(CalYear); FEBdays := FEBshort + LeapYearFlag; {Initialize Julian date to days elapsed in this month} JulianDate := CalDay; {add days of previous months in this year to get final Julian date} if CalMonth > 1 then JulianDate := JulianDate + JANdays endif if CalMonth > 2 then JulianDate := JulianDate + FEBdays endif if CalMonth > 3 then JulianDate := JulianDate + MARdays endif if CalMonth > 4 then JulianDate := JulianDate + APRdays endif if CalMonth > 5 then JulianDate := JulianDate + MAYdays endif if CalMonth > 6 then JulianDate := JulianDate + JUNdays endif if CalMonth > 7 then JulianDate := JulianDate + JULdays endif if CalMonth > 8 then JulianDate := JulianDate + AUGdays endif if CalMonth > 9 then JulianDate := JulianDate + SEPdays endif if CalMonth > 10 then JulianDate := JulianDate + OCTdays endif if CalMonth > 11 then JulianDate := JulianDate + NOVdays endif {add Julian date to days of previous years to get final absolute date} AbsDate := AbsDate + JulianDate; end; {proc/func} End. {unit}

LICENSE INFORMATION

I, Stephen R. Ferg, affirm that I myself developed the truedate date arithmetic algorithm, and the code and text in which it is here described and expressed.

To the extent possible under law,
Stephen R. Ferg
has waived all copyright and related or neighboring rights to
Truedate date arithmetic algorithm.
This work is published from
the United States of America.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.