Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add erf and erfc to f32 and f64 #89

Closed
ajtribick opened this issue Aug 20, 2022 · 12 comments
Closed

Add erf and erfc to f32 and f64 #89

ajtribick opened this issue Aug 20, 2022 · 12 comments
Labels
ACP-accepted API Change Proposal is accepted (seconded with no objections) api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api

Comments

@ajtribick
Copy link

ajtribick commented Aug 20, 2022

Proposal

Problem statement

The Gauss error function erf occurs relatively frequently in statistics and physics and is part of the C99 standard library. Currently the erf function and the related erfc are only accessible in Rust via external crates (e.g. libm).

Motivation, use-cases

The error function erf on a real number $x$ is defined as:

$$ \mathrm{erf} x = \frac{2}{\sqrt\pi}\int_0^x e^{-t^2} dt $$

Specific cases for erf:

  • erf(±0) returns ±0
  • erf(±∞) returns ±1
  • erf(NaN) returns NaN

The related complementary error function erfc is defined as:

$$ \mathrm{erfc} x = 1 - \mathrm{erf} x $$

Specific cases for erfc:

  • erfc(∞) returns 0
  • erfc(-∞) returns 2
  • erfc(NaN) returns NaN

These functions cannot be simply expressed in terms of the other Rust std library floating-point functions. The function is available in the standard libraries of C since C99 and Python since Python 3.2.

The primary use case for erf is its relation to the cumulative distribution function $\Phi$ of the normal (Gaussian) distribution, which occurs frequently in statistics. The relationship, expressed in terms of erf on f64 is:

fn phi(x: f64) -> f64 {
    0.5 * (1.0 + (x * f64::consts::FRAC_1_SQRT_2).erf())
}

or in terms of erfc as:

fn phi(x: f64) -> f64 {
    0.5 * (-x * f64::consts::FRAC_1_SQRT_2).erfc()
}

This link to the normal distribution results in erf (and functions derived from erf) occurring fairly often in mathematical/statistical code. Together with the ongoing proposal for implementing the gamma function (rust-lang/rfcs#864), this brings Rust's standard library closer to implementing the full range of mathematical functions in C99.

Solution sketches

For C99-compatible implementations of the C standard library, the process would involve exposing the library functions erf and erfc (for f64), and erff and erfcf (for f32).

Otherwise there exists a Rust implementation in the libm crate, which would provide a basis in case of any future plans to move the floating point mathematical functions into core.

An alternative approach might be to stabilise the cumulative normal distribution function $\Phi$ directly, though this would require additional processing of the C standard library output and possibly be less familiar than having erf available directly given its inclusion in the standard libraries of other languages.

Links and related work

What happens now?

This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals in its weekly meeting. You should receive feedback within a week or two.

@ajtribick ajtribick added api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api labels Aug 20, 2022
@thomcc
Copy link
Member

thomcc commented Aug 22, 2022

So this does come up a fair bit in numeric code, is already in our libm crate, and is in most/all system libms. I think the main reason against it is that it's still comparatively more niche than most of the functions we currently expose on f32/f64.

@ajtribick
Copy link
Author

While erf is definitely less familiar than the trigonometric functions, I'm not sure it's substantially more niche than the majority of the hyperbolic functions (tanh is a convenient smooth step function so probably gets more usage). I'd agree that going much beyond the list of special functions available in C risks bloating the API (in my opinion the C++17 Mathematical Special Functions are sufficiently niche that requiring an external crate doesn't feel too unreasonable) but I think erf/erfc are sufficiently useful to be worth considering.

@thomcc
Copy link
Member

thomcc commented Aug 30, 2022

Yeah, my gut feeling would be that erf{,c} are more useful than the hyperbolic and inverse hyperbolic trig functions we provide.... I suspect those were added because they appear in the set of operations recommended by IEEE754, or in some other library, not because they're that useful1.

Anyway, I'm a bit torn here, and ended up writing too much). Here's why I kinda think we shouldn't:

