# unit-intervals
[](https://github.com/sunsided/unit-intervals/actions/workflows/ci.yml)
[](https://github.com/sunsided/unit-interval/blob/main/Cargo.toml)
[](https://github.com/rust-secure-code/safety-dance/)
[](https://docs.rust-embedded.org/book/intro/no-std.html)
Small constrained float types for values in the closed intervals `[0, 1]` and
`[-1, 1]`.
`unit-interval` provides `UnitInterval` and `SignedUnitInterval` wrappers for
normalized `f32` and `f64` values. Constructors reject out-of-range values and
`NaN`, while saturating constructors clamp inputs into the nearest valid value.
Operations that may leave the interval are available in checked and saturating
forms; operations that are closed over the interval return constrained values
directly.
## Rationale
Many domains use floats whose valid range is smaller than the full floating-point
space:
- Probabilities and confidence scores live in `[0, 1]`.
- Interpolation factors, progress values, weights, blend factors, and opacity
often live in `[0, 1]`.
- RGBA color channels are commonly represented as normalized channel values in
`[0, 1]`.
- Sine and cosine results, joystick axes, audio pan, centered offsets, and
balance controls live in `[-1, 1]`.
Encoding those ranges in the type system keeps checks close to input boundaries
and lets later code state its requirements directly. A function that accepts
`UnitInterval` does not need to rediscover whether `1.2`, `-0.1`, or `NaN` can
arrive.
## Examples
```rust
use unit_intervals::UnitInterval;
let probability = UnitInterval::new(0.8).unwrap();
let clamped = UnitInterval::saturating(1.2);
assert_eq!(probability.get(), 0.8);
assert_eq!(clamped, UnitInterval::ONE);
assert_eq!(UnitInterval::<f32>::new(f32::NAN), None);
```
```rust
use unit_intervals::{SignedUnitInterval, UnitInterval};
let axis = SignedUnitInterval::new(-0.5).unwrap();
let weight = UnitInterval::new(0.25).unwrap();
assert_eq!((axis * weight).get(), -0.125);
assert_eq!(axis.saturating_add(weight).get(), -0.25);
```
```rust
use unit_intervals::UnitInterval;
fn mix(start: f32, end: f32, amount: UnitInterval) -> f32 {
amount.lerp(start, end)
}
assert_eq!(mix(10.0, 20.0, UnitInterval::HALF), 15.0);
```
## Feature Flags
- `std` is enabled by default and provides APIs that require the Rust standard
library. Disable default features for `no_std` use.
- `arbitrary` enables `Arbitrary` support for generating valid fuzz inputs.
- `bytemuck` enables `Zeroable`, `NoUninit`, and `CheckedBitPattern` support.
The interval wrappers intentionally do not implement `Pod`, because not every
backing float bit pattern is valid for these constrained types.
- `num-traits` enables conversion and bounds traits from `num-traits`.
- `serde` enables transparent serialization and checked deserialization through
the inner floating-point value.
- `rkyv` enables zero-copy serialization and checked deserialization through the
inner floating-point value.
- `assertions` enables internal invariant assertions in non-test builds.
- `unsafe` enables unchecked constructors and operations such as
`UnitInterval::new_unchecked` and `SignedUnitInterval::new_unchecked`.
Unsafe code is forbidden unless the `unsafe` or `bytemuck` feature is explicitly
enabled. The unchecked APIs are behind the `unsafe` feature gate and require the
caller to prove the value is inside the relevant interval and is not `NaN`; the
`bytemuck` feature only enables unsafe marker-trait implementations.
## Development
This repository uses the Taskfile as the local automation entry point:
```sh
task fmt:check
task lint:check
task test:all-features
task fuzz:build
task fuzz:smoke
```