! Fixed Point Number 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
! "fixedpt_", while members private to this file begin with "fixedpt__".

! Fixed-point math allows for decimal arithmetic using a fixed number of decimal
! places.  This library uses the standard 16-bit word value to define 8 bits
! of non-decimal and 8 bits of decimal.


! Notes about arithmetic with fixed-points:
!    The following operators act exactly the same in normal and fixed modes:
!       + - < > <= >= ==
!    The sign operator (negation -) works the same, too.
!    ++ and -- now perform an increment of 1/256 on the values.
!    Modulo is meaningless.
!
! The only funky operations you need this for are multiplication and
! division.

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


System_file;



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

Message "Adding Fixed-point math library";

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

Include "math";

!-------------------------------------------------------------------------------
! Locale specific data
Ifndef FIXEDPT_NEGATIVE_SIGN;
Constant FIXEDPT_NEGATIVE_SIGN = "-";
Endif;

Ifndef FIXEDPT_DECIMAL_POINT;
Constant FIXEDPT_DECIMAL_POINT = ".";
Endif;

Ifndef FIXEDPT_DISPLAYED_DECIMALS;
! Number of decimal places to display.  This cannot be < 1.
Constant FIXEDPT_DISPLAYED_DECIMALS = 4;
Endif;
!-------------------------------------------------------------------------------


! Used for interior operations
Array fixedpt__long1 -> 4;
Array fixedpt__long2 -> 4;
Array fixedpt__long3 -> 4;


