Rust bietet zwei Float-Typen — f32 (32 bit) und f64 (64 bit, Default). Beide folgen dem IEEE-754-Standard, was bedeutet: sie können NaN (Not a Number), +Infinity und -Infinity annehmen, und Gleichheit zwischen Floats ist eine subtile Sache. Dieser Artikel zeigt, wie Floats in Rust funktionieren, warum sie nur PartialOrd (nicht Ord) sind, wie man sicher vergleicht und welche Methoden in der Standard-Library bereitstehen.

f32 und f64 — was sie unterscheidet

Aspektf32f64
Bitbreite3264
Signifikante Stellen≈ 7 dezimal≈ 15-16 dezimal
Wertebereich±3,4 · 10³⁸±1,8 · 10³⁰⁸
Default-Typneinja
Performanceetwas schneller auf 32-bit-Hardwareidentisch oder schneller auf modernen x86_64
Speicher4 Bytes8 Bytes
AnwendungGrafik, ML mit halber Präzisionsonst alles

In der überwiegenden Mehrheit aller Anwendungsfälle ist f64 die richtige Wahl. f32 lohnt sich gezielt bei:

  • Grafik/Rendering — GPUs arbeiten oft nativ in f32.
  • Speicher-kritischen Vektoren — Audio-Samples, große Mess-Datensätze.
  • WebAssembly — kleinere Daten reduzieren Transfer- und Speicherkosten.

Literale und Suffixe

Rust Float-Literale
let a = 3.14;             // f64 (Default)
let b = 3.14f32;          // f32
let c = 1_000_000.5;       // f64, mit Unterstrich
let d = 2.5e10;           // f64, Exponenten-Schreibweise = 25_000_000_000.0
let e = 1.0f32;           // f32

let f: f32 = 1.0;          // f32 durch Annotation
let g = 0.0;              // f64 (Default)

Zwei wichtige Eigenheiten:

  • Ein Punkt erzeugt automatisch einen Float: 1.0 ist f64, 1 ist i32. 1. allein ist gültig und f64.
  • Float-Literale benötigen einen Punkt oder Exponenten: let x: f64 = 5; ist ein Fehler. Stattdessen 5.0 oder 5_f64.

NaN und Infinity

IEEE-754 reserviert spezielle Float-Werte, die in der Arithmetik auftreten können:

Rust Spezielle Werte
let nan = f64::NAN;
let pos_inf = f64::INFINITY;
let neg_inf = f64::NEG_INFINITY;

println!("{nan} {pos_inf} {neg_inf}");
// NaN inf -inf

// Entsteht u.a. so:
let a = 0.0_f64 / 0.0;       // NaN
let b = 1.0_f64 / 0.0;       // inf
let c = (-1.0_f64).sqrt();   // NaN (negativer Wert unter Wurzel)

Prüfen, ob ein Wert NaN oder Infinity ist:

Rust Prüfen
let x = 0.0_f64 / 0.0;
println!("{}", x.is_nan());     // true
println!("{}", x.is_finite());  // false

let y: f64 = 1.0 / 0.0;
println!("{}", y.is_infinite());  // true

Warum nur PartialOrd, nicht Ord?

Floats können nicht total geordnet werden — wegen NaN. Per IEEE-754 ist NaN != NaN, NaN < x ist false für jedes x, und NaN > x ebenfalls.

Das ist der Grund, warum f64 zwar PartialOrd implementiert (lieferte ein Option<Ordering>), aber nicht Ord (lieferte ein nicht-optionales Ordering).

Folge: viele generische Funktionen, die Ord verlangen, funktionieren nicht direkt mit Floats:

Rust Sortierungs-Problem
let mut werte: Vec<f64> = vec![3.0, 1.0, 2.0];
// werte.sort();   // Fehler — sort verlangt Ord

werte.sort_by(|a, b| a.partial_cmp(b).unwrap());
println!("{:?}", werte);     // [1.0, 2.0, 3.0]

partial_cmp gibt Option<Ordering> zurück; wenn beide Werte vergleichbar sind (also keiner NaN), kommt Some(Ordering) raus.

Mit Rust 1.50+ gibt es bequemere Methoden:

Rust total_cmp
let mut werte: Vec<f64> = vec![3.0, f64::NAN, 1.0];
werte.sort_by(|a, b| a.total_cmp(b));
// total_cmp definiert eine totale Ordnung — auch mit NaN

total_cmp sortiert auch mit NaN-Werten (per Konvention werden positive NaN am Ende einsortiert).

Float-Gleichheit — die zentrale Falle

== auf Floats ist legal, aber selten richtig:

Rust Klassisches Float-Problem
fn main() {
    let a = 0.1_f64 + 0.2_f64;
    let b = 0.3_f64;
    println!("{a} == {b}: {}", a == b);
    // 0.30000000000000004 == 0.3: false
}

Floats sind binäre Annäherungen. 0.1 lässt sich nicht exakt als binäre Fraktion darstellen, also tritt bei jeder Operation ein winziger Fehler auf.

