Math #

Rust provides complete math operations right in the standard library — from basic arithmetic to trigonometric functions, logarithms, and rounding. All mathematical operations are available as methods on numeric types (i32, u64, f64, etc.) without the need to import additional modules. What differentiates Rust from other languages ​​is its explicit handling of overflow: overflowing integer operations in debug mode will panic, and Rust provides four different overflow modes for complete control. This article discusses all the mathematical facilities in the standard library along with the rand crate for random numbers.

Mathematical Constants #

fn main() {
    // Constant for f64
    println!("π  = {:.15}", std::f64::consts::PI);         // 3.141592653589793
    println!("e  = {:.15}", std::f64::consts::E);           // 2.718281828459045
    println!("√2 = {:.15}", std::f64::consts::SQRT_2);      // 1.4142135623730951
    println!("ln2= {:.15}", std::f64::consts::LN_2);        // 0.6931471805599453
    println!("log2(e) = {}", std::f64::consts::LOG2_E);
    println!("log10(e) = {}", std::f64::consts::LOG10_E);
    println!("τ (2π) = {:.6}", std::f64::consts::TAU);      // 6.283185...

    // Numeric type limit
    println!("\n--- Batas Integer ---");
    println!("i8:  {} .. {}", i8::MIN, i8::MAX);            // -128 .. 127
    println!("i32: {} .. {}", i32::MIN, i32::MAX);          // -2147483648 .. 2147483647
    println!("i64: {} .. {}", i64::MIN, i64::MAX);
    println!("u8:  {} .. {}", u8::MIN, u8::MAX);            // 0 .. 255
    println!("u32: {} .. {}", u32::MIN, u32::MAX);          // 0 .. 4294967295

    println!("\n--- Batas Float ---");
    println!("f32 max: {:.2e}", f32::MAX);                   // 3.40e38
    println!("f64 max: {:.2e}", f64::MAX);                   // 1.80e308
    println!("f64 min positif: {:.2e}", f64::MIN_POSITIVE);  // 2.23e-308
    println!("f64 epsilon: {:.2e}", f64::EPSILON);           // 2.22e-16
    println!("NaN: {}", f64::NAN);
    println!("Infinity: {}", f64::INFINITY);
    println!("Neg Infinity: {}", f64::NEG_INFINITY);
}

Integer and Overflow Operations #

Rust provides complete control over integer overflow behavior — no “undefined behavior” like in C:

fn main() {
    let a: i32 = i32::MAX;  // 2147483647

    // ANTI-PATTERN: overflow wraparound in release mode (panic in debug mode)
    // let b = a + 1;  // panic is debugged, wraps is released

    // CORRECT: use methods that are explicit about overflow handling

    // checked_* — returns None if overflow
    println!("{:?}", a.checked_add(1));      // None
    println!("{:?}", a.checked_add(-1));     // Some(2147483646)
    println!("{:?}", 100i32.checked_mul(3)); // Some(300)

    // saturating_* — stops at MIN or MAX limit
    println!("{}", a.saturating_add(1));     // 2147483647 (stays at MAX)
    println!("{}", i32::MIN.saturating_sub(1)); // -2147483648 (fixed at MIN)
    println!("{}", 200u8.saturating_add(100));  // 255 (u8::MAX)

    // wrapping_* — wraparound (arithmetic modular behavior)
    println!("{}", a.wrapping_add(1));       // -2147483648 (wraparound to MIN)
    println!("{}", 255u8.wrapping_add(1));   // 0

    // overflowing_* — return (result, is_overflow)
    let (hasil, overflow) = a.overflowing_add(1);
    println!("hasil: {}, overflow: {}", hasil, overflow);  // -2147483648, true

    // Basic arithmetic
    println!("\n--- Aritmetika Dasar ---");
    println!("7 / 2 = {}", 7 / 2);          // 3 (integer division, truncate)
    println!("7 % 2 = {}", 7 % 2);          // 1 (remainder)
    println!("-7 % 2 = {}", -7 % 2);        // -1 (sign follows numerator)
    println!("7.0 / 2.0 = {}", 7.0_f64 / 2.0); // 3.5
    println!("2_i32.pow(10) = {}", 2_i32.pow(10)); // 1024
    println!("2_u64.pow(32) = {}", 2_u64.pow(32)); // 4294967296
}

Advanced Integer Operations #

