Skip to content
All insights

Academy

Floating-Point Types, Literals, and the Prefix/Suffix System

Master floating-point precision in Rust with this overview of `f32` and `f64` types, literal syntax, and the complete prefix/suffix numeric system.

6 min read

Floating-Point Types, Literals, and the Prefix/Suffix System
On this page

If you've ever written 0.1 + 0.2 in any programming language and been surprised that the result is 0.30000000000000004, congratulations—you've discovered the fundamental nature of floating-point arithmetic. This isn't a bug in Rust, or JavaScript, or Python, or any language. It's a consequence of representing infinite decimal fractions in finite binary storage. The number 0.1 cannot be represented exactly in binary floating-point, just as 1/3 cannot be represented exactly in decimal. The approximation is incredibly close—close enough for virtually all practical purposes—but it's not exact.

Understanding floating-point arithmetic isn't optional for systems programmers. It's the difference between code that produces correct results and code that produces results that look correct but accumulate errors over millions of iterations until your financial system is off by thousands of dollars or your physics simulation has objects drifting through walls.

Rust provides two primary floating-point types—f32 and f64—with clear, predictable semantics based on IEEE 754. Beyond floating-point, Rust has a systematic approach to type literals through prefixes and suffixes that applies across all numeric types. Understanding this system makes numeric code clearer and more intentional.

At RantAI, where our AI models use floating-point for weights, gradients, and loss functions, and our scientific simulations accumulate millions of floating-point operations per timestep, understanding precision isn't academic—it's the difference between trustworthy results and numerical garbage. This article, drawn from Chapter 8, Sections 8.2.9, 8.2.10, and 8.3 of our guide "The Rust Programming Language," covers floating-point types, literal syntax, and the complete prefix/suffix system.

The Two Floating-Point Types#

Rust
fn main() {
    // f64: double precision (the default) — ~15 decimal digits of precision
    let pi: f64 = 3.141592653589793;
    let avogadro: f64 = 6.022e23;      // Scientific notation
    let planck: f64 = 6.626e-34;       // Very small number
    
    // f32: single precision — ~7 decimal digits of precision
    let pi_approx: f32 = 3.1415927;    // Only 7 digits of precision
    let gravity: f32 = 9.81;
    
    // Default literal type is f64
    let x = 1.0;   // f64 (inferred)
    let y = 2.5f32; // f32 (explicit suffix)
    let z = 3.0f64; // f64 (explicit suffix, same as default)
    
    // The classic floating-point surprise
    println!("0.1 + 0.2 = {:.20}", 0.1_f64 + 0.2_f64);
    // 0.30000000000000004441 — not exactly 0.3!
}

Use f64 by default. It provides approximately 15 decimal digits of precision with a range from ~5×10⁻³²⁴ to ~1.8×10³⁰⁸. On modern hardware, f64 operations are typically the same speed as f32 operations—the FPU processes both at the same rate. The extra precision costs nothing in performance and prevents accumulation errors.

Use f32 when memory bandwidth matters (GPU computation, large arrays of floats), when interfacing with APIs that expect 32-bit floats (OpenGL, some audio libraries), or when you're doing SIMD operations where f32 gives you twice as many lanes.

Literal Syntax: Scientific Notation and Suffixes#

Rust
fn main() {
    // Standard decimal notation
    let a = 1.0;       // f64
    let b = 0.5;       // f64
    let c = 100.0;     // f64
    
    // Scientific notation (e or E)
    let d = 1.5e10;    // 1.5 × 10¹⁰ = 15,000,000,000.0
    let e = 2.998e8;   // Speed of light in m/s (approximate)
    let f = 1.6e-19;   // Electron charge in coulombs
    
    // Type suffixes
    let g = 3.14f32;   // Explicitly f32
    let h = 2.718f64;  // Explicitly f64
    
    // Underscores for readability
    let big = 1_000_000.000_001;
    let sci = 6.022_140_76e23;  // Avogadro's number
    
    // Special values
    let inf = f64::INFINITY;
    let neg_inf = f64::NEG_INFINITY;
    let nan = f64::NAN;
    
    // NaN is weird: it's not equal to anything, including itself
    println!("NaN == NaN: {}", nan == nan);  // false!
    println!("Is NaN: {}", nan.is_nan());     // true
}