Sichere Gleichheit mit Epsilon

Rust Epsilon-Vergleich
fn nahe_gleich(a: f64, b: f64, epsilon: f64) -> bool {
    (a - b).abs() < epsilon
}

fn main() {
    let a = 0.1 + 0.2;
    println!("{}", nahe_gleich(a, 0.3, 1e-10));   // true
}

Für robustere Vergleiche existieren Crates wie approx mit relativen und absoluten Epsilon-Vergleichen. Für die meisten Anwendungsfälle reicht aber ein einfaches < 1e-10.

f64::EPSILON ist die kleinste positive Zahl, sodass 1.0 + EPSILON != 1.0 — also etwa 2.22 · 10⁻¹⁶. Sinnvoll als Untergrenze für relative Vergleiche, aber nicht als „magic number" für jeden Float-Vergleich.

Wichtige Methoden auf f64

Rust Mathematische Methoden
let x = 2.0_f64;

x.sqrt();         // 1.4142135623730951
x.powi(10);       // 1024.0 — Potenz mit Integer-Exponent
x.powf(2.5);      // 5.656854249492381 — Potenz mit Float-Exponent
x.exp();          // e^x = 7.389056098930650
x.ln();           // natürlicher Logarithmus
x.log10();        // 0.301029995663981
x.log2();         // 1.0
x.sin(); x.cos(); x.tan();   // Trigonometrie (Bogenmaß)
x.asin(); x.acos(); x.atan();
(1.0_f64).atan2(1.0);    // 0.785... = π/4

x.floor();        // Aufrunden zur niedrigeren ganzen Zahl
x.ceil();         // Aufrunden zur höheren
x.round();        // Klassisches Runden
x.trunc();        // In Richtung 0 abschneiden
x.fract();        // Nur Nachkommateil

(-3.5_f64).abs();         // 3.5
(3.7_f64).max(5.2);        // 5.2
(3.7_f64).min(5.2);        // 3.7
Rust Konstanten
std::f64::consts::PI;            // 3.141592653589793
std::f64::consts::E;             // 2.718281828459045
std::f64::consts::TAU;           // 2π = 6.283...
std::f64::consts::SQRT_2;        // √2 = 1.414...
std::f64::consts::LN_2;          // ln(2) = 0.693...

f64::MAX;        // 1.7976931348623157e308
f64::MIN;        // -1.7976931348623157e308
f64::EPSILON;     // 2.220446049250313e-16
f64::INFINITY;
f64::NAN;

Float ↔ Integer

Konvertierungen zwischen Float und Integer brauchen as (oder die sichereren Konvertierungs-Traits):

Rust Float zu Integer
let f = 3.7_f64;
let i = f as i32;             // 3 — schneidet Nachkommateil ab (Richtung 0)

let neg = -3.7_f64;
let i_neg = neg as i32;       // -3 — Richtung 0

let zu_gross = 1.0e20_f64;
let i_max = zu_gross as i32;  // i32::MAX = 2_147_483_647 (saturiert!)

let nan = f64::NAN;
let i_nan = nan as i32;       // 0 — NaN wird zu 0

as von Float zu Integer hat seit Rust 1.45 deterministisches Saturieren — Werte außerhalb des Integer-Bereichs werden an die Limits angedockt, NaN wird zu 0. Vorher war es Undefined Behavior, was zu subtilen Bugs führte.

Integer-zu-Float ist meist verlustfrei für kleine Werte:

Rust Integer zu Float
let i = 1000_i32;
let f = i as f64;             // 1000.0, verlustfrei
let f2 = i as f32;            // 1000.0, verlustfrei

let gross = i64::MAX;
let f3 = gross as f64;        // verlustig — i64 hat 63 Bit Mantisse, f64 nur 52

Praxis: Wo Floats wirklich gebraucht werden

Geo-Distanz mit Haversine-Formel

Zwei Geo-Koordinaten in Kilometern messen — Standard-Pattern in jeder Karten- oder Navigation-Anwendung.

Rust Distanz zwischen GPS-Punkten
const ERDRADIUS_KM: f64 = 6371.0;

fn haversine(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
    let d_lat = (lat2 - lat1).to_radians();
    let d_lon = (lon2 - lon1).to_radians();
    let lat1 = lat1.to_radians();
    let lat2 = lat2.to_radians();

    let a = (d_lat / 2.0).sin().powi(2)
        + lat1.cos() * lat2.cos() * (d_lon / 2.0).sin().powi(2);
    let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());

    ERDRADIUS_KM * c
}

fn main() {
    // Berlin -> München
    let km = haversine(52.5200, 13.4050, 48.1351, 11.5820);
    println!("{km:.1} km");      // ~504 km
}

f64 hier zwingend — f32 würde bei Werten nahe der Erd-Skala Präzision verlieren.

3D-Vektor-Operationen

