Math

#308 Aug 2026

308. sin_cos — One Angle, Both Halves, No Swap Bug

Every rotation, polar conversion, and circle you draw needs sin and cos of the same angle — and every codebase has one spot where x got the sin and y got the cos.

Two separate calls means two chances to put the results in the wrong slot, and the compiler can’t help — both are f64. sin_cos returns the pair as a tuple, so the destructure names them once and the math below reads like math:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let theta = 30.0_f64.to_radians(); // bite 307
let r = 2.0;

// two calls, two chances to swap them
let x = r * theta.cos();
let y = r * theta.sin();

// one call, one destructure
let (sin, cos) = theta.sin_cos();
let (x, y) = (r * cos, r * sin);
assert!((y - 1.0).abs() < 1e-15); // r·sin 30° = 1

One thing to burn in: the tuple is (sin, cos) — sin first, matching the method name, even though (x, y) order would put cos first. Destructure as let (s, c) = … and the names keep you honest.

The classic customer is a 2D rotation, where the same sin and cos appear four times:

1
2
3
4
5
6
7
fn rotate((x, y): (f64, f64), theta: f64) -> (f64, f64) {
    let (s, c) = theta.sin_cos();
    (x * c - y * s, x * s + y * c)
}

let (x, y) = rotate((1.0, 0.0), 90.0_f64.to_radians());
assert!(x.abs() < 1e-15 && (y - 1.0).abs() < 1e-15);

Without sin_cos that function either calls sin and cos twice each, or you write the two temporaries by hand — which is exactly where the swap sneaks in.

Don’t reach for it as a speed hack: std is free to implement it as the two calls you’d have written, and often does. The win is intent — one angle in, both halves out, and bite 300’s atan2 will happily turn the pair back into the angle:

1
2
let (s, c) = 1.0_f64.sin_cos();
assert!((s.atan2(c) - 1.0).abs() < 1e-15);

If a sin and a cos of the same angle sit within three lines of each other, they want to be one sin_cos.

#307 Aug 2026

307. to_degrees / to_radians — Stop Hand-Typing 180.0 / PI

sin wants radians, your users want degrees, and somewhere in your codebase a hand-typed * 180.0 / PI is waiting to be fat-fingered. The conversion has been a method on floats since Rust 1.0.

Every trig function in std speaks radians. Every protractor, compass heading, and UI slider speaks degrees. So this line gets written over and over:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use std::f64::consts::PI;

let heading: f64 = 90.0;

// hand-rolled
let rad = heading * PI / 180.0;

// stdlib
let rad = heading.to_radians();
assert!((rad - PI / 2.0).abs() < 1e-15);

Same in the other direction — bite 300’s atan2 hands you radians, and to_degrees turns them back into something a human can read:

1
2
let angle = 1.0_f64.atan2(1.0); // 45° as radians
assert!((angle.to_degrees() - 45.0).abs() < 1e-12);

The readability win is obvious: no magic constant to mistype (57.2958, 0.0174533, and their truncated cousins show up in real codebases), and the intent is in the method name instead of a comment.

There’s a correctness win too. to_degrees multiplies by one precomputed constant — 180.0 / PI rounded once at full precision. The hand-rolled version does two operations, and the intermediate x * 180.0 can overflow even when the final result is representable:

1
2
3
4
let big = f64::MAX / 100.0;

assert!((big * 180.0 / PI).is_infinite()); // x * 180 blew up
assert!(big.to_degrees().is_finite());     // one multiply, no overflow

The everyday use is keeping unit confusion out of trig calls — convert at the boundary, and everything inside stays in radians:

1
2
let slope = 30.0_f64.to_radians().sin();
assert!((slope - 0.5).abs() < 1e-15);

If a PI / 180.0 appears anywhere outside a constants module, it wants to be a to_radians.

#306 Aug 2026

306. powi — Integer Powers Without the powf Detour

x * x * x doesn’t scale, and powf(3.0) routes an integer exponent through the full floating-point pow machinery. powi is the method built for exactly this case.

Three ways to cube a float:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let x: f64 = 1.05;

// the hand-rolled chain
let cubed = x * x * x;

// powf: full transcendental pow
let cubed = x.powf(3.0);

// powi: integer exponent, fast path
let cubed = x.powi(3);

The chain stops being readable past the third power and can’t handle a runtime exponent at all. powf works, but it’s the general routine — built to handle any real exponent, when all you have is a small integer.

