! Fixed Point Number mathematical routines.
!   Version 1.0 (19-Sep-2001)
!
! by Matt Albrecht - groboclown@users.sourceforge.net
!
! (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;
];



