; FP Math TODO:
; 1. Addition and subtraction
; 2. Multiplication and division
; 3. Miscellaneous math operations:
;   - Roots/powers
;   - Logarithms
;   - Trigonometry
;   - etc.
; 4. Conversion to and from IEEE 754 floats/doubles



;; itofp [Decimal Floating Point]
;;  Converts a 32-bit unsigned integer into a floating-point
;;  binary coded decimal format and stores it to the buffer at HL.
;; Inputs:
;;  ACIX: Unsigned integer
;;  HL: Pointer to 9-byte destination buffer
;; Notes:
;;  The result is in the following format:
;;
;;  - 1 byte flags:
;;    - 7: Sign bit
;;    - 4-6: Reserved for kernel
;;    - 0-3: Available for program use
;;  - 1 byte signed exponent, normalized to 0x80 instead of 0
;;  - 7 byte mantissa, BCD encoded with two digits per byte
itofp:
    ; Modified version of itostr
    push hl
    push ix
    push bc
    push af
    push de
    push iy
        ; Move HL to IY for convenience
        push hl \ pop iy
        ; Clear the flags
        ld (iy), 0
        ld b, 0     ; Our digit counter
.loop:
        ; Check if ACIX is zero
        push bc
            ; If a is not zero
            or a
            jp nz, .notZero
            ; If c is not zero
            ld b, a
            ld a, c
            or a
            ld a, b
            jp nz, .notZero
            ; If ixh is not zero
            ld b, a
            ld a, ixh
            or a
            ld a, b
            jp nz, .notZero
            ; If ixl is not zero
            ld b, a
            ld a, ixl
            or a
            ld a, b
.notZero:
        pop bc
        ; If ACIX is down to zero, exit loop
        jr z, _
        ; Divide ACIX by 10
        push iy
            ld iyh, b
            ld de, 10
            call div32By16
            ld b, iyh
        pop iy
        push hl     ; Push the remainder to the stack
        inc b       ; Increment our digit counter
        jr .loop
_:
        ; Write a zero if ACIX is zero
        ld a, b
        or a
        jr nz, .writeExp
        ld (iy), 0
        inc iy
.writeExp:
        ld a, b
        add a, 0x7f
        ld (iy+1), a
.writeMantissa:
        ld a, b
        or a
        jr z, _     ; If our digit counter is zero, exit loop
        pop de      ; Pop our digit from the stack
        dec b       ; Decrement our digit counter
        ld a, b
        or a
        jr z, .lastDigit    ; If we have an odd number of digits, don't pop
        pop hl      ; Pop our digit from the stack
        dec b       ; Decrement our digit counter
        jr .writeDigits
.lastDigit:
        ld hl, 0     ; Load a zero instead if there are no more digits
.writeDigits:
        ; Pack the two digits
        ld a, e
        rrca
        rrca
        rrca
        rrca
        or l
        ; Write the digits
        ld (iy+2), a
        inc iy
        jr .writeMantissa
_:
    pop iy
    pop de
    pop af
    pop bc
    pop ix
    pop hl
    ret

;; strtofp [Decimal Floating Point]
;;  Converts an ASCII-encoded signed decimal into a floating-point
;;  binary coded decimal format and stores it to the buffer at HL.
;; Inputs:
;;  IX: Pointer to string
;;  HL: Pointer to 9-byte destination buffer
;; Outputs:
;;  Z: Set on success, reset on error
;; Notes:
;;  See `itofp` for the result format.
;;
;;  Only the first 14 significant digits are converted. The rest are truncated
;;  but still used for exponent calculation.
;;
;;  In case of error, the destination buffer's contents are undefined.
;;
;;  The destination buffer must be zeroed before calling strtofp.
strtofp:
    push ix
    push hl
    push bc
    push de
    push iy
    push af
        ; Store HL into IY for convenience
        push hl \ pop iy
        ; Check for a negative sign at the beginning
        ld a, (ix)
        cp '-'
        jr nz, _
        ld (hl), 0x80
        inc ix
_:
        inc hl
        inc hl
        ld b, 14      ; Remaining digits counter
        ld de, 0x0080 ; Parser flags and place value counter