The big argument against these applies to basically any new special numeric function: it increases the number of functions that Rust code is expected to provide on numeric types. This has (IMO) a semi-large impact, applying both to code in the ecosystem and in the stdlib. For example, if we add this, it would be reasonable for users to want it to also be supported on:

  1. Types already in the stdlib (in an unstable form) that imitate the numeric API, like std::simd::Simd<$f32_or_f64, N>. (Note that these already provide most of the special functions, although they're currently implemented quite naïvely).
  2. Numeric types we could (in principal) add in the future like f16, f128, ...
  3. Numeric types implemented in user crates, things like Complex<$float_type> (depending on domain), or BigFloat (plausibly)...
  4. Numeric traits (such as some of the ones provided by num_traits, or from other libraries).
  5. ... and so on.

IMO this doesn't mean that we can't add new functions to the float types (the cost of this for one or two functions is not unthinkable), just that we are justified being fairly picky about it.


So, do erf{,c} make the cut? I don't know. I have pulled in libm for erfc in order to compute a CDF, and I know some tricks you can do with erf in audiovisual code (although I've never trusted the performance enough to use it there), but this sort of thing that's fairly obscure, and seems reasonable to punt to a library on crates.io2...

Overall, I'd probably lean towards no (even though I've wanted them before). They can be easily provided by a crate, and I do think they're pretty niche (even if I do find them more useful than, say, acosh).

An interesting metric might be "how many people depend on libm just for these functions?" I don't know how to answer something like this easily, but if it's a large percentage that's a pretty strong indication that they'd be more useful than I'm giving them credit.

CC @workingjubilee (who cares about numbers too and may have stronger feelings), and @cuviper (who maintains num_traits and related code).

Footnotes

  1. TBH, we've had issues with the design of our float APIs that imply they got less consideration than we'd put in now (fairly understandably).

  2. While the libm crate may not offer the performance of system math libraries (in theory, anyway), there's no reason something on crates.io cannot call out to system math libraries.

@cuviper
Copy link
Member

cuviper commented Aug 30, 2022

An interesting metric might be "how many people depend on libm just for these functions?" I don't know how to answer something like this easily, but if it's a large percentage that's a pretty strong indication that they'd be more useful than I'm giving them credit.

I assume std would use C's libm -- is that qualitatively better than the Rust libm implementation? (in accuracy, performance, or perhaps both?) This can weigh whether we're providing value over just using the libm crate.

CC [...] @cuviper (who maintains num_traits and related code).

I don't think this should be a blocker for std, but it would be difficult to add this to trait Float without a breaking change. We would need a generically-written default implementation, and there's no simple formula for it. Maybe one of the looser approximations would be good enough to start though.

@ajtribick
Copy link
Author

  1. Numeric types implemented in user crates, things like Complex<$float_type> (depending on domain), or BigFloat (plausibly)...

This is actually my secondary motivation for raising this. The decision on whether or not Rust will aim to match the scope of the C99 mathematics library would factor into my decision on when to bump the version number of my custom floating point crate twofloat to version 1.0. From my perspective, providing an erf implementation wouldn't be too onerous but I appreciate that providing an equivalent for complex numbers would be rather more involved.

@ajtribick
Copy link
Author

ajtribick commented Oct 19, 2022

@thomcc - the issue template here says "Once this issue is filed the libs-api team will review open proposals in its weekly meeting." but I see from the Zulip logs that ACPs aren't actually included in the meetings.

In this case, what is the process for advancing here, either to proceed with an implementation or (as I guess is more realistic given the sentiments in this thread) get some kind of official "we're not going to do this".

@thomcc
Copy link
Member

thomcc commented Oct 19, 2022

We only just got to the point where we are about to start reviewing these in periodic meetings, sorry. Part of this was blocked on #118

@joshtriplett joshtriplett added the ACP-accepted API Change Proposal is accepted (seconded with no objections) label May 17, 2023
@joshtriplett
Copy link
Member

joshtriplett commented May 17, 2023

Ship it.

Please open a tracking issue and file a PR adding these as unstable methods.

@m-ou-se
Copy link
Member

m-ou-se commented May 17, 2023

Feel free to open a tracking issue for this and open a PR to add it as unstable. Thanks!

@hkBst
Copy link
Member

hkBst commented Jan 26, 2025

This proposal was accepted, but no tracking issue is listed and I could not find one. I briefly considered opening a stub tracking issue, but I am not convinced that would not be counter-productive. Is there anything to do here to preserve the decision or is it prefereable to let the decision expire (as it were) and retake it if it comes up in the future?

@pitaj
Copy link

pitaj commented Jan 26, 2025

Someone just needs to open a tracking issue and contribute an initial implementation.

@tgross35
Copy link

Opened a tracking issue so we have it rust-lang/rust#136321

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ACP-accepted API Change Proposal is accepted (seconded with no objections) api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api
Projects
None yet
Development

No branches or pull requests

8 participants