# 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.2"
```
## 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.
Unsigned-float values also forward common float formatting to their promoted
native value:
```rust
use unsigned_float::Uf16;
let value = Uf16::from_f32(1.5);
assert_eq!(format!("{value:.2}"), "1.50");
assert_eq!(format!("{value:e}"), "1.5e0");
```
## Exponents
Use `PowUf` to raise native floats to unsigned-float exponents:
```rust
use unsigned_float::{PowUf, Uf16};
let exponent = Uf16::from_f32(0.5);
let root = 9.0_f32.powuf(exponent);
assert_eq!(root, 3.0);
```
`PowUf` is dependency-free from the caller's perspective and works in `no_std`
builds by using `libm` internally. It is implemented for `f32` and `f64` bases
over all exported unsigned-float exponent layouts. With `f128`, `Uf64` can also
be used as an exponent for `f32` and `f64` bases.
The trait dispatches through small kernels before falling back to general
`libm` exponentiation. Exact exponents `0`, `1`, and `0.5` map to identity,
base, and square-root paths, while exact small integer exponents use
exponentiation by squaring.
UF8 exponentiation is also available and uses generated lookup tables. The
output follows the receiver's layout, including when the exponent uses the
other UF8 layout:
```rust
use unsigned_float::{PowUf, Uf8, Uf8E5M3};
let root = Uf8::from_f32(9.0).powuf(Uf8::from_f32(0.5));
let cross = Uf8::from_f32(9.0).powuf(Uf8E5M3::from_f32(0.5));
assert_eq!(root.to_f32(), 3.0);
assert_eq!(cross.to_f32(), 3.0);
```
For complement powers, use `Pow1mUf` to evaluate `(1 - u)^a` directly:
```rust
use unsigned_float::{Pow1mUf, Uf8};
let root = Uf8::from_f32(0.75).pow1muf(Uf8::from_f32(0.5));
assert_eq!(root.to_f32(), 0.5);
```
Native `f32`/`f64` complement powers use `log1p`/`expm1` for the general
`0 <= u <= 1` case. Same-layout and cross-layout UF8 complement powers use
generated lookup tables, so `u` and `a` can stay compact from input through
output.
## 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`, `Div`, UF8 `PowUf`, and UF8 `Pow1mUf`. 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
- float-style `Display`, `LowerExp`, and `UpperExp` formatting
- `PowUf` for native float bases and unsigned-float exponents
- `Pow1mUf` for native and UF8 complement powers
- fallible conversions from `f64` and primitive integers
- `Add`, `Sub`, `Mul`, and `Div`
- raw-bit `Ord`/`PartialOrd`
- generated UF8 arithmetic and exponentiation 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).