.loop:
        ; Check if we are at the end of the buffer
        xor a
        cp b
        jr z, .loopEnd
        ; Check if we've reached a null terminator
        ld a, (ix)
        or a
        jr z, .loopEnd
        cp '.'
        jr nz, _
        ; Make sure we haven't already seen a decimal point
        bit 0, d
        jr nz, .error
        set 0, d
        jr .loopCont
_:
        ; Error on non-numeric characters
        cp '9' + 1
        jr nc, .error
        sub '0'
        jr c, .error
        jr nz, _
        ; Handle zero
        bit 1, d
        jr z, .loopCont     ; Skip leading zero
_:
        set 1, d    ; Finished with all leading zeroes
        rld         ; Shift the digit into the current BCD byte
        bit 0, b    ; Only increment HL every other digit
        jr z, .loopIter
        inc hl
.loopIter:
        ; Adjust place value for digits before decimal
        ld a, d
        cp 0x02
        jr nz, _
        inc e
_:
        inc ix
        djnz .loop
        jr .loopEnd
.loopCont:
        ; Adjust place value for leading zeroes after decimal
        ld a, d
        cp 0x01
        jr nz, _
        dec e
_:
        inc ix
        jr .loop
.loopEnd:
        ; Shift the last digit if there were an odd number
        bit 0, b
        jr z, _
        xor a
        rld
_:
        ; Check if there may be more place values that need counted
        ld a, b
        or a
        jr nz, .remainderLoopEnd
        bit 0, d
        jr nz, .remainderLoopEnd
.remainderLoop:
        ; Count remaining place values
        ld a, (ix)
        or a
        jr z, .remainderLoopEnd
        cp '.'
        jr z, .remainderLoopEnd
        cp '9' + 1
        jr nc, .error
        cp '0'
        jr c, .error
        inc e
        inc ix
        jr .remainderLoop
.remainderLoopEnd:
        ; Correct for one's place value
        ld a, 0x80
        cp e
        jr nc, _
        dec e
_:
        ; Save the place value counter into the exponent byte
        ld (iy + 1), e
        pop af
        cp a    ; Set Z flag
        jr .exit
.error:
        pop af
        ; Save A
        ld b, a
        or 1    ; Reset Z flag
        ; Restore A
        ld a, b
.exit:
    pop iy
    pop de
    pop bc
    pop hl
    pop ix
    ret

;; fptostr [Decimal Floating Point]
;;  Converts a floating point number into an ASCII-encoded decimal string.
;; Inputs:
;;  IX: Pointer to floating point number
;;  HL: Pointer to destination buffer
;;  A: Flags / digits
;; Notes:
;;  The destination buffer must be at least 20 characters in length.
;;
;;  The most significant nibble of A should be set to flags. `FP_STR_*`
;;  macros in kernel.inc can be ORed and stored in A for convenience.
;;
;;  The least significant nibble of A is the number of digits to display
;;  after the decimal: 0-9 for that many digits, or 0xF for as many are
;;  non-zero.
;;
;;  Examples:
;;
;;  All nonzero decimals in scientific notation, with decimal as ',':
;;
;;      ld a, FP_STR_INV_PUNC | FP_DISP_SCIENTIFIC | 0xF
;;
;;  5 fixed digits grouped with ',':
;;
;;      ld a, FP_GROUP_DIGITS | 5
; TODO:
;  * Rounding last digit - buggy, currently abandoned
;  * Never show exponent if significand is 0 - not started
.macro fptostrIter1(reg)
        ; Output the first digit in the byte pointed to by reg
        ld a, (reg)
        rrca
        rrca
        rrca
        rrca
        and 0x0F
        add a, '0'
        ld (hl), a
        inc hl
.endmacro
.macro fptostrIter2(reg)
        ; Output the second digit in the byte pointed to by reg
        ld a, (reg)
        and 0x0F
        add a, '0'
        ld (hl), a
        inc hl
.endmacro
.macro fptostrIter1Round(reg)
        ; Like fptostrIter1 but it rounds the last digit
        ld a, (reg)
        rrca
        rrca
        rrca
        rrca
        and 0x0F
        dec b
        jr nz, .fptostrIter1RoundSkip
        ld c, a
        ld a, (reg)
        and 0x0F
        cp 5
        jr c, .fptostrIter1RoundSkip
        ld a, c
        inc a