powi takes an i32 and computes the power by repeated multiplication. Negative exponents give you the reciprocal, no 1.0 / dance:

1
assert_eq!(2.0_f64.powi(-3), 0.125); // 1 / 2³

The quieter win is negative bases. As bite 302 showed with cbrt, powf on a negative base is one fractional exponent away from NaN. With powi the exponent is an integer by construction, so the sign question always has an answer:

1
2
assert_eq!((-3.0_f64).powi(2), 9.0);
assert_eq!((-3.0_f64).powi(3), -27.0);

Compound growth is the everyday use case — the exponent is a year count, not a real number:

1
2
3
4
5
6
let balance: f64 = 1_000.0;
let rate: f64 = 0.05;
let years: i32 = 10;

let future = balance * (1.0 + rate).powi(years);
assert!((future - 1_628.89).abs() < 0.01);

If the exponent in your powf call ends in .0, it wants to be a powi.

#298 Aug 2026

298. copysign — Stamp One Number's Sign Onto Another, Sign Bit and All

Bite 297 showed signum reading the sign bit. copysign is the other half: it writes one — take this magnitude, give it that number’s sign, one call.

The branch

You have a fixed magnitude and a direction living in another variable — a step size, a force, a correction — and out comes the same conditional every time:

1
2
3
let delta: f64 = -3.2;

let step = if delta < 0.0 { -0.25 } else { 0.25 };

One call

1
2
3
4
5
let step = 0.25_f64.copysign(delta); // -0.25

assert_eq!(3.5_f64.copysign(-1.0), -3.5);
assert_eq!((-3.5_f64).copysign(1.0), 3.5);
assert_eq!(7.0_f64.copysign(2.0), 7.0);

The receiver contributes the magnitude, the argument contributes the sign. Yesterday’s “step toward the target” loop works for floats too — magnitude from you, direction from the gap:

1
2
3
4
5
6
7
let mut pos = 1.0_f64;
let target = 2.0;

while (target - pos).abs() >= 0.25 {
    pos += 0.25_f64.copysign(target - pos);
}
assert_eq!(pos, 2.0); // 0.25 divides 1.0 exactly

It really is the sign bit

Like f64::signum, copysign works on the raw sign bit — which is exactly what makes it more honest than the if:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// the branch calls -0.0 "positive"...
let x = -0.0_f64;
let s = if x < 0.0 { -1.0 } else { 1.0 };
assert_eq!(s, 1.0);

// ...copysign actually looks at the bit
assert!(1.0_f64.copysign(-0.0).is_sign_negative());

// and a NaN's magnitude stays NaN, sign still applied
assert!(f64::NAN.copysign(-1.0).is_sign_negative());

Anywhere you’re bolting a direction onto a magnitude — drag opposing velocity, rounding away from zero, mirroring a coordinate — copysign is the branch-free, -0.0-correct way to say it.

#297 Aug 2026

297. signum — The -1 / 0 / +1 You Keep Building With an If-Else Ladder

Every “which direction?” check ends up as the same three-branch ladder: greater, less, equal. signum is that ladder as one method call — with a float twist you need to know about.

The ladder

You want the direction of a difference — for a comparator, a game step, a sort key:

1
2
3
4
5
6
7
8
9
let delta: i32 = -7;

let dir = if delta > 0 {
    1
} else if delta < 0 {
    -1
} else {
    0
};

One call

1
2
3
4
5
let dir = delta.signum(); // -1

assert_eq!(42i32.signum(), 1);
assert_eq!(0i32.signum(), 0);
assert_eq!((-3i64).signum(), -1);

It shines whenever you need to move one step toward a target, whatever the distance:

1
2
3
4
5
6
7
let mut pos: i32 = 3;
let target = 7;

while pos != target {
    pos += (target - pos).signum(); // steps ±1, stops exactly
}
assert_eq!(pos, target);

No overshoot, no separate branches for “target is behind me” — the sign does the steering.

The float trap

f64::signum is not the same function. It reports the sign bit, so zero is never 0.0:

1
2
3
assert_eq!(0.0_f64.signum(), 1.0);    // not 0.0!
assert_eq!((-0.0_f64).signum(), -1.0);
assert!(f64::NAN.signum().is_nan());

If you want the integer-style three-way answer for floats, be explicit about zero:

1
2
3
let x = 0.0_f64;
let dir = if x == 0.0 { 0.0 } else { x.signum() };
assert_eq!(dir, 0.0);

