satint 0.3.1

Saturating integers
Documentation

Saturating integers

CI coverage crates.io docs.rs MSRV no_std license

satint provides no_std, no-alloc integer wrapper types whose arithmetic operators saturate at the destination type's numeric bounds.

The signed wrappers are Si8, Si16, Si32, Si64, Si128, and Sisize. The unsigned wrappers are Su8, Su16, Su32, Su64, Su128, and Susize. Each type has a matching const constructor function: si8, si16, sisize, su8, su16, susize, and so on.

use satint::{Su8, su8};

assert_eq!(su8(250) + su8(10), Su8::MAX);
assert_eq!(su8(0) - 1, Su8::ZERO);

let mut health = Su8::MAX;
health += 1;
assert_eq!(health, Su8::MAX);

Types And Constructors

Wrappers are transparent newtypes around core::num::Saturating<T>. Use the associated new constructor, the short free constructor, or From for the matching primitive. Use into_inner to recover the primitive value.

use satint::{Si16, Su32, sisize, si16, su32, susize};

let signed = Si16::new(-40);
let also_signed = si16(-40);
let unsigned = Su32::from(40_u32);
let pointer_sized = sisize(-10) + susize(5);

assert_eq!(signed, also_signed);
assert_eq!(unsigned, su32(40));
assert_eq!(pointer_sized, sisize(-5));
assert_eq!(signed.into_inner(), -40);

Each wrapper provides BITS, MIN, MAX, ZERO, and ONE.

use satint::{Si8, Su8};

assert_eq!(Si8::MIN.into_inner(), i8::MIN);
assert_eq!(Su8::MAX.into_inner(), u8::MAX);
assert_eq!(Su8::ZERO + Su8::ONE, Su8::ONE);

Arithmetic

+, -, *, and signed unary - use saturating arithmetic. Assignment forms such as += and *= have the same behavior. The left-hand side determines the output type, so cross-width arithmetic clamps to the left-hand side's range.

use satint::{Si8, Si16, Su8, Su16, si8, si16, su8};

assert_eq!(Su8::MAX + 1, Su8::MAX);
assert_eq!(su8(0) - 1, Su8::ZERO);
assert_eq!(si8(100) * 2, Si8::MAX);
assert_eq!(-Si8::MIN, Si8::MAX);

assert_eq!(si16(120) + si8(10), si16(130));
assert_eq!(Si8::MAX + Si16::new(10), Si8::MAX);

let mut value = Su16::new(10);
value += Su8::new(5);
value *= 3;
assert_eq!(value.into_inner(), 45);

Mixed signed/unsigned addition and subtraction are supported for wrapper and primitive right-hand sides. Mixed signed/unsigned multiplication is not.

use satint::{Si8, Su8, si8, su8};

assert_eq!(si8(10) - su8(30), Si8::new(-20));
assert_eq!(si8(120) + su8(20), Si8::MAX);
assert_eq!(su8(5) + -10, Su8::ZERO);
assert_eq!(su8(5) - -10, Su8::new(15));

Left shifts saturate when bits would be shifted out of range. Right shifts match the primitive integer behavior, except oversized unsigned shifts produce zero and oversized signed shifts extend the sign.

use satint::{Si8, Su8, si8, su8};

assert_eq!(su8(0b0100_0000) << 2, Su8::MAX);
assert_eq!(su8(0b1000_0000) >> 8, Su8::ZERO);
assert_eq!(si8(-1) >> 8, si8(-1));

Bitwise &, |, ^, and ! are available for another value of the same wrapper type or the matching primitive type.

use satint::{Su8, su8};

let mask = su8(0b1100);
assert_eq!(mask & su8(0b1010), su8(0b1000));
assert_eq!(mask | 0b0011_u8, su8(0b1111));
assert_eq!(!Su8::ZERO, Su8::MAX);

Division And Remainder

The inherent checked_* methods accept the same wrapper type and return Option.

use satint::{Si32, si32, su32};

assert_eq!(si32(20).checked_div(si32(3)), Some(si32(6)));
assert_eq!(si32(20).checked_rem(si32(3)), Some(si32(2)));
assert_eq!(su32(20).checked_div(su32(0)), None);
assert_eq!(Si32::MIN.checked_div(si32(-1)), None);

The TryDiv, TryRem, TryDivAssign, and TryRemAssign traits accept any same-sign wrapper width or same-sign primitive right-hand side and return Result<_, DivError>.

use satint::{DivError, Si8, Si16, TryDiv, TryDivAssign, TryRem, si8};

assert_eq!(si8(20).try_div(Si16::new(3)), Ok(si8(6)));
assert_eq!(si8(20).try_rem(3_i8), Ok(si8(2)));
assert_eq!(si8(20).try_div(0_i8), Err(DivError::DivisionByZero));

let mut value = Si8::new(20);
value.try_div_assign(4_i8).unwrap();
assert_eq!(value, si8(5));

With the optional panicking-ops feature, /, %, /=, and %= are also implemented. Those operators panic on zero divisors just like primitive integer division.

Conversions

Use From and Into for conversions that cannot lose information.

use satint::{Si32, Su32, si8, su8};

let signed: Si32 = si8(-5).into();
let unsigned: Su32 = su8(200).into();
let primitive: u32 = unsigned.into();