.fptostrIter1RoundSkip:
        inc b
        add a, '0'
        ld (hl), a
        inc hl
.endmacro
.macro fptostrIter2Round(reg)
        ; Like fptostrIter2 but it rounds the last digit
        ld a, (reg)
        and 0x0F
        dec b
        jr nz, .fptostrIter2RoundSkip
        ld c, a
        inc reg
        ld a, (reg)
        dec reg
        rrca
        rrca
        rrca
        rrca
        and 0x0F
        cp 5
        jr c, .fptostrIter2RoundSkip
        ld a, c
        inc a
.fptostrIter2RoundSkip:
        inc b
        add a, '0'
        ld (hl), a
        inc hl
.endmacro
.macro fptostrI18N(normal, alt)
        ; Output a different character depending on the bit 7 flag
        ex (sp), hl
        bit 7, h
        ex (sp), hl
        jr nz, $+6
        ld (hl), normal
        jr $+4
        ld (hl), alt
        inc hl
.endmacro
.macro fptostrInsertPVSep
        ; Output a place value separator depending on the bit 6 flag
        pop af \ push af
        bit 6, a
        jr z, ++_
        push de
            ld d, b
            dec d
            ld e, 3
            call div8By8
            or a
            jr nz, _
            or d
            jr z, _
        pop de
        fptostrI18N(',', '.')
        jr ++_
_:
        pop de
_:
.endmacro

fptostr:
    push hl
    push bc
    push de
    push af
        ; Check which mode to use
        bit 4, a
        jp nz, _
        ld a, (ix + 1)
        cp 0x8a
        jp nc, _
        cp 0x7d
        jp c, _
        ; Normal mode
        pop af \ push af
        jr ++_
_:
        ; Scientific mode
        pop af \ push af
        set 4, a
        res 5, a
_:
        push af
            ; Negative sign
            ld a, (ix)
            and 0x80
            jr z, _
            ld (hl), '-'
            inc hl
_:
            ; Calculate number of digits to display
            pop af \ push af
            bit 4, a
            jr z, _
            ; Scientific notation
            ld a, 1
            jr .beginIPart
_:
            ; Normal mode
            ld a, (ix + 1)
            cp 0x80
            jr c, _
            sub 0x7F
            jr .beginIPart
_:
            ; Negative exponent
            ld (hl), '0'
            inc hl
            push ix \ pop de
            inc de \ inc de
            ld c, 10    ; Should be 9, but that causes it to skip the last digit
            xor a
            jp .doneWithIPart
.beginIPart:
            ld b, a     ; Number of pre-decimal digits
            ld a, 10
            sub b
            ld c, a     ; (Maximum) number of post-decimal digits
            push ix \ pop de
            inc de \ inc de
.iPartLoop:
            fptostrIter1(de)
            fptostrInsertPVSep
            dec b
            ld a, 1     ; Mark that this byte isn't complete yet
            jr z, .doneWithIPart
            fptostrIter2(de)
            fptostrInsertPVSep
            inc de
            xor a       ; Mark that this byte is complete
            djnz .iPartLoop
.doneWithIPart:
            ; Check if we are using fixed point or not
            push af
                inc sp \ inc sp
                pop af \ push af
                dec sp \ dec sp
                and 0x0F
                jr z, ++_
                cp 10
                jr nc, +++_
                cp c
                jr nc, _
                ld c, a
_:
                xor a
                or c
                jr z, _
            pop af
            jr .skipFloating
_:
            pop af
            jr .end
_:
            pop af
            ; Calculate how many fractional digits there are
            push af
            push hl
            push de
                ; Escape early if there isn't room for fractional digits
                xor a
                or c
                jr z, _
                push ix \ pop hl
                ld de, 6    ; Ignore last 2 bytes for display purposes
                add hl, de
.countFractionalDigitsLoop:
                ld a, (hl)
                and 0x0F
                jr nz, ++_
                dec c
                jr z, _
                ld a, (hl)
                and 0xF0
                jr nz, ++_
                dec hl
                dec c
                jr nz, .countFractionalDigitsLoop
_:
            pop de
            pop hl
            pop af
            jp .end