Reach for signum on integers without a second thought; on floats, remember it answers “which sign bit?” — not “is this positive, negative, or zero?”.

#245 Jul 2026

245. rem_euclid — The Modulo That Never Goes Negative

-1 % 5 is -1 in Rust, not 4% keeps the sign of the dividend. When you want a true wrap-around index or clock value, (-1i32).rem_euclid(5) gives you the 4 you actually meant.

Why % bites you

Rust’s % is a remainder, not a mathematical modulo. Its result takes the sign of the left operand:

1
2
assert_eq!(-1 % 5, -1);
assert_eq!(-8 % 3, -2);

That’s fine for the positive case, but the moment a negative number sneaks in — a decrementing index, an offset that can go below zero — you get a negative result. Feed that into arr[idx as usize] and you either panic or cast into a giant number.

rem_euclid always lands in [0, n)

1
2
3
assert_eq!((-1i32).rem_euclid(5), 4);
assert_eq!((-8i32).rem_euclid(3), 1);
assert_eq!(7i32.rem_euclid(5), 2);

For a positive divisor, rem_euclid is guaranteed to return a non-negative result strictly less than the divisor — exactly what “wrap around” should mean.

Where it pays off: wrapping an index

Stepping backwards through a ring buffer is the textbook case. With % you’d have to add the length back in by hand to avoid going negative:

1
2
3
4
5
6
7
8
9
let buf = ['a', 'b', 'c', 'd'];
let len = buf.len() as i32;

// Move one step left from index 0, wrapping to the end
let mut idx = 0i32;
idx = (idx - 1).rem_euclid(len);

assert_eq!(idx, 3);
assert_eq!(buf[idx as usize], 'd');

No + len fixup, no branch on “did it go negative.” The same trick handles clock arithmetic ((hour + delta).rem_euclid(24)) and angle normalization.

div_euclid is its partner: (-8).div_euclid(3) == -3, and the identity a == b * a.div_euclid(b) + a.rem_euclid(b) always holds. Whenever you catch yourself writing ((x % n) + n) % n to force a positive remainder, that’s rem_euclid spelled the hard way.

#215 Jun 2026

215. next_multiple_of — Round Up to a Multiple Without the +m-1 Dance

Padding a length up to the next multiple of 8? The classic (n + 7) / 8 * 8 works right up until it overflows or you fat-finger the constant. next_multiple_of says exactly what you mean.

The hand-rolled version shows up everywhere alignment matters — buffer sizes, page rounding, table padding:

1
2
3
4
// Easy to get subtly wrong, and `n + m - 1` can overflow near the top of the range
fn round_up(n: usize, m: usize) -> usize {
    (n + m - 1) / m * m
}

Every integer type has next_multiple_of: the smallest value >= self that is a multiple of the argument. Already a multiple? It’s returned unchanged.

1
2
3
4
assert_eq!(13u32.next_multiple_of(8), 16); // round up
assert_eq!(16u32.next_multiple_of(8), 16); // already aligned, untouched
assert_eq!(0u32.next_multiple_of(8), 0);
assert_eq!(23u32.next_multiple_of(10), 30);

When the rounded value would overflow, next_multiple_of panics — but checked_next_multiple_of hands you an Option instead, so you stay in control:

1
2
assert_eq!(250u8.checked_next_multiple_of(8), None); // 256 won't fit in u8
assert_eq!(13u8.checked_next_multiple_of(8), Some(16));

One method, the intent on the page, and no off-by-one waiting to bite you.

#132 May 2026

132. abs_diff — Subtract Without Caring Which Side Is Bigger

Subtracting two unsigned integers and the smaller one comes first? Instant panic. a.abs_diff(b) returns the gap as a u* regardless of which side is bigger — no branching, no overflow.

The Problem

Unsigned subtraction in Rust panics in debug and wraps in release the moment the result would go negative. You end up writing the same branch over and over:

1
2
3
4
5
6
fn gap(a: u32, b: u32) -> u32 {
    if a > b { a - b } else { b - a }
}

assert_eq!(gap(10, 3), 7);
assert_eq!(gap(3, 10), 7);

It works, but it’s noise. And the same trick on signed integers has a sneakier bug: i32::MIN.abs_diff(i32::MAX) overflows an i32 — the gap doesn’t fit in the signed range.

After: abs_diff

Every integer type carries an abs_diff method that returns the unsigned gap directly. Signed inputs come back as the matching unsigned type, so the result always fits:

