Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions library/core/src/primitive_docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1333,10 +1333,12 @@ mod prim_f16 {}
/// such as NaN, +/-Inf, or -0.0 may behave in unexpected ways, but these operations
/// will never cause undefined behavior.
///
/// Because of the unpredictable nature of compiler optimizations, the same inputs may produce
/// different results even within a single program run. **Unsafe code must not rely on any property
/// of the return value for soundness.** However, implementations will generally do their best to
/// pick a reasonable tradeoff between performance and accuracy of the result.
/// Algebraic operations are non-deterministic. This means that two invocations of such an operation
/// with the same inputs may produce different results even within a single program run. No
/// guarantees are made about the results of individual operations, except that they produce *some*
/// valid floating-point value. **Unsafe code must not rely on any property of the return value for
/// soundness.** However, implementations will generally do their best to pick a reasonable tradeoff
/// between performance and accuracy of the result.
///
/// For example:
///
Expand All @@ -1362,6 +1364,21 @@ mod prim_f16 {}
/// x = ((a + b) + c) + d; // As written
/// x = (a + c) + (b + d); // Reordered to shorten critical path and enable vectorization
/// ```
///
/// The following example demonstrates the non-determinism:
///
/// ```
/// # #![allow(unused_assignments)]
/// # let a: f32 = 1.0;
/// # let b: f32 = 2.0;
/// let x1 = a.algebraic_add(b);
/// let x2 = a.algebraic_add(b);
/// assert_eq!(x1.to_bits(), x1.to_bits()); // this is guaranteed
/// # if false {
/// assert_eq!(x1.to_bits(), x2.to_bits()); // but this may fail
/// assert!(!x2.is_nan()); // this may also fail, even if there was no NaN input

@RalfJung RalfJung Aug 4, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also clarified that the NaN here can arise spuriously

View changes since the review

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be useful to hear in more detail how NaNs can arise spuriously. And maybe move this out of the example and to the top-level? Cause that's a pretty significant impact.

Thanks for adding more detail!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be more specific in why this is good to know, consider the following, with the assumption that the inputs are not NaN:

fn pairwise_sum(values: &[f64]) -> f64 {
    let n = values.len();
    if n > 128 {
        let half = n / 2;
        pairwise_sum(&values[0..half])
            + pairwise_sum(&values[half..n])
    } else {
        let mut total: f64 = 0.0;
        for value in values {
            total = total.algebraic_add(*value);
        }
        total
    }
}

My understanding is that normal float addition will never result in NaN. Can this function result in NaNs, given it's all addition and not clear how it could optimize into any other operation?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess inf + -inf is NaN. But assuming all positive numbers, as in the current example.

@RalfJung RalfJung Aug 4, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docs say that no guarantee is made about the return value. That implies no guarantee about absence of NaN.

I don't actually know if anything LLVM does can cause funny NaNs here, but I think if we want to explore providing non-NaN guarantees here then that should be a new issue. Even if LLVM doesn't do this today, I think the LangRef permits them to do it in the future. This PR just clarifies the intent of what we had already written: Unsafe code must not rely on any property of the return value for soundness. In practice, we'd treat it as a bug if the code above produced a NaN as it violates the last sentence ("implementations will generally do their best to pick a reasonable tradeoff between performance and accuracy of the result"), but it would not be a soundness bug.

/// # }
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
mod prim_f32 {}

Expand Down
Loading