# unsigned-float
[](https://crates.io/crates/unsigned-float)
[](https://docs.rs/unsigned-float)
[](https://github.com/MicroPerceptron/ufloat/commits/main)
[](https://github.com/MicroPerceptron/ufloat/blob/main/LICENSE)
[](https://github.com/MicroPerceptron/ufloat/issues)
[](#no_std)
Unsigned floating-point types for values that can never be negative.
`unsigned-float` provides compact unsigned float newtypes with IEEE-like
exponent and mantissa fields, but no sign bit. The missing sign bit buys either
extra precision or range, removes negative zero, and makes total ordering as
simple as comparing the raw integer representation.
This crate is young and experimental. The current behavior prioritizes a small,
clear API and predictable bit layouts over exhaustive IEEE 754 compatibility.
## Formats
| `Uf8` | `Uf8E4M4` | E4M4 | `u8` | LUT by default, nightly `f16` optional |
| | `Uf8E5M3` | E5M3 | `u8` | LUT by default, nightly `f16` optional |
| `Uf16` | `Uf16E5M11` | E5M11 | `u16` | promote to `f32` |
| | `Uf16E6M10` | E6M10 | `u16` | promote to `f32` |
| `Uf32` | `Uf32E8M24` | E8M24 | `u32` | promote to `f64` |
| `Uf64` | `Uf64E11M52` | E11M52 | `u64` | promote to nightly `f128` |
The all-ones exponent is reserved for infinity and NaN, following the usual
IEEE-style convention. Negative native inputs encode to NaN. Operations whose
mathematical result is negative also produce NaN.
`Uf8`, `Uf16`, and `Uf32` are ergonomic aliases for the default concrete layout
types. Layout-specific names are exported for every format because changing the
exponent/mantissa split changes the numeric contract. Use E4M4/E5M11 when
precision near 1.0 matters more; use E5M3/E6M10 when range matters more.
`Uf64` is available only with the `f128` feature.
## Install
```toml
[dependencies]
unsigned-float = "0.1"
```
## Example
```rust
use unsigned_float::{Uf8, Uf8E5M3, Uf16, Uf32};
let a = Uf16::from_f32(1.5);
let b = Uf16::from_f32(2.0);
let product = a * b;
assert_eq!(product.to_f32(), 3.0);
let small = Uf8::from_f32(0.5);
let large = Uf8::from_f32(4.0);
assert!(small < large);
assert_eq!(Uf8::ONE.to_bits(), 0x70);
let wide = Uf32::from_f64(1024.0);
assert_eq!(wide.to_f64(), 1024.0);
let wider_range = Uf8E5M3::from_f32(1024.0);
assert_eq!(wider_range.to_f32(), 1024.0);
```
## Raw Ordering
Unsigned floats are ordered by their raw bits:
```rust
use unsigned_float::Uf16;
let values = [Uf16::from_f32(4.0), Uf16::ZERO, Uf16::from_f32(1.0)];
let mut sorted = values;
sorted.sort();
assert_eq!(sorted[0], Uf16::ZERO);
assert_eq!(sorted[1], Uf16::ONE);
```
Because there is no sign bit and no negative zero, `Ord` and `PartialOrd` are
implemented as simple unsigned integer comparisons of the underlying bits. NaN
payloads therefore sort after infinity rather than behaving like IEEE
`f32::partial_cmp`.
## Conversions
Use the explicit constructors when you want IEEE-like encoding behavior:
```rust
use unsigned_float::Uf8;
assert!(Uf8::from_f32(-1.0).is_nan());
assert!(Uf8::from_f32(f32::INFINITY).is_infinite());
```
Use `TryFrom`/`TryInto` when you want to reject values outside the finite,
non-negative domain:
```rust
use unsigned_float::{ConversionError, Uf16};
assert_eq!(Uf16::try_from(42_u32), Ok(Uf16::from_f32(42.0)));
assert_eq!(Uf16::try_from(-1_i32), Err(ConversionError::Negative));
assert_eq!(Uf16::try_from(f64::INFINITY), Err(ConversionError::Infinite));
```
The crate implements fallible conversions from `f64` and primitive integer
types. With the `f16` feature enabled, `from_f16`, `to_f16`,
`From<f16>`, and `Into<f16>` are also available.
## Features
| `f16` | No | Uses nightly primitive `f16` for `Uf8` arithmetic dispatch. Requires nightly Rust. |
| `f128` | No | Enables `Uf64`/`Uf64E11M52` and promotes its arithmetic through nightly primitive `f128`. |
| `nightly` | No | Convenience feature enabling both `f16` and `f128`. |
| `soft-float` | No | Forces the software/LUT dispatch path where available. If combined with `f16`, `soft-float` wins for `Uf8`. |
The default `Uf8` arithmetic path uses generated 256x256 lookup tables for
`Add`, `Sub`, `Mul`, and `Div`. These tables are generated by `build.rs` into
Cargo's `OUT_DIR` and embedded with `include_bytes!`, so they do not need to be
checked in.
## no_std
The library is `#![no_std]`. The build script and benchmarks use `std`, but the
crate API itself does not require allocation or the standard library.
## Benchmarks
The benchmark suite uses nightly's built-in `test` harness and covers
conversions, arithmetic, and raw-bit ordering for all exported layouts.
```sh
cargo bench --bench arithmetic
cargo bench --features f16 --bench arithmetic
cargo bench --all-features --bench arithmetic
```
Each command benchmarks the dispatch path selected by that feature set. For
example, `--features f16` measures the `Uf8` primitive-`f16` path, while the
default command measures the generated LUT path.
## Status
Implemented:
- `Uf8`, `Uf16`, and `Uf32` transparent newtypes
- explicit concrete layout names: `Uf8E4M4`, `Uf8E5M3`, `Uf16E5M11`,
`Uf16E6M10`, `Uf32E8M24`, and feature-gated `Uf64E11M52`
- raw bit constructors and extractors
- native float conversions
- fallible conversions from `f64` and primitive integers
- `Add`, `Sub`, `Mul`, and `Div`
- raw-bit `Ord`/`PartialOrd`
- generated UF8 arithmetic lookup tables
- benchmarks across conversions, arithmetic, and ordering
Still worth exploring:
- native baseline benchmarks against `f32` and `f64`
- configurable UF8 dispatch for direct LUT versus promote-to-native comparisons
- SIMD bulk operations
- more explicit NaN payload policy
- broader property tests for all finite `Uf16` layout edge cases
## License
See [LICENSE](LICENSE).