1
2
3
4
5
6
assert_eq!(10u32.abs_diff(3), 7);
assert_eq!(3u32.abs_diff(10), 7);

// Signed → unsigned, no overflow at the extremes
assert_eq!((-5i32).abs_diff(5), 10u32);
assert_eq!(i32::MIN.abs_diff(i32::MAX), u32::MAX);

No if, no checked_sub, no casting through i64 to dodge overflow. One call, one number.

Where It Earns Its Keep

Distance-style calculations are the obvious fit — anywhere “how far apart are these” is the real question and the sign is incidental:

1
2
3
4
5
6
fn manhattan(a: (i32, i32), b: (i32, i32)) -> u32 {
    a.0.abs_diff(b.0) + a.1.abs_diff(b.1)
}

assert_eq!(manhattan((1, 2), (4, 6)), 7);
assert_eq!(manhattan((-3, -3), (3, 3)), 12);

It also cleans up timestamp deltas, where one side is “now” and the other could be in the past or the future:

1
2
3
4
5
let scheduled: u64 = 1_700_000_000;
let actual:    u64 = 1_699_999_995;

let drift = scheduled.abs_diff(actual);
assert_eq!(drift, 5);

Whenever you catch yourself writing if a > b { a - b } else { b - a }, reach for abs_diff instead.

#121 May 2026

121. rem_euclid — The Modulo That Doesn't Go Negative

-1 % 7 in Rust is -1, not 6. That’s a math gotcha lurking in every wraparound index, every clock arithmetic, every “what day of the week” calculation. rem_euclid is the modulo you actually wanted.

Rust’s % operator follows the same rule as C: the sign of the result matches the sign of the dividend. Useful sometimes, surprising the rest of the time:

1
2
3
assert_eq!(-1_i32 % 7, -1);
assert_eq!(-8_i32 % 7, -1);
assert_eq!( 8_i32 % 7,  1);

Try indexing a circular buffer with that and you get a panic the first time you step backwards across zero. The fix is rem_euclid, which always returns a value in [0, |divisor|):

1
2
3
assert_eq!((-1_i32).rem_euclid(7), 6);
assert_eq!((-8_i32).rem_euclid(7), 6);
assert_eq!(( 8_i32).rem_euclid(7), 1);

A real-world shape — wrap an index around a slice in either direction, no if ladder, no manual + len trick:

1
2
3
4
5
6
7
8
9
let days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];

fn day_after(today: i32, delta: i32) -> i32 {
    (today + delta).rem_euclid(7)
}

assert_eq!(days[day_after(0, -1) as usize], "Sun"); // Mon - 1 = Sun
assert_eq!(days[day_after(2,  4) as usize], "Sun"); // Wed + 4 = Sun
assert_eq!(days[day_after(0, -8) as usize], "Sun"); // wraps past week boundary

div_euclid is the partner that pairs with it: a.div_euclid(b) * b + a.rem_euclid(b) == a always holds, even for negative a. Plain / and % only satisfy that identity for non-negative inputs.

1
2
3
let a = -7_i32;
let b =  3_i32;
assert_eq!(a.div_euclid(b) * b + a.rem_euclid(b), a);

Both are available on every signed integer type (and floats), and they’re const. The rule of thumb: if your code can ever see a negative operand and you want the mathematician’s modulo — not the hardware’s — reach for rem_euclid.

#082 Apr 2026

82. isqrt — Integer Square Root Without Floating Point

(n as f64).sqrt() as u64 is the classic hack — and it silently gives the wrong answer for large values. Rust 1.84 stabilized isqrt on every integer type: exact, float-free, no precision traps.

The floating-point trap

Converting to f64, calling .sqrt(), and casting back is the go-to pattern. It looks fine. It isn’t.

1
2
3
4
let n: u64 = 10_000_000_000_000_000_000;
let bad = (n as f64).sqrt() as u64;
// bad == 3_162_277_660, but floor(sqrt(n)) is 3_162_277_660 — or is it?
// For many large u64 values, the f64 round-trip is off by 1.

f64 only has 53 bits of mantissa, so for u64 values above 2^53 the conversion loses precision before you even take the square root.

The fix: isqrt

1
2
3
4
let n: u64 = 10_000_000_000_000_000_000;
let root = n.isqrt();
assert_eq!(root * root <= n, true);
assert_eq!((root + 1).checked_mul(root + 1).map_or(true, |sq| sq > n), true);

