dsp-fixedpoint 0.2.0

Fixed point types for DSP
Documentation

Fixed point primitives

dsp-fixedpoint provides small no_std fixed-point primitives with explicit integer storage and widening accumulators.

Model

Q<T, A, F> stores a raw integer T and interprets it as scaled by 2^F.

  • T is the storage type.
  • A is the widened accumulator type used for intermediate results.
  • F is the number of fractional bits.

Type aliases cover the common signed, unsigned, and wrapping pairs: Q8/Q16/Q32/Q64, P8/P16/P32/P64, W8/W16/W32/W64, and V8/V16/V32/V64.

Construction

There are two construction modes:

  • from_int, from_f32, and from_f64 scale into the fixed-point domain.
  • from_bits is the raw-representation constructor.
use dsp_fixedpoint::Q8;

let scaled = Q8::<4>::from_int(3);
let raw = Q8::<4>::from_bits(3 << 4);

assert_eq!(scaled, raw);
assert_eq!(raw.into_bits(), 48);

Operator semantics

The crate keeps addition-like operators conservative and multiplication-like operators efficient. Q means Q<T, A, F> unless shown otherwise.

Operation Result Notes
Q<F> + Q<F>, Q<F> - Q<F>, Q<F> % Q<F> Q<T, A, F> Requires the same F; use .scale::<F1>() explicitly when scales differ.
Q * Q, Q / Q Q<T, A, F> Preserves the left-hand scale and quantizes the fixed-point result.
Q * T Q<A, T, F> Widened raw-integer multiplication; same as q.mul_wide(t).
T * Q T Applies Q as a gain to T and quantizes; same as q.apply(t).
Q / T Q<T, A, F> Raw integer division of the stored representation.
T / Q T Divides by Q as a fixed-point gain and quantizes.
Q *= Q, Q /= Q Q<T, A, F> Assignment forms follow the left-hand scale.
Q *= T, Q /= T Q<T, A, F> Assignment forms operate on the raw stored representation.

The mixed multiplication and division asymmetry is intentional: operand order chooses whether the result remains wide or is quantized immediately. Use mul_wide() and apply() when spelling that choice explicitly is clearer than relying on operand order.

Scale restrictions

F is an i8, but not every i8 value is meaningful everywhere.

  • Q::<_, _, -128>::DELTA is rejected at compile time.
  • Q::<_, _, F>::one() and Q::<_, _, F>::ONE are rejected at compile time when the mathematical value 1 is not exactly representable by the storage type and scale.
use dsp_fixedpoint::Q8;
use num_traits::One;

let _ = Q8::<7>::one();

Serialization

With feature = "serde", Q serializes transparently as its raw representation. For lossy scaled values, use dsp_fixedpoint::serde::as_f32 or dsp_fixedpoint::serde::as_f64.

# use serde::{Deserialize, Serialize};
use dsp_fixedpoint::Q32;

#[derive(Deserialize, Serialize)]
struct Config {
    #[serde(with = "dsp_fixedpoint::serde::as_f64")]
    gain: Q32<3>,
}

Ecosystem Traits

Q implements a small set of generic numeric traits where the semantics stay clear:

  • Bounded forwards to the raw storage bounds.
  • ToPrimitive, FromPrimitive, and NumCast convert numeric values rather than raw bits.
  • Signed is implemented for signed Q/W families.
  • Hash follows the raw representation.

Signed inherits One, so the existing representability restriction still applies: operations that need an exact 1, such as signum(), are only usable when F >= 0.

Formatting

Display shows decimal notation. Binary/Octal/UpperHex/LowerHex show dot notation.

use dsp_fixedpoint::Q8;

assert_eq!(
    format!("{:#b}", Q8::<3>::from_bits(0b01101001)),
    "0b1101.001"
);
assert_eq!(format!("{:x}", Q8::<4>::from_bits(-0x14)), "-1.4");

defmt

With feature = "defmt", Q implements defmt::Format and logs as a decimal value using f32. This keeps the target-side implementation compact for embedded use. Exact radix-dot formatting remains available through the standard Binary, Octal, and hex format traits.

bytemuck

With feature = "bytemuck", Q derives Pod, Zeroable, and TransparentWrapper. That makes zero-copy buffer casts and wrapper conversions available when the underlying storage and accumulator types also satisfy the corresponding bytemuck bounds.