Important syntax note: no spaces within a literal. 65.43e-21 is valid. 65.43 e-21 is a syntax error—Rust sees 65.43 and e as separate tokens.

The Complete Prefix/Suffix System#

Rust uses a systematic approach to numeric literals that applies across all types:

Rust
fn main() {
    // Integer literal prefixes (base)
    let decimal = 255;           // No prefix = decimal (base 10)
    let hex = 0xFF;              // 0x = hexadecimal (base 16)
    let octal = 0o377;           // 0o = octal (base 8)
    let binary = 0b1111_1111;    // 0b = binary (base 2)
    
    // Integer literal suffixes (type)
    let a = 42i8;    // i8
    let b = 42i16;   // i16
    let c = 42i32;   // i32 (default)
    let d = 42i64;   // i64
    let e = 42i128;  // i128
    let f = 42u8;    // u8
    let g = 42u16;   // u16
    let h = 42u32;   // u32
    let i = 42u64;   // u64
    let j = 42u128;  // u128
    let k = 42usize; // usize
    let l = 42isize; // isize
    
    // Float literal suffixes
    let m = 3.14f32; // f32
    let n = 3.14f64; // f64 (default)
    
    // Character/string literal prefixes
    let ch = 'A';     // char (Unicode)
    let byte = b'A';  // u8 (ASCII byte)
    let string = "hello";      // &str (UTF-8)
    let byte_string = b"hello"; // &[u8; 5] (byte string)
    let raw = r"no \escapes";   // &str (raw, no escape processing)
    let raw_bytes = br"raw bytes"; // &[u8] (raw byte string)
}

The system is consistent: prefixes specify the base or encoding, suffixes specify the type. Once you see the pattern, numeric literals in Rust are self-documenting—the literal itself tells you exactly what type and base it represents.

Floating-Point Best Practices#

Rust
fn main() {
    // DON'T compare floats with ==
    let a = 0.1 + 0.2;
    let b = 0.3;
    // if a == b { ... }  // Unreliable!
    
    // DO use epsilon comparison
    let epsilon = 1e-10;
    if (a - b).abs() < epsilon {
        println!("Close enough to equal");
    }
    
    // DON'T use floats for money
    // let price = 19.99f64;  // Will accumulate rounding errors
    
    // DO use integer cents
    let price_cents: i64 = 1999;  // $19.99 represented exactly
    
    // Useful float methods
    let x = -3.7f64;
    println!("abs: {}", x.abs());      // 3.7
    println!("floor: {}", x.floor());  // -4.0
    println!("ceil: {}", x.ceil());    // -3.0
    println!("round: {}", x.round());  // -4.0
    println!("sqrt of 2: {}", 2.0f64.sqrt());  // 1.4142...
}

Broader Implications: Precision as Engineering#

At RantAI, floating-point precision isn't an afterthought—it's an engineering concern that's addressed in code review. Our ML training pipelines use f64 for loss accumulation (where precision matters over millions of iterations) and f32 for model weights (where memory matters for GPU batches). The type choice is documented, justified, and consistent.

Practical Applications & Strategic Takeaways#

For newcomers: Use f64 for everything. Switch to f32 only when an API requires it or when memory is a measured bottleneck. Never use floats for financial calculations.

For scientific programmers: Understand that f64 gives you about 15 significant digits. If your computation accumulates more than ~10¹⁵ operations, error analysis is essential.

For everyone: The prefix/suffix system is your friend. 0xFF_u8 is self-documenting: hexadecimal, 8-bit unsigned. No comment needed.

Our Commitment to Open Knowledge#

RantAI is committed to open education. Floating-point types and the literal system are covered in Chapter 8, Sections 8.2.9, 8.2.10, and 8.3 of our guide, "The Rust Programming Language," freely available online.

Explore these concepts further: https://trpl.rantai.dev

Support Our Mission & Get Your Handbook#

What's your worst floating-point precision story? The calculation that was wrong by just enough to matter? Share your numerical nightmares below!

#RustLang #FloatingPoint #IEEE754 #Literals #RantAI #LearnRust #Precision #NumericTypes #SystemsProgramming #TypeSafety

Next article

Integer Types and Literals: Choosing the Right Size for the Right Job