Klassisches Pattern in Spiele-Engines, Physik-Simulationen und Computer-Grafik:

Rust 3D-Vektor
#[derive(Clone, Copy)]
struct Vec3 { x: f32, y: f32, z: f32 }

impl Vec3 {
    fn laenge(self) -> f32 {
        (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
    }

    fn normiert(self) -> Vec3 {
        let l = self.laenge();
        Vec3 { x: self.x / l, y: self.y / l, z: self.z / l }
    }

    fn dot(self, other: Vec3) -> f32 {
        self.x * other.x + self.y * other.y + self.z * other.z
    }
}

f32 ist hier idiomatisch — Grafik-APIs (Vulkan, Metal, wgpu) arbeiten überwiegend in f32, und für 3D-Koordinaten ist die Präzision ausreichend.

Streaming-Statistik

Online-Mittelwert und Varianz mit Welfords Algorithmus — nützlich für Monitoring, Sensor-Daten, ML-Feature-Aggregation:

Rust Welford-Algorithmus
struct Stats {
    count: u64,
    mean: f64,
    m2: f64,
}

impl Stats {
    fn new() -> Stats {
        Stats { count: 0, mean: 0.0, m2: 0.0 }
    }

    fn beobachte(&mut self, x: f64) {
        self.count += 1;
        let delta = x - self.mean;
        self.mean += delta / self.count as f64;
        let delta2 = x - self.mean;
        self.m2 += delta * delta2;
    }

    fn varianz(&self) -> f64 {
        if self.count < 2 { 0.0 } else { self.m2 / (self.count - 1) as f64 }
    }
}

Vorteil gegenüber dem naiven „Summe und Summe-der-Quadrate"-Ansatz: numerisch stabil auch bei Millionen von Werten. Float-Präzisionsverluste sammeln sich nicht akkumulativ.

Häufige Stolperfallen

== auf Floats ist (fast) immer falsch.

Außer in sehr spezifischen Fällen (Vergleich mit 0.0, Vergleich gegen einen anderen, gerade berechneten Wert ohne Zwischenoperationen) führt == zu Bugs. Lösung: Epsilon-Vergleiche mit (a - b).abs() < epsilon oder die approx-Crate für robustere Patterns.

NaN != NaN — auch nicht mit sich selbst.

IEEE-754 spezifiziert das. Resultat in Rust: f64::NAN == f64::NAN ist false. Tests gegen NaN sollten x.is_nan() nutzen, nicht x == f64::NAN. Auch in HashMap-Schlüsseln sind Floats problematisch (kein Eq), weshalb es die Crate ordered-float gibt.

Float-Sortierung verlangt partial_cmp oder total_cmp.

vec.sort() funktioniert nicht direkt. Lösung: vec.sort_by(|a, b| a.total_cmp(b)) für seit Rust 1.62 verfügbare totale Ordnung mit NaN-Behandlung, oder sort_by(|a, b| a.partial_cmp(b).unwrap()) mit Panic-Risiko bei NaN.

0.1 + 0.2 != 0.3 ist kein Rust-Bug.

Das gleiche Verhalten findest du in JavaScript, Python, C, Java. Es ist eine IEEE-754-Eigenschaft. Wenn dezimale Präzision (Geld!) wichtig ist, nimm ein Decimal-Crate wie rust_decimal — oder rechne in „Cent" mit Integer.

powi ist schneller als powf.

Für ganzzahlige Exponenten: x.powi(3) statt x.powf(3.0). Erster nutzt iterative Multiplikation, zweiter eine teurere Expo-Logarithmus-Formel. Faktor 2–5 Performance-Unterschied bei Hot-Path-Berechnungen.

Float-zu-Integer kann saturieren — das war nicht immer so.

Vor Rust 1.45 war (f64::INFINITY as i32) undefined behavior. Seit 1.45 ist das deterministisch: INFINITYi32::MAX, -INFINITYi32::MIN, NaN0. Wer auf älteren Compilern unterwegs ist, sollte das im Hinterkopf behalten — heutiger Code ist sicher.

f32::EPSILON ist nicht „der kleinste Float“.

EPSILON ist die Lücke zwischen 1.0 und dem nächsten darstellbaren Float — also etwa 1.19 · 10⁻⁷ für f32. Der kleinste positive Float ist f32::MIN_POSITIVE (≈ 1.18 · 10⁻³⁸), der allerkleinste subnormale Float ist f32::MIN_POSITIVE.next_down(). Wichtig zu wissen, wenn man absolute Vergleiche kalibriert.

Floats in HashMaps oder als Eq-Typ sind verboten.

Da f64 nicht Eq implementiert (nur PartialEq), kann man HashMap<f64, V> nicht direkt nutzen. Die Crate ordered-float liefert einen Ord-fähigen Wrapper. Für absolute Werte als Schlüssel: lieber in Integer-Repräsentation umwandeln (z. B. „Cent" statt „Euro").

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Primitive Datentypen

Zur Übersicht