It’s defined on every integer type — u8, u16, u32, u64, u128, usize, and their signed counterparts — and always returns the exact floor of the square root. No casts, no rounding, no surprises.

Signed integers too

1
2
3
4
5
6
let x: i32 = 42;
assert_eq!(x.isqrt(), 6); // 6*6 = 36, 7*7 = 49

// Negative values would panic, so check first:
let maybe_neg: i32 = -4;
assert_eq!(maybe_neg.checked_isqrt(), None);

Use checked_isqrt on signed types when the input might be negative — it returns Option<T> instead of panicking.

When you’d reach for it

Perfect-square checks, tight loops over divisors, hash table sizing, geometry on integer grids — anywhere you were reaching for f64::sqrt purely to round down, reach for isqrt instead. It’s faster, exact, and one character shorter.

#078 Apr 2026

78. div_ceil — Divide and Round Up Without the Overflow Bug

Need to split items into fixed-size pages or chunks? The classic (n + size - 1) / size trick silently overflows. div_ceil does it correctly.

The classic footgun

Paging, chunking, allocating — any time you divide and need to round up, this pattern shows up:

1
2
3
fn pages_needed(total: u64, per_page: u64) -> u64 {
    (total + per_page - 1) / per_page // ⚠️ overflows when total is large
}

It works until total + per_page - 1 wraps around. With u64::MAX items and a page size of 10, you get a wrong answer instead of a panic or correct result.

The fix: div_ceil

Stabilized in Rust 1.73, div_ceil handles the rounding without intermediate overflow:

1
2
3
fn pages_needed(total: u64, per_page: u64) -> u64 {
    total.div_ceil(per_page)
}

One method call, no overflow risk, intent crystal clear.

Real-world examples

Allocating pixel rows for a tiled renderer:

1
2
3
4
let image_height: u32 = 1080;
let tile_size: u32 = 64;
let tile_rows = image_height.div_ceil(tile_size);
assert_eq!(tile_rows, 17); // 16 full tiles + 1 partial

Splitting work across threads:

1
2
3
4
let items: usize = 1000;
let threads: usize = 6;
let chunk_size = items.div_ceil(threads);
assert_eq!(chunk_size, 167); // each thread handles at most 167 items

It works on all unsigned integers

div_ceil is available on u8, u16, u32, u64, u128, and usize. Signed integers also have it (since Rust 1.73), but watch out — it rounds toward positive infinity, which for negative dividends means rounding away from zero.

1
2
let signed: i32 = -7;
assert_eq!(signed.div_ceil(2), -3); // rounds toward +∞, not toward 0

Next time you reach for (a + b - 1) / b, stop — div_ceil already exists and it won’t betray you at the boundaries.

#068 Apr 2026

68. f64::next_up — Walk the Floating Point Number Line

Ever wondered what the next representable floating point number after 1.0 is? Since Rust 1.86, f64::next_up and f64::next_down let you step through the number line one float at a time.

The problem

Floating point numbers aren’t evenly spaced — the gap between representable values grows as the magnitude increases. Before next_up / next_down, figuring out the next neighbor required bit-level manipulation of the IEEE 754 representation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fn main() {
    // The hard way (before 1.86): manually decode bits
    let x: f64 = 1.0;
    let bits = x.to_bits();
    let next_bits = bits + 1;
    let next = f64::from_bits(next_bits);

    assert!(next > x);
    assert_eq!(next, 1.0000000000000002);
}

Error-prone, unreadable, and doesn’t handle edge cases like negative numbers, zero, or special values.

The clean way

next_up returns the smallest f64 greater than self. next_down returns the largest f64 less than self:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
fn main() {
    let x: f64 = 1.0;

    let up = x.next_up();
    let down = x.next_down();

    assert!(up > x);
    assert!(down < x);
    assert_eq!(up, 1.0000000000000002);
    assert_eq!(down, 0.9999999999999998);

    // There's no float between x and its neighbors
    assert_eq!(up.next_down(), x);
    assert_eq!(down.next_up(), x);
}

They handle all the edge cases you’d rather not think about — negative numbers, subnormals, and infinity:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
fn main() {
    // Works across zero
    assert_eq!(0.0_f64.next_up(), 5e-324);   // smallest positive f64
    assert_eq!(0.0_f64.next_down(), -5e-324); // largest negative f64

    // Infinity is the boundary
    assert_eq!(f64::MAX.next_up(), f64::INFINITY);
    assert_eq!(f64::INFINITY.next_up(), f64::INFINITY);

    // NaN stays NaN
    assert!(f64::NAN.next_up().is_nan());
}