fn main() {
    // GCD — Euclidean Algorithm
    fn gcd(a: u64, b: u64) -> u64 {
        if b == 0 { a } else { gcd(b, a % b) }
    }

    // LCM
    fn lcm(a: u64, b: u64) -> u64 {
        a / gcd(a, b) * b
    }

    println!("GCD(48, 18) = {}", gcd(48, 18));   // 6
    println!("LCM(4, 6) = {}", lcm(4, 6));        // 12

    // Bit manipulation
    let n: u8 = 0b1010_1100;  // 172
    println!("n = {:08b} = {}", n, n);
    println!("n << 2 = {:08b}", n << 2);          // left shift 2
    println!("n >> 1 = {:08b}", n >> 1);           // right shift 1
    println!("n & 0x0F = {:08b}", n & 0x0F);       // AND (take lower 4 bits)
    println!("n | 0x0F = {:08b}", n | 0x0F);       // OR
    println!("n ^ 0xFF = {:08b}", n ^ 0xFF);       // XOR
    println!("!n = {:08b}", !n);                    // NOT
    println!("count_ones: {}", n.count_ones());     // number of bits 1
    println!("count_zeros: {}", n.count_zeros());   // number of bits 0
    println!("leading_zeros: {}", n.leading_zeros());
    println!("trailing_zeros: {}", n.trailing_zeros());
    println!("reverse_bits: {:08b}", n.reverse_bits());
    println!("rotate_left(3): {:08b}", n.rotate_left(3));

    // Integer to bytes and vice versa
    let angka: u32 = 0x12345678;
    println!("\nu32 = 0x{:08X}", angka);
    let be = angka.to_be_bytes();   // big-endian
    let le = angka.to_le_bytes();   // little-endian
    println!("big-endian bytes: {:02X?}", be);    // [12, 34, 56, 78]
    println!("little-endian bytes: {:02X?}", le); // [78, 56, 34, 12]

    // From bytes back to integer
    let kembali = u32::from_be_bytes(be);
    println!("kembali: 0x{:08X}", kembali);  // 0x12345678
}

Floating Point Operation #

fn main() {
    let x: f64 = 2.0;

    // Roots and powers
    println!("√2 = {}", x.sqrt());            // 1.4142135...
    println!("∛8 = {}", 8.0_f64.cbrt());      // 2.0
    println!("2^10 = {}", x.powi(10));         // 1024.0 (integer rank)
    println!("2^1.5 = {}", x.powf(1.5));       // 2.828...
    println!("e^2 = {}", x.exp());             // 7,389...
    println!("2^x = {}", x.exp2());            // 4.0

    // Logarithm
    println!("\n--- Logaritma ---");
    println!("ln(e) = {}", std::f64::consts::E.ln());     // 1.0
    println!("ln(2) = {}", 2.0_f64.ln());                 // 0.693...
    println!("log2(8) = {}", 8.0_f64.log2());             // 3.0
    println!("log10(1000) = {}", 1000.0_f64.log10());     // 3.0
    println!("log_5(125) = {}", 125.0_f64.log(5.0));      // 3.0

    // Absolute value, min, max
    println!("\n--- Absolut, Min, Max ---");
    println!("|−5.5| = {}", (-5.5_f64).abs());            // 5.5
    println!("|-5| = {}", (-5_i32).abs());                 // 5
    println!("min(3, 7) = {}", 3.0_f64.min(7.0));         // 3.0
    println!("max(3, 7) = {}", 3.0_f64.max(7.0));         // 7.0
    println!("clamp(15, 0, 10) = {}", 15.0_f64.clamp(0.0, 10.0)); // 10.0
    println!("clamp(-5, 0, 10) = {}", (-5.0_f64).clamp(0.0, 10.0)); // 0.0

    // Rounding
    println!("\n--- Pembulatan ---");
    let f = 3.7_f64;
    println!("floor({}) = {}", f, f.floor());   // 3.0
    println!("ceil({}) = {}", f, f.ceil());      // 4.0
    println!("round({}) = {}", f, f.round());    // 4.0
    println!("trunc({}) = {}", f, f.trunc());    // 3.0 (remove decimals)
    println!("fract({}) = {}", f, f.fract());    // 0.7 (decimal only)

    // Rounding to N decimals
    fn bulat_n_desimal(x: f64, n: u32) -> f64 {
        let faktor = 10f64.powi(n as i32);
        (x * faktor).round() / faktor
    }
    println!("π rounded 4dp: {}", bulat_n_desimal(std::f64::consts::PI, 4)); // 3.1416

    // Check special value
    println!("\n--- Nilai Khusus ---");
    println!("NaN is_nan: {}", f64::NAN.is_nan());         // true
    println!("Inf is_infinite: {}", f64::INFINITY.is_infinite()); // true
    println!("3.14 is_finite: {}", 3.14_f64.is_finite());  // true
    println!("0.0 is_sign_positive: {}", 0.0_f64.is_sign_positive()); // true
    println!("-0.0 is_sign_negative: {}", (-0.0_f64).is_sign_negative()); // true

    // NaN is not equal to itself
    let nan = f64::NAN;
    println!("NaN == NaN: {}", nan == nan);  // false!
    println!("NaN.is_nan(): {}", nan.is_nan());  // true — the correct way
}

