rust-lang / rust-lang/libs-team

ACP: Add `flush_to_zero` methods on floats

Open
#877 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

api-change-proposal
Dominant language
Rust
Stars
178
Forks
28
Avg merge
15m
Merged PRs (30d)
1

Description

Proposal

Problem statement

Subnormal floating point numbers are a special class of floats whose presence can cause performance degradation on some hardware configurations. In some near real-time domains such as games or audio signal processing, approximating small numbers as zero is preferred to taking the performance hit, so these numbers are clamped to zero when they're below a threshold.

With no library-provided way to do this, users have to re-implement this functionality in each codebase or reach for more unsafe solutions which are instantly UB.

Motivating examples or use cases

Subnormal numbers can easily be created as a result of exponential decay during an audio fadeout, a filter's feedback loop, or a physics dampening simulation. It's common to snap values below some arbitrary threshold to 0.

This article from 2005 lists some methods of combating denormals/subnormals in audio applications. All of these methods come with their own disadvantages that reduce precision or sacrifice performance/maintainability which must be weighed for the application.

Finally, a commonly recommended solution for C/C++ programs is to modify the floating point environment by setting the Flush To Zero (FTZ) and Denormals Are Zero (DAZ) flags. This is not a portable solution nor IEEE 754-compliant, but more urgently, this is instantly UB in Rust. According to RFC3514, Rust expects the default floating point environment and any observable modification to that state is undefined behavior. As noted in the documentation for setting the x86 status and control register, _mm_setcsr, you are allowed to modify it with inline assembly but you must do the operations in that block before restoring the default mode. There are many examples on Github that violate these terms by setting the register at the beginning and end of a function, or trying to use RAII guards to set and restore state.

The proposed methods of this ACP will not completely eliminate subnormal performance problems, but they do provide a safe and portable way to mitigate the costs.

Solution sketch

I propose adding flush_to_zero and flush_to_positive_zero methods to floats.

flush_to_zero converts subnormals into a 0 with the same sign as the input.

flush_to_positive_zero converts subnormals and -0 to +0, omitting a call to copysign which makes it slightly more efficient when autovectorized. Applications where flushing to zero is acceptable are unlikely to care about preserving signed zero.

impl fN {
    /// Returns `self` if `self` is not `FpCategory::Subnormal`,
    /// otherwise returns `0.0` with the same sign as `self.`
    pub const fn flush_to_zero(self) -> fN {
        match self.classify() {
            FpCategory::Zero | FpCategory::Subnormal => 0.0_fN.copysign(self),
            _ => self,
        }
    }

    /// Returns `self` if `self` is not `FpCategory::Subnormal` or `-0.0`,
    /// otherwise returns `+0.0`.
    pub const fn flush_to_positive_zero(self) -> fN {
        match self.classify() {
            FpCategory::Zero | FpCategory::Subnormal => 0.0_fN,
            _ => self,
        }
    }
}

These functions shouldn't be implemented naively with is_subnormal because that does an extra check on the mantissa bits to determine if the float is FpCategory::Zero. We only care that the exponent bits are 0 which is a characteristic that subnormals and zeros share.

Alternatives

  1. Do nothing. This can be written today by wrapping the already-existing <float>::classify.
  2. I considered naming it flush_subnormals[_to_zero] but that seemed too verbose, along with indicating same_sign or preserve_sign in the function name.

Links and related work

https://rust-lang.github.io/rfcs/3514-float-semantics.html#assumptions-about-floating-point-environment
https://doc.rust-lang.org/1.98.0/core/arch/x86/fn._mm_setcsr.html
https://ldesoras.fr/doc/articles/denormal-en.pdf - Denormal numbers in floating point signal processing applications

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the proposed flush_to_zero and flush_to_positive_zero semantics alongside <float>::classify, is_subnormal, and RFC 3514. Confirm the signed-zero, subnormal, const, portability, and performance requirements; done means reaching agreement on the API design and its implementation and test scope.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
api
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.