Practical use: precision-aware comparisons

The gap between adjacent floats is called an ULP (unit in the last place). next_up lets you build tolerance-aware comparisons without guessing at epsilon values:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
fn almost_equal(a: f64, b: f64, max_ulps: u32) -> bool {
    if a == b { return true; }

    let mut current = a;
    for _ in 0..max_ulps {
        current = if a < b { current.next_up() } else { current.next_down() };
        if current == b { return true; }
    }
    false
}

fn main() {
    let a = 0.1 + 0.2;
    let b = 0.3;

    // They're not equal...
    assert_ne!(a, b);

    // ...but they're within 1 ULP of each other
    assert!(almost_equal(a, b, 1));
}

Also available on f32 with the same API. These methods are const fn, so you can use them in const contexts too.

#057 Apr 2026

57. New Math Constants — GOLDEN_RATIO and EULER_GAMMA in std

Tired of defining your own golden ratio or Euler-Mascheroni constant? As of Rust 1.94, std ships them out of the box — no more copy-pasting magic numbers.

Before: Roll Your Own

If you needed the golden ratio or the Euler-Mascheroni constant before Rust 1.94, you had to define them yourself:

1
2
const PHI: f64 = 1.618033988749895;
const EULER_GAMMA: f64 = 0.5772156649015329;

This works, but it’s error-prone. One wrong digit and your calculations drift. And every project that needs these ends up with its own slightly-different copy.

After: Just Use std

Rust 1.94 added GOLDEN_RATIO and EULER_GAMMA to the standard consts modules for both f32 and f64:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use std::f64::consts::{GOLDEN_RATIO, EULER_GAMMA};

fn main() {
    // Golden ratio: (1 + √5) / 2
    let phi = GOLDEN_RATIO;
    assert!((phi * phi - phi - 1.0).abs() < 1e-10);

    // Euler-Mascheroni constant
    let gamma = EULER_GAMMA;
    assert!((gamma - 0.5772156649015329).abs() < 1e-10);

    println!("φ = {phi}");
    println!("γ = {gamma}");
}

These sit right alongside the constants you already know — PI, TAU, E, SQRT_2, and friends.

Where You’d Actually Use Them

Golden ratio shows up in algorithm design (Fibonacci heaps, golden-section search), generative art, and UI layout proportions:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
use std::f64::consts::GOLDEN_RATIO;

fn golden_section_dimensions(width: f64) -> (f64, f64) {
    let height = width / GOLDEN_RATIO;
    (width, height)
}

fn main() {
    let (w, h) = golden_section_dimensions(800.0);
    assert!((w / h - GOLDEN_RATIO).abs() < 1e-10);
    println!("Width: {w}, Height: {h:.2}");
}

Euler-Mascheroni constant appears in number theory, harmonic series approximations, and probability:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
use std::f64::consts::EULER_GAMMA;

/// Approximate the N-th harmonic number using the
/// asymptotic expansion: H_n ≈ ln(n) + γ + 1/(2n)
fn harmonic_approx(n: u64) -> f64 {
    let nf = n as f64;
    nf.ln() + EULER_GAMMA + 1.0 / (2.0 * nf)
}

fn main() {
    // Exact H_10 = 1 + 1/2 + 1/3 + ... + 1/10
    let exact: f64 = (1..=10).map(|i| 1.0 / i as f64).sum();
    let approx = harmonic_approx(10);

    println!("Exact H_10:  {exact:.6}");
    println!("Approx H_10: {approx:.6}");
    assert!((exact - approx).abs() < 0.01);
}

The Full Lineup

With these additions, std::f64::consts now includes: PI, TAU, E, SQRT_2, SQRT_3, LN_2, LN_10, LOG2_E, LOG2_10, LOG10_2, LOG10_E, FRAC_1_PI, FRAC_1_SQRT_2, FRAC_1_SQRT_2PI, FRAC_2_PI, FRAC_2_SQRT_PI, FRAC_PI_2, FRAC_PI_3, FRAC_PI_4, FRAC_PI_6, FRAC_PI_8, GOLDEN_RATIO, and EULER_GAMMA. That’s a pretty complete toolkit for numerical work — all with full f64 precision, available at compile time.