Unofficial IEEE 754 reference implementation: simple-soft-float 0.1.0; Includes optional Python bindings

(Disclaimer: I'm not associated with IEEE 754)

Prioritizes being correct and readable over performance. Built on top of algebraics, the algebraic numbers library that I also wrote.

Supports emulating all platforms without changing compile-time configuration options (unlike Berkeley SoftFloat).

Allows big-int based floats -- can make f65536 if desired! (though it will be very slow for big numbers)

Supports IEEE 754 status flags.
Supports 5 different rounding modes.
Supports emulating trapping FP operations. (checking status flags after each operation still needed)

Caveat: currently doesn't completely handle NaN payload propagation, however should be 100% bit-level accurate on RISC-V where NaN payload propagation isn't needed.

Tested against Berkeley SoftFloat, the de-facto industry-standard reference implementation of IEEE 754.

Source on Debian Salsa (also mirrored on GitHub):

Implemented Types:

  • f16
  • f32
  • f64
  • f128
  • custom types (not thoroughly tested, all IEEE754 standard interchange types should be bug-free except for NaN payload propagation)
  • DynamicFloat (dynamically adjustable type, big-int based)

Implemented operations:

  • Add, Sub, Mul, Div, Sqrt, fused mul-add
  • Quiet/Signaling Compare
  • Integer <-> Float conversions
  • Float <-> Float conversions
  • next_down/next_up
  • sign manipulation (neg, abs, copy_sign, direct sign access)
  • reciprocal square-root 1 / sqrt(x) -- not supported on Berkeley SoftFloat
  • many more (implements all IEEE 754-2019 required operations)

Implemented platforms:

  • ARM
  • RISC-V
  • Power ISA
  • x86 SSE/AVX
  • Sparc
  • HPPA
  • MIPS 2008 revision
  • MIPS pre-2008 revision
  • Custom

It should be possible to add posit representations for those who want to experiment with posits as a storage format. (See §6.3 of the second cited paper; the rest of that paper explains why posits are problematic when used directly for computation.)

Neat! Pull requests welcome (on salsa or github, salsa preferred)!

I wrote simple-soft-float for testing against FP hardware we're implementing for the Libre-RISCV CPU/GPU, so I probably won't implement support for Posits myself.

1 Like

Very impressive!

How difficult would it be to make it const fn compatible, and what features would you need from the compiler to do so?

This might help with trying to do floating-point evaluation at compile time.


It shouldn't be that difficult, it's mostly replacing fn with const fn and disabling the cache built into the algebraics crate for mathematical constants (log 2 and a few others).
It would need looping, match, allocation/deallocation, and const panic (aborting compilation on panic is sufficient).
It does support the top-level values (the types user code directly uses) not containing allocations for f16/f32/f64/f128 (but not big-int based types), but it uses allocations extensively for most of the operations since it does arithmetic using algebraic numbers built on polynomials of big-ints.

It might work, but, since it uses RealAlgebraicNumber to implement most of the operations, it would be quite slow due to the complexity of RealAlgebraicNumber's implementation. Think factoring-polynomials-in-a-ring-of-modular-integers level of complexity (and that's only about a third of RealAlgebraicNumber's implementation).

Just as a guess, even running natively on x86_64, it probably can currently do a few thousand operations per second as it is, rather than the millions or 10 millions that fast soft-float libraries can do.

It could be sped up, but that would probably make it harder to read simple-soft-float's implementation; I tried to prioritize readability over speed since IEEE 754 is really complex and we (Libre-RISCV) are using it to verify our hardware implementation where correctness is much higher priority than speed of testing.

That said, pull requests welcome as long as the implementation is relatively easy to understand and/or there could be a way to switch between a not-yet-built fast and obscure, and the existing slower but readable implementation.