liveBook · Manning

Q-format

As explained in Rust in Action: Systems programming concepts and techniques

Q format is a fixed-point numeric representation convention that allocates bits between a signed integer encoding and a fractional scale.

Q format represents fractional values with fixed-point scaling rather than a moving decimal place. Q7 stores values from approximately −1 to 1 in one signed byte: seven value bits plus one sign bit. section 5.5

A Q7 implementation:

  • Wraps an i8 in a Rust tuple struct, allowing the compiler to track the sign and derive comparison-related traits. section 5.5
  • Converts f64 values by multiplying by 128 and storing the result as i8. section 5.5
  • Clamps values at or beyond the boundaries to Q7(127) and Q7(-128), then converts back using a 2⁻⁷ scale. section 5.5

Converting a value

A value such as 0.5 becomes Q7(64), because 0.5 × 128 equals 64; converting it back multiplies 64 by 2⁻⁷. section 5.5

Listing 5.14 Full code implementation of the Q7 format
 #[derive(Debug,Clone,Copy,PartialEq,Eq)]
 pub struct Q7(i8);
 
 impl From<f64> for Q7 {
     fn from (n: f64) -> Self {
         if n >= 1.0 {
             Q7(127)
         } else if n <= -1.0 {
             Q7(-128)
         } else {
             Q7((n * 128.0) as i8)
         }
     }
 }
 
 impl From<Q7> for f64 {
     fn from(n: Q7) -> f64 {
         (n.0 as f64) * 2f64.powf(-7.0)
     }
 }
 
 impl From<f32> for Q7 {
     fn from (n: f32) -> Self {
         Q7::from(n as f64)
     }
 }
 
 impl From<Q7> for f32 {
     fn from(n: Q7) -> f32 {
         f64::from(n) as f32
     }
 }
 
 #[cfg(test)]
 mod tests {            #1
     use super::*;      #2
     #[test]
     fn out_of_bounds() {
         assert_eq!(Q7::from(10.), Q7::from(1.));
         assert_eq!(Q7::from(-10.), Q7::from(-1.));
     }
 
     #[test]
     fn f32_to_q7() {
         let n1: f32 = 0.7;
         let q1 = Q7::from(n1);
 
         let n2 = -0.4;
*         let q2 = Q7::from(n2);
 
         let n3 = 123.0;
         let q3 = Q7::from(n3);
 
         assert_eq!(q1, Q7(89));
         assert_eq!(q2, Q7(-51));
         assert_eq!(q3, Q7(127));
     }
 
     #[test]
     fn q7_to_f32() {
         let q1 = Q7::from(0.7);
         let n1 = f32::from(q1);
         assert_eq!(n1, 0.6953125);
 
         let q2 = Q7::from(n1);
         let n2 = f32::from(q2);
         assert_eq!(n1, n2);
     }
 }

Inspect the complete Q7 implementation, including conversions for f32 and f64 and tests for bounds and round trips. section 5.5

How Rust in Action: Systems programming concepts and techniques covers it

How deep it goes
Defines Q7 storage and conversion mechanics, but does not develop broader fixed-point arithmetic.
Examples
Uses Q7 conversion listings, boundary cases, f32 and f64 conversions, and passing Rust tests.
What you can do after
Build it from scratch as a Rust Q7 type with floating-point conversions and boundary tests.
How it connects
Connects fixed-point representation with floating-point formats, quantization, Rust tuple structs, and integer storage.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Rust in Action: Systems programming concepts and techniques. The section links point to the parts of the book it draws on.