Trigonometry #

fn main() {
    use std::f64::consts::PI;

    // Trigonometric functions (arguments in radians)
    println!("--- Trigonometri (radian) ---");
    println!("sin(π/6) = {:.4}", (PI / 6.0).sin());   // 0.5
    println!("cos(π/3) = {:.4}", (PI / 3.0).cos());   // 0.5
    println!("tan(π/4) = {:.4}", (PI / 4.0).tan());   // 1.0

    // Convert degrees ↔ radians
    fn ke_radian(derajat: f64) -> f64 { derajat * PI / 180.0 }
    fn ke_derajat(radian: f64) -> f64 { radian * 180.0 / PI }

    println!("\n--- Trigonometri (derajat) ---");
    println!("sin(30°) = {:.4}", ke_radian(30.0).sin());   // 0.5000
    println!("cos(60°) = {:.4}", ke_radian(60.0).cos());   // 0.5000
    println!("tan(45°) = {:.4}", ke_radian(45.0).tan());   // 1.0000

    // Inverse trigonometry (returns radians)
    println!("\n--- Invers Trigonometri ---");
    println!("asin(0.5) = {:.4}° = 30°", ke_derajat(0.5_f64.asin()));
    println!("acos(0.5) = {:.4}° = 60°", ke_derajat(0.5_f64.acos()));
    println!("atan(1.0) = {:.4}° = 45°", ke_derajat(1.0_f64.atan()));

    // atan2 — angle of vector (y, x), range -π to π
    println!("atan2(1, 1) = {:.4}° = 45°", ke_derajat(1.0_f64.atan2(1.0)));
    println!("atan2(1, -1) = {:.4}° = 135°", ke_derajat(1.0_f64.atan2(-1.0)));

    // Hyperbolic
    println!("\n--- Hiperbolik ---");
    println!("sinh(1) = {:.4}", 1.0_f64.sinh());
    println!("cosh(1) = {:.4}", 1.0_f64.cosh());
    println!("tanh(1) = {:.4}", 1.0_f64.tanh());

    // sin_cos — calculate both at once (more efficient)
    let (sin, cos) = (PI / 4.0).sin_cos();
    println!("\nsin_cos(π/4) = ({:.4}, {:.4})", sin, cos);  // (0.7071, 0.7071)

    // Hypot — hypotenuse length: √(x² + y²)
    println!("hypot(3, 4) = {}", 3.0_f64.hypot(4.0));  // 5.0
}

Numerical Comparison and Sorting #

fn main() {
    // Integer: Ord — can sort directly
    let mut angka = vec![5, 2, 8, 1, 9, 3];
    angka.sort();
    println!("Sorted: {:?}", angka);

    angka.sort_by(|a, b| b.cmp(a));  // descending
    println!("Descending: {:?}", angka);

    // Float: PartialOrd only (NaN not comparable)
    let mut floats = vec![3.14, 1.0, 2.718, 0.5];
    floats.sort_by(|a, b| a.partial_cmp(b).unwrap());
    println!("Floats sorted: {:?}", floats);

    // sort_by_key
    let mut pasangan = vec![(3, "tiga"), (1, "satu"), (2, "dua")];
    pasangan.sort_by_key(|&(k, _)| k);
    println!("By key: {:?}", pasangan);

    // min and max of iterator
    let data = vec![5, 2, 8, 1, 9];
    println!("Min: {:?}", data.iter().min());  // Some(1)
    println!("Max: {:?}", data.iter().max());  // Some(9)

    // sum and product
    let jumlah: i32 = data.iter().sum();
    let produk: i32 = data.iter().product();
    println!("Sum: {}, Product: {}", jumlah, produk);

    // Avg
    let rata: f64 = data.iter().map(|&x| x as f64).sum::<f64>() / data.len() as f64;
    println!("Rata-rata: {:.2}", rata);

    // Safe float comparison (avoid == for float)
    fn kira_sama(a: f64, b: f64, epsilon: f64) -> bool {
        (a - b).abs() < epsilon
    }
    println!("0.1 + 0.2 ≈ 0.3: {}", kira_sama(0.1 + 0.2, 0.3, f64::EPSILON * 10.0));
}

Random Number with rand #

Standard library does not provide random numbers. Use the rand crate:

[dependencies]
rand = "0.8"
use rand::{Rng, thread_rng};
use rand::seq::SliceRandom;
use rand::distributions::{Distribution, Uniform};