assert_eq!(signed.into_inner(), -5);
assert_eq!(primitive, 200);

Use SaturatingFrom or SaturatingInto when the source may not fit. These traits clamp to the destination range. Signed-to-unsigned conversions clamp negative values to zero. The traits are implemented between all primitive integer types, between wrappers, and between wrappers and primitive integers.

use satint::{SaturatingFrom, SaturatingInto, Si8, Su8, si16, su16};

let unsigned: Su8 = su16(999).saturating_into();
let signed: Si8 = si16(-300).saturating_into();
let from_primitive = Su8::saturating_from(-12_i32);
let primitive: u8 = u8::saturating_from(si16(300));
let narrowed: i8 = i16::MAX.saturating_into();
let non_negative: usize = isize::MIN.saturating_into();

assert_eq!(unsigned, Su8::MAX);
assert_eq!(signed, Si8::MIN);
assert_eq!(from_primitive, Su8::ZERO);
assert_eq!(primitive, u8::MAX);
assert_eq!(narrowed, i8::MAX);
assert_eq!(non_negative, 0);

Same-width signedness conversions also have inherent saturating helpers.

use satint::{Si32, Su32, si32, su32};

assert_eq!(Su32::MAX.to_signed(), Si32::MAX);
assert_eq!(si32(-1).to_unsigned(), Su32::ZERO);
assert_eq!(su32(42).to_signed(), si32(42));
assert_eq!(si32(42).to_unsigned(), su32(42));

Floating-point sources can be converted into primitive integers or wrappers with SaturatingFrom and SaturatingInto. These conversions use Rust's as cast behavior: finite values truncate toward zero, out-of-range values clamp, and NaN becomes zero.

use satint::{SaturatingFrom, SaturatingInto, Si32, Su8};

let primitive: i8 = 200.0_f32.saturating_into();
let unsigned_primitive = u8::saturating_from(-1.0_f64);

assert_eq!(Si32::saturating_from(3.7_f64).into_inner(), 3);
assert_eq!(Si32::saturating_from(-3.7_f64).into_inner(), -3);
assert_eq!(Si32::saturating_from(f64::NAN), Si32::ZERO);
assert_eq!(Si32::saturating_from(f64::INFINITY), Si32::MAX);
assert_eq!(Su8::saturating_from(-1.0_f32), Su8::ZERO);
assert_eq!(Su8::saturating_from(300.0_f32), Su8::MAX);
assert_eq!(primitive, i8::MAX);
assert_eq!(unsigned_primitive, 0);

Wrapper-to-float From impls are provided only where every source value is represented exactly: Si8, Si16, Su8, and Su16 convert to f32; those types plus Si32 and Su32 convert to f64.

use satint::{si16, su32};

let as_f32: f32 = si16(-1234).into();
let as_f64: f64 = su32(4_000_000_000).into();

assert_eq!(as_f32, -1234.0);
assert_eq!(as_f64, 4_000_000_000.0);

Numeric Helpers

Wrappers expose many primitive integer helpers, returning wrapper values when the primitive method would return an integer of the same type.

use satint::{Si8, Su8, si8, su8};

assert_eq!(si8(-5).abs(), si8(5));
assert_eq!(Si8::MIN.abs(), Si8::MAX);
assert_eq!(si8(-10).abs_diff(si8(5)), su8(15));
assert_eq!(si8(16).checked_isqrt(), Some(si8(4)));

assert_eq!(su8(15).next_power_of_two(), su8(16));
assert_eq!(Su8::MAX.next_power_of_two(), Su8::MAX);
assert_eq!(su8(15).checked_next_power_of_two(), Some(su8(16)));
assert_eq!(su8(16).isqrt(), su8(4));

Common bit, endian, checked division/remainder, power, and integer logarithm helpers mirror primitive integer methods.

use satint::{Si8, Su16, su16};

let value = su16(0x1234);
assert_eq!(value.count_ones(), 5);
assert_eq!(Su16::from_be_bytes(value.to_be_bytes()), value);
assert_eq!(su16(2).pow(20), Su16::MAX);
assert_eq!(su16(100).checked_ilog10(), Some(2));

assert!(Si8::MIN.is_min());
assert!(Su16::ZERO.is_zero());

Iterators

Sum and Product are implemented for scalar values and references.

use satint::{Su32, su32};

let values = [su32(1), su32(2), su32(3), su32(4)];

assert_eq!(values.iter().copied().sum::<Su32>(), su32(10));
assert_eq!(values.iter().product::<Su32>(), su32(24));

Optional Features

The serde feature serializes and deserializes wrappers as their inner primitive integer values.

The rand feature implements rand 0.10 uniform sampling for every wrapper.

use rand::{RngExt, SeedableRng, rngs::SmallRng};
use satint::{si16, su8};

let mut rng = SmallRng::seed_from_u64(1);

let signed = rng.random_range(si16(-10)..si16(10));
assert!(signed >= si16(-10));
assert!(signed < si16(10));

let unsigned = rng.random_range(su8(1)..=su8(6));
assert!(unsigned >= su8(1));
assert!(unsigned <= su8(6));

no_std

satint is #![no_std], does not use alloc, and forbids unsafe code.

License

Licensed under either of MIT or Apache-2.0, at your option.