liveBook · Manning

Bit pattern

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

A bit pattern is an arrangement of binary digits whose interpretation depends on the data type assigned to it.

A bit pattern is an arrangement of binary digits whose meaning comes from the data type assigned to it. The CPU stores bits, while the type system supplies their interpretation. section 5.1

One pattern, two numbers

The pattern 1100001111000011 represents 50115 as a u16 but -15421 as an i16. The bits remain unchanged; only their interpretation changes. section 5.1

The same idea explains file formats. Text files use an encoding that consistently maps bit strings to characters; arbitrary binary files may provide no externally visible meaning. section 5.1

Reinterpreting a floating-point pattern

A Rust f32 value such as 42.42 can be reinterpreted as a u32 without changing its underlying bits, printed in binary, and converted back to the original float. section 5.1

Figure 5.1 An overview of the three components encoded within the bits of a floating-point number for the f32 type in Rust

Use the layout overview to see how an f32 bit pattern divides into sign, exponent, and mantissa fields. section 5.4.1

An f32 stores 42.42 as 01000010001010011010111000010100, or 0x4229AE14. Its sign, exponent, and mantissa fields decode into the final value. section 5.4.1

Dissecting a floating-point bit pattern involves three steps:

  • Extract the fields from the container with shifts and masks. section 5.4.5
  • Decode each raw field into its numerical meaning. section 5.4.5
  • Reassemble the decoded parts into an ordinary number. section 5.4.5
Listing 5.10 Deconstructing a floating-point value
 const BIAS: i32 = 127;                            #1
 const RADIX: f32 = 2.0;                           #1
 
 fn main() {                                       #2
   let n: f32 = 42.42;
 
   let (sign, exp, frac) = to_parts(n);
   let (sign_, exp_, mant) = decode(sign, exp, frac);
   let n_ = from_parts(sign_, exp_, mant);
 
   println!("{} -> {}", n, n_);
   println!("field    |  as bits | as real number");
   println!("sign     |        {:01b} | {}", sign, sign_);
   println!("exponent | {:08b} | {}", exp, exp_);
   println!("mantissa | {:023b} | {}", frac, mant);
 }
 
 fn to_parts(n: f32) -> (u32, u32, u32) {
   let bits = n.to_bits();
 
   let sign     = (bits >> 31) & 1;                #3
   let exponent = (bits >> 23) & 0xff;             #4
   let fraction =  bits & 0x7fffff ;               #5
 
   (sign, exponent, fraction)                      #6
 }
 
 fn decode(
   sign: u32,
   exponent: u32,
   fraction: u32
 ) -> (f32, f32, f32) {
   let signed_1 = (-1.0_f32).powf(sign as f32);    #7
 
   let exponent = (exponent as i32) - BIAS;        #8
   let exponent = RADIX.powf(exponent as f32);     #8
 
   for i in 0..23 {                                #9
     let mask = 1 << i;                            #9
     let one_at_bit_i = fraction & mask;           #9
     if one_at_bit_i != 0 {                        #9
       let i_ = i as f32;                          #9
       let weight = 2_f32.powf( i_ - 23.0 );       #9
       mantissa += weight;                         #9
     }                                             #9
   }                                               #9
 
   (signed_1, exponent, mantissa)
 }
 
 fn from_parts(                                    #10
   sign: f32,
   exponent: f32,
   mantissa: f32,
 ) -> f32 {
     sign *  exponent * mantissa
 }

The worked program demonstrates extracting, decoding, and reconstructing the fields of 42.42 as an f32. section 5.4.5

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

How deep it goes
Explains type-dependent meanings, bit-preserving conversion, and f32 field extraction, stopping short of a comprehensive format reference.
Examples
Uses signed-versus-unsigned integers, an f32-to-u32 round trip, the value 42.42, and a compact decimal format for model values.
What it assumes
Assumes basic Rust types and binary notation while teaching floating-point representation and bit-field operations through examples.
What you can do after
Diagnose and interpret untyped bytes by extracting, decoding, and reconstructing their bit fields.
How it connects
Connects data types with binary files, text encoding, floating-point numbers, IEEE 754, data compression, and bit manipulation.

Also explained in

Compare how 2 books explain bit pattern →

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.