_:
            pop de
            pop hl
            pop af
.skipFloating:
            fptostrI18N('.', ',')
            ; Check if we need to add leading zeroes
            push af
                ; Make sure we aren't in scientific notation mode
                inc sp \ inc sp
                pop af \ push af
                dec sp \ dec sp
                bit 4, a
                jr nz, _
                ; Check the exponent
                ld a, (ix + 1)
                sub 0x80
                jr nc, _
                neg
                dec a
                jr z, _
                ld b, a
.leadingZeroLoop:
                ld (hl), '0'
                inc hl
                dec c
                djnz .leadingZeroLoop
_:
            pop af
            ld b, c
            dec a
            jr z, .fPartLoopHalf
.fPartLoop:
            fptostrIter1(de)
            ; fptostrIter1Round(de)
            dec b
            jr z, .end
.fPartLoopHalf:
            fptostrIter2(de)
            ; fptostrIter2Round(de)
            inc de
            djnz .fPartLoop
.end:
        ; Check if need to include exponent or not
        pop af
        bit 4, a
        jr z, .terminate
        ld (hl), 'E'
        inc hl
        ; Exponent
        ld a, (ix + 1)
        sub 0x80
        jr nc, _
        ld (hl), '-'
        inc hl
        neg
_:
        ld d, a
        ld e, 10
        ld b, 0     ; Exponent digit counter
.calcExponentLoop:
        call div8By8
        add a, '0'
        push af
        inc b
        ld a, d
        or a
        jr nz, .calcExponentLoop
.writeExponentLoop:
        pop af
        ld (hl), a
        inc hl
        djnz .writeExponentLoop
.terminate:
        ; Null terminator
        ld (hl), 0
    pop af
    pop de
    pop bc
    pop hl
    ret
.undefine fptostrIter1
.undefine fptostrIter2
.undefine fptostrIter1Round
.undefine fptostrIter2Round
.undefine fptostrI18N
.undefine fptostrInsertPVSep

;; fpLdConst [Decimal Floating Point]
;;  Loads a floating point constant specified by A into HL.
;; Inputs:
;;  A: Constant to load, use `FP_*` macros from kernel.inc
;;  HL: Pointer to destination buffer
fpLdConst:
    push af
    push bc
    push de
    push hl
        ex de, hl
        ld hl, .lookupTable
        ld bc, 9
.macro fpLdConstIter
        add hl, bc
        dec a
        jr z, _
.endmacro
        or a
        jr z, _
        fpLdConstIter
        fpLdConstIter
        fpLdConstIter
        fpLdConstIter
        fpLdConstIter
        fpLdConstIter
        fpLdConstIter
        fpLdConstIter
        fpLdConstIter
.undefine fpLdConstIter
        ; None matched - return unchanged
        jr .end
_:
        ldir
.end:
    pop hl
    pop de
    pop bc
    pop af
    ret

.lookupTable:
    ; 0
    .db 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    ; 1
    .db 0x00, 0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
    ; pi
    .db 0x00, 0x80, 0x31, 0x41, 0x59, 0x26, 0x53, 0x58, 0x98
    ; pi/2
    .db 0x00, 0x80, 0x15, 0x70, 0x79, 0x63, 0x26, 0x79, 0x49
    ; pi/4
    .db 0x00, 0x7F, 0x78, 0x53, 0x98, 0x16, 0x33, 0x97, 0x45
    ; pi/180
    .db 0x00, 0x7E, 0x17, 0x45, 0x32, 0x92, 0x51, 0x99, 0x43
    ; 180/pi
    .db 0x00, 0x81, 0x57, 0x29, 0x57, 0x79, 0x51, 0x30, 0x82
    ; e
    .db 0x00, 0x80, 0x27, 0x18, 0x28, 0x18, 0x28, 0x45, 0x90
    ; log(e)
    .db 0x00, 0x7F, 0x43, 0x42, 0x94, 0x48, 0x19, 0x03, 0x25
    ; ln(10)
    .db 0x00, 0x80, 0x23, 0x02, 0x58, 0x50, 0x92, 0x99, 0x40

; (Internal) Normalize the floating point number at HL.
fpNormalize:
    push af
    push bc
    push de
        inc hl \ inc hl
        push hl
            xor a
            ld c, a