!-------------------------------------------------------------------------------
! Conversion routines
!-------------------------------------------------------------------------------

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Displays a fixed-point number as a signed decimal value
! Use by:
!      print (fixedpt_signed)val;
[ fixedpt_signed
   x      ! parameter to display
   ;   ! locals

   if (x < 0)
   {
       print (string)FIXEDPT_NEGATIVE_SIGN;

       ! remove the sign from the value
       x = -x;
   }
   fixedpt_unsigned( x );
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Displays a fixed-point number as an unsigned decimal value
! Use by:
!      print (fixedpt_signed)val;
[ fixedpt_unsigned
   x     ! parameter to display
   modulo count;   ! locals

   ! display the upper part of the number.
   modulo = fixedpt__getUnsignedIntegerPart( x );
   print (math_unsigned)modulo;

   ! display the decimal value.
   print (string)FIXEDPT_DECIMAL_POINT;
   x = fixedpt__getDecimalPart( x );

   ! quick & simple check for easy case.
   if (x == 0)
   {
       print "0";
       return;
   }

   ! long division
   for (count = 0 : count < FIXEDPT_DISPLAYED_DECIMALS : ++count)
   {
       ! next decimal place
       x = x * 10;

       ! find the character to display
       modulo = x / $100;
!print "[x = ",x,", m = ",modulo,"]";
       if (modulo > 10 || modulo < 0)
       {
           modulo = 0;
       }
       print (char)('0' + modulo);
       x = x - ($100 * modulo);

       if (x == 0)
       {
           ! don't display any more digits
           break;
       }
   }
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Convert the fixed-point number to an integer, ignoring the decimal points.
! This performs a fixed -> word conversion, as opposed to a fixed -> fixed
! conversion.
[ fixedpt_unsigned_floor_word
   x;  ! parameter

   return fixedpt__getUnsignedIntegerPart( x );
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Convert the fixed-point number to an integer, ignoring the decimal points.
! This performs a fixed -> word conversion, as opposed to a fixed -> fixed
! conversion.
[ fixedpt_signed_floor_word
   x;  ! parameter

   return fixedpt__getSignedIntegerPart( x );
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Convert the fixed-point number to an integer, rounding up to the next integer
! if the decimal part is > 0.
! This performs a fixed -> word conversion, as opposed to a fixed -> fixed
! conversion.
[ fixedpt_unsigned_ceiling_word
   x  ! parameter
   i; ! local

   i = fixedpt__getUnsignedIntegerPart( x );
   if (fixedpt__getDecimalPart( x ) > 0) ++i;

   return i;
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Convert the fixed-point number to an integer, rounding up to the next integer
! if the decimal part is > 0.
! This performs a fixed -> word conversion, as opposed to a fixed -> fixed
! conversion.
[ fixedpt_signed_ceiling_word
   x  ! parameter
   i; ! local

   i = fixedpt__getSignedIntegerPart( x );
   if (fixedpt__getDecimalPart( x ) > 0) ++i;

   return i;
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Convert the fixed-point number to an integer, rounding to the nearest integer.
! This performs a fixed -> word conversion, as opposed to a fixed -> fixed
! conversion.
[ fixedpt_unsigned_round_word
   x  ! parameter
   i; ! local

   i = fixedpt__getUnsignedIntegerPart( x );
   if (fixedpt__getDecimalPart( x ) >= $80) ++i;

   return i;
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Convert the fixed-point number to an integer, rounding to the nearest integer.
! This performs a fixed -> word conversion, as opposed to a fixed -> fixed
! conversion.
[ fixedpt_signed_round_word
   x  ! parameter
   i; ! local

   i = fixedpt__getSignedIntegerPart( x );
   if (fixedpt__getDecimalPart( x ) >= $80) ++i;

   return i;
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Convert a word integer into an unsigned fixed number.
! If the word value is >= 256, it is truncated to 255.
[ fixedpt_word_to_unsigned_fixed
   x;  ! parameter

   if (x > $ff) x = $ff;

   return math_unsigned_shift( x, 8 );
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Convert a word integer into a signed fixed number.
! If the word value is >= 128, it is truncated to 127, and if it is
! < -128, it is truncated to -128.
[ fixedpt_word_to_signed_fixed
   x;  ! parameter

   if (x >= $80)
   {
       ! return biggest signed fixed
       return $7fff;
   }
   else
   if (x <= -128)
   {
       ! return smallest signed fixed
       return $8000;
   }

   if (x < 0)
   {
       return math_signed_shift( x, 8 ) - $ff;
   }

   return math_signed_shift( x, 8 );
];



!-------------------------------------------------------------------------------
! Arithmetic
!-------------------------------------------------------------------------------

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Multiply two signed values together
!  - due to possible overflow, this uses the longint routines to perform the
!    operation.
[ fixedpt_signed_mul
   x y     ! parameters
   r s;     ! locals

   ! operation = (x * y) >> 8;

   ! long1 = x
   math__set_signed_word_to_long( x, fixedpt__long1 );
   ! long2 = y
   math__set_signed_word_to_long( y, fixedpt__long2 );

   ! long3 = long1 * long2
   LongMul( fixedpt__long3, fixedpt__long1, fixedpt__long2 );

   ! Due to decimal place movement, the resulting value is in the top 3 bytes
   ! of long3.
   if (fixedpt__long3->0 < 0 && fixedpt__long3 ~= $ff)
   {
       ! overflow - return smallest signed fixed
       return $8000;
   }
   else
   if (fixedpt__long3->0 > 0)
   {
       ! overflow - return biggest signed fixed
       return $7fff;
   }

   ! no overflow
   r = fixedpt__long3->1 & $ff;
   s = fixedpt__long3->2 & $ff;
   return math_signed_shift( r, 8 ) + s;
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Multiply two unsigned values together
!  - due to possible overflow, this uses the longint routines to perform the
!    operation.
[ fixedpt_unsigned_mul
   x y     ! parameters
   r s;     ! locals

   ! operation = (x * y) >> 8;

   ! long1 = x
   math__set_unsigned_word_to_long( x, fixedpt__long1 );
   ! long2 = y
   math__set_unsigned_word_to_long( y, fixedpt__long2 );

   ! long3 = long1 * long2
   LongMul( fixedpt__long3, fixedpt__long1, fixedpt__long2 );

   if (fixedpt__long3->0 ~= 0)
   {
       ! overflow - return biggest unsigned fixed
       return $ffff;
   }

   ! no overflow
   r = fixedpt__long3->1 & $ff;
   s = fixedpt__long3->2 & $ff;
   return math_unsigned_shift( r, 8 ) + s;
];




!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Divide two signed values together (returns x / y)
!  - due to possible overflow, this uses the longint routines to perform the
!    operation.
[ fixedpt_signed_div
   x y     ! parameters
   r s;     ! locals

   ! operation = (x / y) << 8;
   ! we don't want to loose precision, so perform the shifting first.

   ! long1 = x << 8
   LongSet( fixedpt__long1, 0,
       math_unsigned_shift( x, 8 ) & $ff, (x & $ff), 0 );
   if (x < 0)
   {
       fixedpt__long1->0 = $ff;
   }
   ! long2 = y
   math__set_signed_word_to_long( y, fixedpt__long2 );

   ! long3 = long1 / long2
   LongSignDiv( fixedpt__long3, fixedpt__long1, fixedpt__long2 );

   r = fixedpt__long3->2 & $ff;
   s = fixedpt__long3->3 & $ff;
   return math_signed_shift( r, 8 ) + s;
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Divide two unsigned values together (returns x / y)
!  - due to possible overflow, this uses the longint routines to perform the
!    operation.
[ fixedpt_unsigned_div
   x y     ! parameters
   r s;     ! locals

   ! operation = (x / y) << 8;
   ! we don't want to loose precision, so perform the shifting first.

   ! long1 = x << 8
   LongSet( fixedpt__long1, 0,
       math_unsigned_shift( x, -8 ) & $ff, (x & $ff), 0 );
   ! long2 = y
   math__set_unsigned_word_to_long( y, fixedpt__long2 );

   ! long3 = long1 / long2, long1 = modulo
   LongUnsignDivMod( fixedpt__long3, fixedpt__long1,
       fixedpt__long1, fixedpt__long2 );

   r = fixedpt__long3->2 & $ff;
   s = fixedpt__long3->3 & $ff;
   return math_unsigned_shift( r, 8 ) + s;
];



!-------------------------------------------------------------------------------
! Private members
!-------------------------------------------------------------------------------



!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Returns the upper 8 bits of the given word.
[ fixedpt__getUnsignedIntegerPart
   x;  ! parameters

   return math_unsigned_shift( x, -8 ) & $ff;
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Returns the upper 8 bits of the given word.
[ fixedpt__getSignedIntegerPart
   x;  ! parameters

   ! no masking to keep the sign bits
   return math_signed_shift( x, -8 );
];


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
! Returns the lower 8 bits of the given word.
[ fixedpt__getDecimalPart
   x;  ! parameters

   return x & $ff;
];