! math.h - Extended Mathematical routines.
!   Version 1.0 (19-Sep-2001)
!
! by Matt Albrecht - [email protected]
!
! (If you need to edit this file, note that indentations are 4 spaces
! and tabs are not used.)
!
! This has been donated to the Public Domain.  Use, abuse, and don't blame me.
!
! To prevent dirtying the global namespace, all members of this file begin with
! "math_", while members private to this file begin with "math__".

! Due to unsigned stuff, requires Z-Machine version 5 or older.

! Note that there is a bug in Nitfol v0.5 - if you unsign divide or mod $8000
! by anything less than $8000, it returns the wrong answer (the negative
! of the correct answer).


System_file;



! C-like header!
Ifndef MATH__INCLUDED;
Constant MATH__INCLUDED;

Message "Adding Extended Math library";

! Depends upon the longint.h library
Ifndef LONGINT__INCLUDED;
Constant LONGINT__INCLUDED;
Include "longint";
Endif;



! Used for interior operations
Array math__long1 -> 4;
Array math__long2 -> 4;
Array math__long3 -> 4;


!-------------------------------------------------------------------------------
! Print routines
!-------------------------------------------------------------------------------

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Print the 16-bit word as unsigned.
[ math_unsigned
   x;  ! parameter

   if (x < 0)
   {
       ! use long longint library to print the sucker
       math__set_unsigned_word_to_long( x, math__long3 );
       print (longunsign)math__long3;
   }
   else
   {
       print x;
   }
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Displays a word as a hex string.
! (Stolen from N.G.'s Inform help pages)
!
! Argument 1: the word to convert to hex.
[ math_hex
   x   ! parameter
   y;  ! local

       y = (x & $ff00) / $100;
       x = x & $ff;
       print (math_hdigit) y/$10, (math_hdigit) y,
         (math_hdigit) x/$10, (math_hdigit) x;
];

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Displays 4 bits as a hex digit.
! (Stolen from N.G.'s Inform help pages)
!
! Argument 1: the 4 bits to convert to hex.
[ math_hdigit
   x; ! parameter

       x = x & $000f;
       if (x < 10) print x; else print (char)'a'+x-10;
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Displays a word as a binary string.
! (Stolen from N.G.'s Inform help pages)
!
! Argument 1: the word to convert to hex.
[ math_binary
   x   ! parameter
   y z;    ! locals
! $8000 is considered negative, so we can't use y > 0
!print "[binary of ",x,":";
   if (x < 0)
   {
!print "... first is negative: ";
       print "1";
   }
   else
   {
       print "0";
   }
   for (y = $4000 : y > 0 : y = y/2)
   {
       ! perform a binary AND
       @and x y -> z;
!print "... ",(hex)x," & ",(hex)y," = ",(hex)z,": ";
       print (z/y);
   }
!print "]^";
];




!-------------------------------------------------------------------------------
! Unsigned Math routines
!-------------------------------------------------------------------------------

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Shift the first parameter by the given number of bits in the second parameter,
! shifting left if it is positive, or right if it is negative.  Note that
! since this is unsigned, shifting right will not do sign extension.
[ math_unsigned_shift
   x count ! parameters
   r;

   @log_shift x count -> r;
   return r;
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Unsigned division of 2 16-bit words (returns x / y).
!
[ math_unsigned_div
   x y     ! parameters
   ret;    ! locals

   math__unsigned_div_mod( x, y );

   ret = math__get_unsigned_long_to_word( math__long1 );
!print (math_unsigned)x," / ",(math_unsigned)y," = ",(math_unsigned)ret,"^";
   return ret;
];



!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Unsigned modulo of 2 16-bit words (returns x % y).
!
[ math_unsigned_mod
   x y     ! parameters
   ret;    ! locals

   math__unsigned_div_mod( x, y );

   ret = math__get_unsigned_long_to_word( math__long2 );
!print (math_unsigned)x," % ",(math_unsigned)y," = ",(math_unsigned)ret,"^";
   return ret;
];


!-------------------------------------------------------------------------------
! Signed Math routines
!-------------------------------------------------------------------------------

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Shift the first parameter by the given number of bits in the second parameter,
! shifting left if it is positive, or right if it is negative.  Note that
! since this is signed, shifting right will perform a sign extension.
[ math_signed_shift
   x count ! parameters
   r;

   @art_shift x count -> r;
   return r;
];


!---------------------------------------------------
[ math_square
       x;
   return x * x;
];


!---------------------------------------------------
[ math_squareRoot
       c       ! parameter
       prev diff last orig; ! locals
    ! Based on Newton-Raphson method: f(x) = x*x - c, f'(x) = 2*x.
    ! So, a[n+1] = a[n] - (f(a[n])/f'(a[n]))
    ! Therefore,
    !     a[n+1] = a[n] - (a[n]*a[n] - c)/(2*a[n])
    ! In order to avoid overflows of precision:
    !     a[n+1] = a[n] - (a[n]*a[n]/(2*a[n])) - (c/(2*a[n]))
    !            = a[n] - (a[n]/2) - (c/2/a[n])


    ! the square root of c is <= c/2 by definition of primes.
    orig = c;
    c = c / 2;
    prev = c;
    if (prev < 0) return -1; ! bad input value

    if (prev == 0) return 0;
    for (::) {
        last = prev;
        diff = prev/2 - c/prev;
        if (diff == 0) break;

        prev = prev - diff;

        ! There are situations where we can toggle between two values,
        ! never reaching a good result.
        if (last == prev+1) break;
    };
    return prev;
];


!-------------------------------------------------------------------------------
! Private Math routines
!-------------------------------------------------------------------------------

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!
[ math__set_unsigned_word_to_long
   x longArray; ! parameters

   LongSet( longArray,
       0, 0,
       math_unsigned_shift( x, -8) & $ff, x & $ff );
!print "[",x," to unsigned long ",(longunsign)longArray,"]^";
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Puts a word value into the given longint array
[ math__set_signed_word_to_long
   x longArray    ! parameters
   top;           ! locals

   top = 0;
   if (x < 0)
   {
       top = $ff;
   }
   LongSet( longArray,
       top, top,
       math_unsigned_shift( x, -8) & $ff, x & $ff );
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Puts a word value into the given longint array
[ math__get_unsigned_long_to_word
   longArray      ! parameters
   x y ret;       ! locals

   x = longArray->2;
   y = longArray->3;
   ret = math_unsigned_shift( x, 8 ) + y;
!print "[converted unsigned long ",(longunsign)longArray," to ",
!(math_unsigned)ret,". ([0] = ",longArray->0,", [1] = ",longArray->1,", [2] = ",
!x,", [3] = ",y,")]^";
   return ret;
];




!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Sets the result of unsigned (x / y) in math__long1
! and (x % y) in math__long2
!
[ math__unsigned_div_mod
   x y     ! parameters
   t;

   ! special cases for optimization
       ! include ~= $8000 for covering Nitfol 0.5 bug
   if (x > 0 && y > 0 && x ~= $8000 && y ~= $8000)
   {
!        print "Unsigned division!^";
       math__set_unsigned_word_to_long( x / y, math__long1 );
       math__set_unsigned_word_to_long( x % y, math__long2 );
   }
! Uncomment these if you feel the performance drop from the if-statements
! is worth the reduced computation for these few special cases.
!    else if (x == y)
!    {
!        math__set_unsigned_word_to_long( 1, math__long1 );
!        math__set_unsigned_word_to_long( 0, math__long2 );
!    }
!    else if (x == 0)
!    {
!        math__set_unsigned_word_to_long( 0, math__long1 );
!        math__set_unsigned_word_to_long( 0, math__long2 );
!    }
!    else if (y == 1)
!    {
!        math__set_unsigned_word_to_long( x, math__long1 );
!        math__set_unsigned_word_to_long( 0, math__long2 );
!    }
   else
   {
       ! long1 = x
       math__set_unsigned_word_to_long( x, math__long1 );
       ! long2 = y
       math__set_unsigned_word_to_long( y, math__long2 );
!print " - ",(math_unsigned)x," (",(longunsign)math__long1,") / ",
!(math_unsigned)y," (",(longunsign)math__long2,"): ";

       ! long1 = long1 / long2, long2 = long1 % long2
       LongUnsignDivMod( math__long1, math__long2,
           math__long1, math__long2 );
   }

   ! There is a bug in Nitfol v0.5 - see header for details
!    if (x == $8000 && y ~= $8000 && y > 0)
!    {
!        ! need to turn the quotient and mod positive
!        math__set_unsigned_word_to_long(
!            -math__get_unsigned_long_to_word( math__long1 ), math__long1 );
!        math__set_unsigned_word_to_long(
!            -math__get_unsigned_long_to_word( math__long2 ), math__long2 );
!    }

!print "quotient = ",(longunsign)math__long1,", remainder = ",
!    (longunsign)math__long2,".^";
];

Endif;