.macro fpNormalizeCountLeading
            ; Find the first nonzero byte
            or (hl)
            jr nz, _
            inc hl
            inc c
.endmacro
            fpNormalizeCountLeading
            fpNormalizeCountLeading
            fpNormalizeCountLeading
            fpNormalizeCountLeading
            fpNormalizeCountLeading
            fpNormalizeCountLeading
            fpNormalizeCountLeading
.undefine fpNormalizeCountLeading
        ; It's all zero, so clean it up and exit
        pop hl
        dec hl
        ld (hl), 0x80
        dec hl
        ld (hl), 0
        jr .end
_:
            ; HL points to the first nonzero byte
            ; Copy from HL to the beginning of the significand
            pop de \ push de
            push bc
                ld a, 7
                sub c
                ld c, a
                ld b, 0
                ldir
            pop bc
            ; Zero out the trailing bytes left over from copying
            ld b, c
            sla c
            jr z, _
            dec hl
            xor a
.zeroLoop:
            ld (hl), a
            dec hl
            djnz .zeroLoop
_:
            ; Shift out the last leading zero if necessary
            pop hl \ push hl
            ld a, (hl)
            and 0xF0
            jr nz, _
            inc c
            ld de, 6
            add hl, de
.macro fpNormalizeShiftLeft
            rld
            dec hl
.endmacro
            fpNormalizeShiftLeft
            fpNormalizeShiftLeft
            fpNormalizeShiftLeft
            fpNormalizeShiftLeft
            fpNormalizeShiftLeft
            fpNormalizeShiftLeft
            fpNormalizeShiftLeft
.undefine fpNormalizeShiftLeft
_:
        ; Fix exponent
        pop hl
        dec hl
        ld a, (hl)
        sub c
        ld (hl), a
        dec hl
.end:
    pop de
    pop bc
    pop af
    ret

;; fpAbs [Decimal Floating Point]
;;  Takes the absolute value of the floating point number at IX.
;; Inputs:
;;  IX: Pointer to operand
;; Outputs:
;;  IX: Pointer to result
fpAbs:
    res 7, (ix)
    ret

;; fpNeg [Decimal Floating Point]
;;  Negates the floating point number at IX.
;; Inputs:
;;  IX: Pointer to operand
;; Outputs:
;;  IX: Pointer to result
fpNeg:
    push af
        ; Make sure operand is not zero
        ld a, (ix + 2)
        or a
        jr z, .end
        ; Invert the sign bit
        ld a, (ix)
        xor 0x80
        ld (ix), a
.end:
    pop af
    ret

;; fpSub [Decimal Floating Point]
;;  Subtracts the two floating point numbers.
;; Inputs:
;;  IX: Pointer to operand 1 (minuend)
;;  IY: Pointer to operand 2 (subtrahend)
;;  HL: Pointer to destination buffer
fpSub:
    push af
        ; Make sure operand is not zero
        ld a, (iy + 2)
        or a
        jr z, .end
        ; Invert the sign bit
        ld a, (iy)
        xor 0x80
        ld (iy), a
.end:
    pop af
    ; Fall through to fpAdd

;; fpAdd [Decimal Floating Point]
;;  Adds the two floating point numbers.
;; Inputs:
;;  IX, IY: Pointers to operands
;;  HL: Pointer to destination buffer
;; Notes:
;;  May destroy one or both operands.
fpAdd:
    push ix
    push iy
    push hl
    push af
    push bc
        ; Make sure that IY has the smaller exponent
        ld a, (ix + 1)
        cp (iy + 1)
        jr nc, _
        ; IY is larger, so swap with IX
        push ix \ push iy \ pop ix \ pop iy
_:
        ; Set the result's exponent to IX's for now
        inc hl
        ld a, (ix + 1)
        ld (hl), a
        dec hl
        ; Align smaller operand's radix point with the larger one's
        sub (iy + 1)
        jr z, .skipShifting
        cp 2
        jr c, .shiftLast
        push hl
        push de
            push af
                push iy \ pop hl
                push iy \ pop de
                ; Point DE to the end of the buffer
                ld bc, 8
                add hl, bc
                ex de, hl
                ; Point HL to the byte that will be moved to the end
                srl a       ; Number of bytes to shift right
                cp 6
                jr c, _
                ld a, 6