fn main() {
    let mut rng = thread_rng();

    // Random integer in range (inclusive)
    let n: i32 = rng.gen_range(1..=100);
    println!("Angka acak 1-100: {}", n);

    // Random float in [0.0, 1.0)
    let f: f64 = rng.gen();
    println!("Float acak: {:.4}", f);

    // Float in range
    let f_range: f64 = rng.gen_range(0.0..=10.0);
    println!("Float 0-10: {:.4}", f_range);

    // Random bool
    let b: bool = rng.gen();
    println!("Bool acak: {}", b);

    // Uniform Distribution (more efficient for many samples from the same range)
    let distribusi = Uniform::from(1..=6);  // 6 sided dice
    println!("Dadu: {:?}", (0..5).map(|_| distribusi.sample(&mut rng)).collect::<Vec<_>>());

    // Select a random element from the slice
    let buah = ["apel", "mangga", "jeruk", "durian", "rambutan"];
    if let Some(pilihan) = buah.choose(&mut rng) {
        println!("Buah acak: {}", pilihan);
    }

    // Select N random elements without repetition
    let pilihan_n: Vec<&&str> = buah.choose_multiple(&mut rng, 3).collect();
    println!("3 buah acak: {:?}", pilihan_n);

    // Shuffle slice
    let mut kartu: Vec<i32> = (1..=10).collect();
    kartu.shuffle(&mut rng);
    println!("Kartu diacak: {:?}", kartu);

    // Deterministic (reproducible) seed — useful for testing
    use rand::SeedableRng;
    use rand::rngs::StdRng;
    let seed: u64 = 42;
    let mut rng_seed = StdRng::seed_from_u64(seed);
    let acak_seed: i32 = rng_seed.gen_range(1..=100);
    println!("Dengan seed {}: {}", seed, acak_seed);  // is always the same
}

Other Math Utilities #

fn main() {
    // Absolute value — available for all signed numeric types
    println!("{}", (-42_i32).abs());     // 42
    println!("{}", (-3.14_f64).abs());   // 3.14

    // signum — sign value (-1, 0, or 1)
    println!("{}", (-5_i32).signum());   // -1
    println!("{}", 0_i32.signum());      // 0
    println!("{}", 5_i32.signum());      // 1

    // Division and modulo with rounding down (floor div)
    // Rust % gives the remainder with a numerator sign
    // For floor division, use div_euclid and rem_euclid
    println!("\n--- Euclidean Division ---");
    println!("-7 % 3 = {}", -7_i32 % 3);              // -1 (Rust)
    println!("-7 rem_euclid 3 = {}", (-7_i32).rem_euclid(3)); // 2 (always positive)
    println!("-7 div_euclid 3 = {}", (-7_i32).div_euclid(3)); // -3

    // Check various numeric properties
    println!("\n--- Properti Numerik ---");
    println!("16 is_power_of_two: {}", 16_u32.is_power_of_two());  // true
    println!("12 is_power_of_two: {}", 12_u32.is_power_of_two());  // false
    println!("next_power_of_two(5): {}", 5_u32.next_power_of_two());  // 8
    println!("leading_zeros(1): {}", 1_u32.leading_zeros());  // 31

    // Convert between numeric types
    let i: i32 = 42;
    let f: f64 = i as f64;        // casting — can be lossy for large values
    let kembali: i32 = f as i32;  // float to int — truncate, not round

    // Safe conversion with TryFrom/TryInto
    use std::convert::TryInto;
    let besar: i64 = 300;
    let kecil: Result<i8, _> = besar.try_into();
    println!("\n300 as i8: {:?}", kecil);  // Err (300 > i8::MAX)

    let kecil2: i64 = 100;
    let ok: Result<i8, _> = kecil2.try_into();
    println!("100 as i8: {:?}", ok);  // Ok(100)
}

Summary #

  • Integer overflow handled explicitly — use checked_* (return None), saturating_* (clamp at boundary), wrapping_* (modular), or overflowing_* (return flag). Don’t leave overflow undefined.
  • Float cannot be ==ed directly0.1 + 0.2 != 0.3 due to IEEE 754 representation. Use comparison with epsilon: (a - b).abs() < f64::EPSILON.
  • f64::NAN != f64::NAN — use .is_nan() instead of == to check NaN.
  • All math functions as methodsx.sqrt(), x.ln(), x.sin(), not free functions. Consistent with Rust style.
  • powi vs powfpowi(n) for integer powers (faster), powf(x) for float powers.
  • atan2(y, x) for vector angles — more robust than atan(y/x) because it handles all quadrants and division by zero.
  • sin_cos() is more efficient than two separate calls — the computer calculates both almost simultaneously internally.
  • rem_euclid for always positive modulo — different from Rust’s % where the sign follows the quantifier.
  • Use Uniform::from(range) for multiple samples — more efficient than gen_range in loops because the distribution is compiled once.
  • Deterministic seed for reproducibilityStdRng::seed_from_u64(seed) produces the same sequence every time. Useful for testing and simulation.


← Previous: IO   Next: Options & Results →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact