Skip to content


Repository files navigation


z80float is a floating point library targeting the Z80 processor. While this project is tested on a TI-84+, the code should be generic enough to run on any Z80 CPU with very little modification (mostly defining scrap RAM locations).

Supported Formats

These have a complete library of routines including the basic 5 functions (+, -, *, /, squareroot) as well as trigonometric functions (sine/cosine/tangent), inverse trig functions (arcsine, arccosine, arctangent), hyperbolic functions (sinh/cosh/tanh), inverse hyperbolics functions, logarithms, exponentials, comparison, and rand:

  • f24 - These are 24-bit floats modeled after IEEE-754 binary32, removing one exponent bit and 7 significand bits. These are well-suited to the Z80-- the operands can be passed via registers allowing the routines to have a small memory and CPU-time footprint, while still having 5 digits of precision. NOTE: f24toa converts an f24 float to a string that can be displayed. This is found in the conversion folder.

  • f32 - an IEEE-754 binary32 format (the classic "single")

    • NOTE: f32toa converts an f32 float to a string that can be displayed. This is found in the conversion folder.
  • single - this is poorly named. This was my own 32-bit float format. It can represent all IEEE-754 binary32 numbers, but the layout of the bits is different (keeping all exponent bits in one byte) and represents 0/inf/NaN differently allowing slightly more numbers to be represented.

  • extended - These are 80-bit floats providing 19-digits precision. These are quite fast all things considered-- My TI-84+'s TI-OS takes over 85000 clock cycles to calculate a square root to 14 digits precision, compared to 6500cc for xsqrt (that's 13 times faster). Most other routines aren't that extreme, but they are generally pretty fast compared to the TI-OS routines. To be fair, TI's floats are BCD, so clunkier to work with.

Future ideas:

  • f64 - an IEEE-754 binary64 format (the classic "double")

    • note that in the conversion folder there are routines f64tox.z80 and xtof64.z80 in case you need to support the binary64 format. This might end up being how most f64 routines are implemented, at least early on.
  • f16 - The binary16 format. Although I might opt to work on DW0RKiN's library instead. Their's has a bunch of 16-bit float implementations including binary16.

  • BCD floats - Might be useful for calculators as these use base-10.

  • base10 floats - encoding 3 digits in 10 bits. It has the advantage of BCD in that it is quick to convert to and from base-10 representation, but it can take advantage of more base-2 arithmetic since 1000 is close to a power of 2 (2^10 = 1024) and it can pack more digits into a smaller space than BCD (10 bits for 3 digits instead of 12).


The compiler that I've been using for this project is spasm-ng which has its share of syntax quirks. In the future, I might migrate to fasmg using jacobly's Z80 includes (they also have ez80 includes).

I've set up z80float so that when you #include a file, it #includes any dependencies that have yet to be included. For example, if you want to use xatan, then #include "xatan.z80" will include all of the following files (if they haven't already been included) in the following order. Note that some routines are shared and so I am omitting multiplicities:


In order for this to work, you will need to add the format's root directory (i.e. extended or single) to your assembler's default search path. You will also need to add the common folder to your compiler's search path!.

With spasm-ng, I use its -I flag. For example:

spasm foo.z80 foo.8xp -I bar/z80float/common -I bar/z80float/extended

General Syntax

NOTE: The following does not apply to the 24-bit floats. View the readme for f24 documentation if you want to use them!

The calling syntax for the single- and extended-precision float routines are:

    HL points to the first operand
    DE points to the second operand (if needed)
    IX points to the third operand (if needed, rare)
    BC points to where the result should be output

In all cases unless specifically noted, registers are preserved on input and output. Notable exceptions are the comparison routines such as xcmp and cmpSingle.

Subroutines, like mul16, do not preserve registers.


In general, the extended precision routines are prefixed with an x. For example, xmul is the extended precision multiplication routine. The single precision floats generally have a suffix of Single, as in mulSingle.

To evaluate log_2(pi^e) with extended-precision floats you could do:

  ;first do pi^e and store it to temp mem, xOP1
  ld hl,xconst_pi
  ld de,xconst_e
  ld bc,xOP1
  call xpow

  ;Now BC points to xOP1, and we want to compute log_2
  ;Need to put the pointer into HL, so do BC --> HL
  ;Store back to xOP1, so don't change BC. DE isn't used.
  ld h,b
  ld l,c
  call xlg

For single-precision:

  ;first do pi^e and store it to temp mem, scrap
  ld hl,const_pi
  ld de,const_e
  ld bc,scrap
  call powSingle

  ;Now BC points to scrap, and we want to compute log_2
  ;Need to put the pointer into HL, so do BC --> HL
  ;Store back to scrap, so don't change BC. DE isn't used.
  ld h,b
  ld l,c
  call lgSingle


Extended Precision Floats

Extended precision floats are stored as 8 little-endian bytes for the (normalized) mantissa, then a 16-bit word with the upper bit being sign, and the lower 15 bits as the exponent. The exponent is biased such that 0x4000 corresponds to an exponent of 0. Special numbers are represented with an exponent of -0x4000 (physical value 0x0000). The sign bit still indicates sign, and the top two bits of the mantissa represent the specific special number:

    0 is represented with %00
    inf is represented with %11
    NaN is represented with %01

The /tools folder has a Python program to convert numbers into the extended-precision format that this library uses. Pass the space delimited numbers on the command line and it will print out the number(s) in .db format. For example:

zeda@zeda:~/z80float/tools$ python 1.2 1337
.db $9A,$99,$99,$99,$99,$99,$99,$99,$00,$40    ;1.2
.db $00,$00,$00,$00,$00,$00,$20,$A7,$0A,$40    ;1337

Single Precision Floats

Single precision floats are stored as a 24-bit little-endian mantissa, with the top bit being an implicit 1. The actual top bit represents the sign. After that is an 8-bit exponent with a bias of +128. Special values are stored with an exponent of -128 (physical value being 0x00). The mantissa requires the top three bits to contain specific values:

    +0 is represented as 0x00
    -0 is represented as 0x80
    +inf is represented as 0x40
    -inf is represented as 0xC0
    NaN is represented as 0x20

As with the extended-precision floats, the /tools folder has a Python program to convert numbers into the single-precision format that this library uses. For example:

zeda@zeda:~/z80float/tools$ python 1.2 1337
.db $9A,$99,$19,$80  ;1.2
.db $00,$20,$27,$8A  ;1337