_:
                neg
                add a, 8
                ld c, a
                add hl, bc
                dec bc
                lddr
            ; Replace shifted digits with leading zeroes
            pop af
            ld b, 0
            ld c, a
            srl c
            add hl, bc
            ld b, c
            ld c, 0
.zeroLoop:
            ld (hl), c
            dec hl
            djnz .zeroLoop
        pop de
        pop hl
.shiftLast:
        ; Shift last digit if necessary
        and 0x01
        jr z, _
        push hl
            ; Swap with HL to make shifting easier
            push iy \ pop hl
            inc hl
            xor a
.macro fpAddShiftRight
            inc hl
            rrd
.endmacro
            fpAddShiftRight
            fpAddShiftRight
            fpAddShiftRight
            fpAddShiftRight
            fpAddShiftRight
            fpAddShiftRight
            fpAddShiftRight
.undefine fpAddShiftRight
        pop hl
.skipShifting:
        ; Invert negative operands using 10's complement
.macro fpAddTensComplIter(r)
        ld a, 0x99
        sub (r)
        ld (r), a
.endmacro
        bit 7, (ix)
        jr z, _     ; Not negative
        fpAddTensComplIter(ix + 2)
        fpAddTensComplIter(ix + 3)
        fpAddTensComplIter(ix + 4)
        fpAddTensComplIter(ix + 5)
        fpAddTensComplIter(ix + 6)
        fpAddTensComplIter(ix + 7)
        fpAddTensComplIter(ix + 8)
_:
        bit 7, (iy)
        jr z, _     ; Not negative
        fpAddTensComplIter(iy + 2)
        fpAddTensComplIter(iy + 3)
        fpAddTensComplIter(iy + 4)
        fpAddTensComplIter(iy + 5)
        fpAddTensComplIter(iy + 6)
        fpAddTensComplIter(iy + 7)
        fpAddTensComplIter(iy + 8)
.undefine fpAddTensComplIter
_:
        ; Start at the end of the buffer
        ld bc, 8
        add hl, bc
        ; Clear the carry flag if operands have the same sign, else set it
        ld a, (ix)
        and 0x80
        ld b, a
        ld a, (iy)
        and 0x80
        cp b
        scf
        ; jr nz, _  ; TODO: find a way to only add 1 when it will overflow
        ccf
_:
.macro fpAddSumIter(x, y)
        ld a, (x)
        adc a, (y)
        daa     ; Adjust the addition for BCD
        ld (hl), a
        dec hl
.endmacro
        fpAddSumIter(ix + 8, iy + 8)
        fpAddSumIter(ix + 7, iy + 7)
        fpAddSumIter(ix + 6, iy + 6)
        fpAddSumIter(ix + 5, iy + 5)
        fpAddSumIter(ix + 4, iy + 4)
        fpAddSumIter(ix + 3, iy + 3)
        fpAddSumIter(ix + 2, iy + 2)
.undefine fpAddSumIter
        ; Handle carry
        jr nc, _
        ld a, 1
        push hl
.macro fpAddCarryIter
            inc hl
            rrd
.endmacro
            fpAddCarryIter
            fpAddCarryIter
            fpAddCarryIter
            fpAddCarryIter
            fpAddCarryIter
            fpAddCarryIter
            fpAddCarryIter
.undefine fpAddCarryIter
        pop hl
        inc (hl)
_:
    pop bc
    pop af
    pop hl
    pop iy
    pop ix
    ret

;; fpMulPow10 [Decimal Floating Point]
;;  Multiplies the floating point number in IX by 10^E.
;; Inputs:
;;  IX: Pointer to operand
;;  E: Signed exponent (i.e. 2 -> 100, -3 -> 0.001)
;; Outputs:
;;  IX: Pointer to result
;; Notes:
;;  Does not check for overflow.
fpMulPow10:
    push af
        ; Make sure operand is not zero
        ld a, (ix + 2)
        or a
        jr z, .end
        ; Add the exponents
        ld a, (ix + 1)
        add e
        ld (ix + 1), a
.end:
    pop af
    ret

;; fpAnd [Decimal Floating Point]
;;  Performs a logical AND on the two floating point numbers.
;; Inputs:
;;  IX, IY: Pointers to operands
;; Outputs:
;;  Z: Result was false
;;  NZ: Result was true
fpAnd:
    push bc
        ; Save A in B to preserve flags
        ld b, a
        ; Check if first operand is 0
        ld a, (ix + 2)
        or a
        jr z, .end
        ; Check if second operand is 0
        ld a, (iy + 2)
        or a
.end:
        ; Restore A
        ld a, b
    pop bc
    ret

;; fpOr [Decimal Floating Point]
;;  Performs a logical OR on the two floating point numbers.
;; Inputs:
;;  IX, IY: Pointers to operands
;; Outputs:
;;  Z: Result was false
;;  NZ: Result was true
fpOr:
    push bc
        ; Save A in B to preserve flags
        ld b, a
        xor a
        ; Check if first operand is 0
        or (ix + 2)
        jr nz, .end
        ; Check if second operand is 0
        or (iy + 2)
.end:
        ; Restore A
        ld a, b
    pop bc
    ret

;; fpXor [Decimal Floating Point]
;;  Performs a logical XOR on the two floating point numbers.
;; Inputs:
;;  IX, IY: Pointers to operands
;; Outputs:
;;  Z: Result was false
;;  NZ: Result was true
fpXor:
    push bc
        ; Save A in B to preserve flags
        ld b, a
        ; Check if first operand is 0
        ld a, (ix + 2)
        or a
        jr nz, _
        ; Check if second operand is 0
        ld a, (iy + 2)
        or a
        jr .end
_:
        ; Check if second operand is nonzero
        ld a, (iy + 2)
        or a
        jr nz, _
        or 1
        jr .end
_:
        xor a
.end:
        ; Restore A
        ld a, b
    pop bc
    ret

;; fpNot [Decimal Floating Point]
;;  Performs a logical NOT on the floating point number.
;; Inputs:
;;  IX: Pointer to operand
;; Outputs:
;;  Z: Result was false
;;  NZ: Result was true
fpNot:
    push bc
        ; Save A in B to preserve flags
        ld b, a
        ; Check if operand is 0
        ld a, (ix + 2)
        or a
        jr nz, _
        or 1
        jr .end
_:
        xor a
.end:
        ; Restore A
        ld a, b
    pop bc
    ret

;; fpCompare [Decimal Floating Point]
;;  Compares the two floating point numbers.
;; Inputs:
;;  IX, IY: Pointers to operands
;; Outputs:
;;  Flags: same as z80 CP instruction
fpCompare:
    push ix
    push iy
    push bc
        ; Save A
        ld c, a
        ; Compare signs
        ld a, (ix)
        and 0x80
        ld b, a
        ld a, (iy)
        and 0x80
        cp b
        jr nz, .end     ; Operands have opposite signs
        ; Check if both operands are negative
        and 0x80
        jr z, _
        ; Both operands are negative, so swap them to ensure correct comparison
        push ix \ push iy \ pop ix \ pop iy
_:
.macro fpCompareIter(x, y)
        ld a, (x)
        cp (y)
        jr nz, .end
.endmacro
        ; Compare exponents
        fpCompareIter(ix + 1, iy + 1)
        ; Compare mantissas
        fpCompareIter(ix + 2, iy + 2)
        fpCompareIter(ix + 3, iy + 3)
        fpCompareIter(ix + 4, iy + 4)
        fpCompareIter(ix + 5, iy + 5)
        fpCompareIter(ix + 6, iy + 6)
        fpCompareIter(ix + 7, iy + 7)
        fpCompareIter(ix + 8, iy + 8)
.undefine fpCompareIter
.end:
        ; Restore A
        ld a, c
    pop bc
    pop iy
    pop ix
    ret

;; fpMin [Decimal Floating Point]
;;  Finds the minimum of the two floating point numbers.
;; Inputs:
;;  IX, IY: Pointer to operands
;; Outputs:
;;  HL: Pointer to minimum
fpMin:
    call fpCompare
    jr c, _
    push iy \ pop hl
    ret
_:
    push ix \ pop hl
    ret

;; fpMax [Decimal Floating Point]
;;  Finds the maximum of the two floating point numbers.
;; Inputs:
;;  IX, IY: Pointers to operands
;; Outputs:
;;  HL: Pointer to maximum
fpMax:
    call fpCompare
    jr c, _
    push ix \ pop hl
    ret
_:
    push iy \ pop hl
    ret

;; fpRand [Decimal Floating Point]
;;  Generates a random floating point number between 0 and 1, similar to
;;  TI-OS's `rand` command.
;; Inputs:
;;  HL: Pointer to output
;; Notes:
;;  Uses `getRandom` to generate the digits, so it is not cryptographically
;;  random.
fpRand:
    push af
    push bc
    push de
        push hl
            ld (hl), 0
            inc hl
            ld (hl), 0x7F
            inc hl
.macro fpRandIter
            call getRandom
            ld b, a
            and 0xF0
            ld d, a
            call div8By8
            rld
            ld a, b
            and 0x0F
            ld d, a
            call div8By8
            rld
            inc hl
.endmacro
            ld e, 10
            fpRandIter
            fpRandIter
            fpRandIter
            fpRandIter
            fpRandIter
            fpRandIter
            fpRandIter
        pop hl
        ; There may be leading zeroes, so normalize.
        ; Note: There is no need to add extra random digits to compensate for
        ; removed leading zeroes. Even leading zeroes count towards entropy,
        ; so every result output from this function should have exactly
        ; 14 digits of entropy (roughly 46.5 bits).
        call fpNormalize
.undefine fpRandIter
    pop de
    pop bc
    pop af
    ret

;; fpIPart [Decimal Floating Point]
;;  Calculates the integer part of a floating point number, similar to
;;  TI-OS's `iPart()` command.
;; Inputs:
;;  IX: Pointer to operand
;; Outputs:
;;  IX: Pointer to result
fpIPart:
    push af
    push bc
    push hl
        push ix \ pop hl
        inc hl \ inc hl
        ; Check exponent
        ld a, (ix + 1)
        ; Very large numbers don't store a fractional part, so skip them
        cp 0x80 + 14
        jr nc, .end
        ; Negative exponents have no integer part
        cp 0x80
        jr nc, _
        ld (ix + 1), 0x80
        xor a
        jr .beginZeroLoop
_:
        sub 0x7F
        ; Point HL to the first fractional byte
        ld b, 0
        ld c, a
        srl c
        add hl, bc
.beginZeroLoop:
        ; If there are an odd number of integer digits, only zero half of
        ; the first byte
        ld b, a
        and 1
        jr z, _
        ld a, (hl)
        and 0xF0
        ld (hl), a
        inc hl
        inc b
_:
        ld a, 14
        sub b
        ld b, a
        srl b
        jr z, .end
.zeroLoop:
        ld (hl), 0
        inc hl
        djnz .zeroLoop
.end:
    pop hl
    pop bc
    pop af
    ret

;; fpFPart [Decimal Floating Point]
;;  Calculates the fractional part of a floating point number, similar to
;;  TI-OS's `fPart()` command.
;; Inputs:
;;  IX: Pointer to operand
;; Outputs:
;;  IX: Pointer to result
fpFPart:
    push af
    push bc
    push hl
        push ix \ pop hl
        inc hl \ inc hl
        ; Check exponent
        ld a, (ix + 1)
        ; Negative exponents are entirely fractional, so skip them
        cp 0x80
        jr c, .end
        ; Very large numbers have no fractional part
        cp 0x80 + 14
        jr c, _
        ld (ix + 1), 0x80
        xor a
        jr .beginZeroLoop
_:
        sub 0x80
        ; Point HL to the last integer byte
        ld b, 0
        ld c, a
        srl c
        add hl, bc
.beginZeroLoop:
        ; If there are an odd number of integer digits, only zero half of
        ; the first byte
        ld b, a
        and 1
        jr nz, _
        ld a, (hl)
        and 0x0F
        ld (hl), a
        dec hl
        dec b
_:
        inc b
        srl b
        jr z, .normalize
.zeroLoop:
        ld (hl), 0
        dec hl
        djnz .zeroLoop
.normalize:
        push ix \ pop hl
        call fpNormalize
.end:
    pop hl
    pop bc
    pop af
    ret
