-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[libc++][math] Mathematical Special Functions: Hermite Polynomial #89982
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
Conversation
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write If you have received no comments on your PR for a week, you can request a review If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
@mordante I would be thankful for feedback on this implementation. |
You should prefix your PR's title with something like [libc++] or [libc++][math], etc. |
libcxx/include/experimental/math
Outdated
#if _LIBCPP_STD_VER >= 17 | ||
|
||
# include <experimental/__math/hermite.h> | ||
# include <type_traits> // enable_if_t, is_integral_v |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't do reviews usually due to my incompetence but here are my 2 cents.
I believe we do includes unconditionally. We also include the granularized headers e.g. enable_if.h, instead of type_traits? Or is it required by the standard to include it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Zingam you're always free to do reviews.
This comment is correct the include should be unconditional and please use the granularized headers. (Headers mandated by the standard should be in the top-level header. I would be extremely surprised when type_traits is mandated.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
@PaulXiCao I'd recommend to check recent PRs and their comments as a general guide for style and recent best practices.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea! Will do so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for working on this!
I mainly focused on the libc++ specific parts of the implementation.
libcxx/include/experimental/math
Outdated
#if _LIBCPP_STD_VER >= 17 | ||
|
||
# include <experimental/__math/hermite.h> | ||
# include <type_traits> // enable_if_t, is_integral_v |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Zingam you're always free to do reviews.
This comment is correct the include should be unconditional and please use the granularized headers. (Headers mandated by the standard should be in the top-level header. I would be extremely surprised when type_traits is mandated.)
@PaulXiCao It may be useful to add a status page to track the progress and assignments. |
libcxx/include/cmath
Outdated
template <class _Integer> | ||
_LIBCPP_HIDE_FROM_ABI std::enable_if_t<std::is_integral_v<_Integer>, double> hermite(unsigned int __n, _Integer __x) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
template <class _Integer> | |
_LIBCPP_HIDE_FROM_ABI std::enable_if_t<std::is_integral_v<_Integer>, double> hermite(unsigned int __n, _Integer __x) { | |
template <class _Integer, std::enable_if_t<std::is_integral_v<_Integer>, int> = 0> | |
_LIBCPP_HIDE_FROM_ABI double hermite(unsigned int __n, _Integer __x) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not have too much experience with TMP. Could you elaborate on the advantage of your proposed change? Introducing an unused template argument type int
seems strange to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just found other code which also uses the style you suggested (e.g. libcxx/include/__math/abs.h#L37). I will implement the change you suggested, but would still welcome your thoughts on this :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 51f82a50b9007edbb000cfcdcc353dfc28cb8ab2.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's nothing inherently wrong about return type SFINAE. It's just that the enable_if_t<..., int> = 0
version works everywhere. Return type SFINAE doesn't work on constructors, enable_if_t<...>* = nullptr
doesn't work in C++03 etc. This avoids confusion on which version should be used in which case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is std::is_integral
the correct concept to check? I am wondering as it would also allow types that seem unreasonable/indicative of wrong usage, e.g. bool
.
From cppreference.com ("Notes" section): "They only need to be sufficient to ensure that for their argument num of integer type, std::hermite(int_num, num) has the same effect as std::hermite(int_num, static_cast(num))."
From the C++23 Standard
- 28.7.1.3: "...where arguments of integer type are considered to have the same floating-point conversion rank as double. ...". This defines that
std::hermite(n,x)
should also accept integer values for itsx
argument. - Integer types are defined in 6.8.2. and do not contain
bool
.
libcxx/include/cmath
Outdated
return __H_n; | ||
} | ||
|
||
inline _LIBCPP_HIDE_FROM_ABI double hermite(unsigned __n, double __x) { return std::__hermite(__n, __x); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not attached: should the mathematical special functions be in the dylib? I expect this will be quite a bit of code, and it would be nice to be consistent between the special functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cannot anticipate how much code that will be nor what the threshold is when we should be using dynamic libraries. Could we make that switch with a later integration, e.g. once the LOC exceed some threshold?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving code into the dylib has quite a few consequences and may change design considerations, so I don't think we should wait until some later point. Unless we want to make the functions experimental for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I will need to look into that. Can you point me in some direction? Documentation or other sample code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From a discussion with @mordante (please feel free to comment if I misunderstood something):
- This function is quite small and probably does not need to go into the dylib.
- Large code or special template magic could be moved there.
- Concerning the other special math functions: I assume they probably will not contain too much code nor any special template magic.
- We still have the option in the future to move other special math functions into the dylib even though this one might stay in the header.
libcxx/include/cmath
Outdated
if (std::isnan(__x)) | ||
return std::numeric_limits<_Real>::quiet_NaN(); | ||
|
||
_Real __H_0{1}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does the standard make n >= 128
implementation-defined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could only speculate. Not sure where to find information on that...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably somewhere deep within the archives of wg21. I hope @lntue has some insight here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no idea why they put that in the standard. My guess is that some might implement the recurrence relation without multiply-by-2 part, and then scale the final results by 2^n
. For n >= 128
, 2^128
exceeds the range of float
type, so this way of computing hermite polynomials might not support n >= 128
for float
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See https://wg21.link/n1884. TLDR: the function changes so rapidly that it's difficult to even get the correct sign.
Done in 18a320a0f523d1939262d1cfc633da6e5fae4870. |
libcxx/include/cmath
Outdated
return __H_n; | ||
} | ||
|
||
inline _LIBCPP_HIDE_FROM_ABI double hermite(unsigned __n, double __x) { return std::__hermite(__n, __x); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving code into the dylib has quite a few consequences and may change design considerations, so I don't think we should wait until some later point. Unless we want to make the functions experimental for now.
|
||
Most functions within the Mathematical Special Functions section contain integral indices. | ||
The Standard specifies the result for larger indices as implementation-defined. | ||
Libc++ pursuits reasonable results by choosing the same formulas as for indices below that threshold. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have you checked whether the results actually make sense beyond 128?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only check upto n<128
.
By the nature of it we can only check upto some finite value and this given value from the Standard seemed reasonable. Also note that our implementation does not distinguish between different orders (e.g. nothing like below/above any threshold). Do you think that we should increase the test coverage for the parameters?
} // namespace | ||
|
||
int main(int, char**) { | ||
test_hermite<float>(1e-5f, 1e-5f); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please refactor the tests to the style
template <class T>
void test() {
{ // Write what the test checks
// put the test here
}
{ // and the next test
// some code
}
}
We generally use this in new tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do I understand you correctly that the main
should only call 3 test<T>();
functions (for T=float, double, long double
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, exactly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented in 8b57a634c764c2aef653106fbb6b762663f9cf75.
libcxx/include/cmath
Outdated
__H_n_prev = __H_n; | ||
__H_n = __H_n_next; | ||
} | ||
return __H_n; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could have somewhat accurate overflow detection here with:
if (std::isnan(__H_n)) {
// Overflow.
if (n & 1)
// n is odd, return infinity of the same sign as x.
return std::copysign(std::numeric_limits<_Real>::infinity(), x);
// n is even, return +Inf
return std::numeric_limits<_real>::infinity();
}
return __H_n;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to think about this a bit more. Alternating Infs is definitely true in the limit x goes to infinity, but I believe there is also a possibility for an overflow because of large n
. The problem is that the polynomials are oscillating and one probably cannot easily tell the correct sign for a small x but larger n. (One needs to know between which roots the x lies...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 to everything in your comment.
- For the first point a simple non-trivial (might be not that useful) example is when
x = std::numeric_limits<_Real>::max()
, following the iterations, you will get that:
hermite(0, x) = 1.0
hermite(1, x) = +Inf
hermite(2, x) = +Inf
hermite(3, x) = NaN
...
and there are definitely more intricate cases.
- Another issue that we might want to address later is precision loss. I haven't looked into the stability of the iterations, but we might have catastrophic cancellation when
__x * __H_n ~ __i * __H_n_prev
. It is interesting to find some non-trivial examples, but we can defer dealing with this case to later PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could make use of your proposed code snippet at the entry of the function, e.g.
_LIBCPP_HIDE_FROM_ABI _Real __hermite(unsigned __n, _Real __x) {
if (std::isnan(__x))
return __x;
if (std::isinf(__x)) {
// ... your proposed overflow handling
}
// ...
Though I am unsure of the importance of handling std::hermite(n, +-Inf)
correctly. Personally I would not expect correct output for such an input (informally known as "garbage in, garbage out" ;D).
Let me know if this is worth implementing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking of something like this:
https://godbolt.org/z/7MaYYzob9
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Can I ask why you check for std::isnan
instead of std::isinf
? I believe I have seen examples where at some n
the next 2 results were Inf
and then turned to NaN
. If I find some more time I will try to find such an example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I think for finite __x
, if some intermediate __H_n
is Inf
, it is "correctly" overflowed (I might need to prove it or if it is not right, find some counter example). So checking isnan
should be enough. OTOH, probably we should just use !std::isfinite(__H_n)
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked some more into possible overflows. First thing to notice is that double overflows at ~1e308.
Hermite polynomial H_127 does not reach such large values inbetween any roots but only for larger x:
- largest root is at x=~16
- function values previous to the last root are significantly below the overflow threshold, i.e. |H_127(x)| < ~1e190 for |x| < ~16
- overflow threshold is reached at roughly: H_127(x=140)~1e308
- (one can see this from this wolframalpha log-log-plot)
For polynomials of smaller order it is even more extreme (e.g. largest root is smaller and overflow happens at even larger x).
I would not take polynomials of higher order into this discussion as their result is deemed implementation-defined by the Standard. Thus, possibly slowing down code for n<128 because of arguments for n>=128 seems strange.
My conclusion: I believe your initial suggestion is enough (e.g. checking the value of __H_n
after the loop). Using !std::isfinite()
also seems the approriate check here.
I will probably push a change similar to hermite2
as in this godbolt. (hermite
is the current implementation, and hermite1
the one you proposed in the previous godbolt.)
(This whole argument assumes atleast 8 byte double
. Is this valid?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented in 511b45679ab65dc3efca81abc976c27711a5df9a.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM
|
||
template <class T> | ||
std::array<T, 7> sample_points() { | ||
return {-1.0, -0.5, -0.1, 0.0, 0.1, 0.5, 1.0}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add some sample points that have magnitude > 1
and +- Inf
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, makes sense. I'll look into that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 0098ee77a763ea393f0993037e9d93aab2a209c9 (for points with magnitude >1
) and c94fad9ba48a4bb4149ab5cc8f97e1a2a3ac34f4 (for +-Inf
).
|
||
#include "type_algorithms.h" | ||
|
||
inline constexpr unsigned MAX_N = 128; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inline constexpr unsigned MAX_N = 128; | |
constexpr unsigned MAX_N = 128; |
Nit: constexpr
implies inline
.
https://eel.is/c++draft/dcl.constexpr#:constexpr
Usually we don't put an explicit inline
on templates and constexpr
for that matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a function or a static data member, so it's not implicit inline
.
For templates we use both with and without inline
and there is not strong preference.
For functions some people have a strong preference to use inline constexpr
; and the inline
is redundant.
Typically we don't use capitalized constants.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed the constant to g_max_n
in df4eadfa73f14831d6cd1bd045d5f5081dea31d6. Unsure if this is the correct naming scheme as I did not find another file with an example. Let me know if it should be changed to something else.
case 0: | ||
return {}; | ||
case 1: | ||
return {T(0)}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return {T(0)}; | |
return {T{0}}; |
Is there any reason for ()
vs {}
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to use the same style on this line as in the following ones. And the next lines need to perform a narrowing conversion (e.g. return {T(0.707106781186548)};
for T=float
). Using braces I assumed that the compiler or some tool (e.g. clang-tidy) would throw warnings. Let me know if this is baseless.
/// \return the Hermite polynomial \f$ H_n(x) \f$ | ||
/// \note The implementation is based on the recurrence formula | ||
/// \f[ | ||
/// H_{n+1}(x) = 2x H_n(x) - 2n H_{n-1} | ||
/// \f] | ||
/// Press, William H., et al. Numerical recipes 3rd edition: The art of | ||
/// scientific computing. Cambridge university press, 2007, p. 183. | ||
template <class _Real> | ||
_LIBCPP_HIDE_FROM_ABI _Real __hermite(unsigned __n, _Real __x) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe there was a comment already that we don't use doxygen comments. I think you should move that comment inside the function body.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in d4ea6088dca10dde0cfc21ad25ec3df218955c8c.
|
||
#include "type_algorithms.h" | ||
|
||
inline constexpr unsigned MAX_N = 128; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a function or a static data member, so it's not implicit inline
.
For templates we use both with and without inline
and there is not strong preference.
For functions some people have a strong preference to use inline constexpr
; and the inline
is redundant.
Typically we don't use capitalized constants.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lntue Thanks for your help reviewing! Can you approve the patch when you feel it's correct from the math point of view? I'll look at the libc++ specifics.
The patch seems mostly good to me.
} | ||
|
||
{ // check input infinity is handled correctly | ||
Real inf = std::numeric_limits<Real>::infinity(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like some infinity tests that use finite values as input.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implemented in 55ec209c8a261e8a48cbefc7a63e0ca776905b90. This contains only tests for T=double
. It is dependent on the size of the type. long double
can be different on other platforms which would result in quite messy test code. Let me know if I should generalize this test some more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The math part looks good to me! Thanks!
Should the documentation record the known error in ulps of the computed result? See, for comparison, https://libc.llvm.org/math/index.html . |
If I see that correctly the cited page lists functions (e.g. For any non-trivial implementation this is mostly calculated by comparing the result with a reference implementation, e.g. an implementation using larger floating-point types or a multi-precision library. I assume they did the same there? This worst-case analysis only holds any meaning for well chosen sample points. We would need to decide what a common input range is. (Sounds difficult for a general-purpose library.) All of this is quite tedious and complicated. Which is why it probably has not been done for most of the "Higher Math Functions" in libc? (The table is almost empty.) A plain documentation by creating a similar table for the "Mathematical Special Functions" only filled with "N/A" does not yield any benefit from my point of view. @efriedma-quic @intue I would be interested in your opinions on this topic. |
The empty squares in that table are stuff that hasn't been implemented in llvm-libc yet. (llvm-libc is, in general, currently a work in progress.) glibc also has a similar table in their documentation. For "float" inputs, in some cases you can probably figure out the worst-case error across the whole range by brute force. For wider inputs, you'd need some combination of random sampling, and brute-force of a few carefully chosen small intervals, yes. |
There is also another option: We could consider splitting off the correctness documentation into its own issue/merge request. My thoughts:
|
In libc++ we have not added this documentation for other math functions. For example It would be nice to do in a separate patch. PS I just restarted to build. |
I'm less concerned about the documentation, and more concerned that you've done the underlying analysis to prove the implementation actually has the numerical properties you want over the defined range. If you skip that analysis, you end up with issues like #92782. |
This reverts commit 165fc118554d4be52898020fbe6edf108e41a217.
a5c72d8
to
7cb43b0
Compare
@mordante I rebased to the current main branch. |
The CI is green. However it seems GitHub wants to merge with a noreply email address, so it seems your address is still hidden. |
@mordante I updated my profile and it now has a public email. Hope that helps. Sorry about the issues.. |
Why does this PR have so few CI checks? |
No problem, I just verified and your e-mail is visible now, thanks. Can you have a look at @philnik777's last comment.
I expect since the last failure was apple only. |
@mordante Fixed the issue. |
@PaulXiCao Congratulations on having your first Pull Request (PR) merged into the LLVM Project! Your changes will be combined with recent changes from other authors, then tested Please check whether problems have been caused by your change specifically, as How to do this, and the rest of the post-merge process, is covered in detail here. If your change does cause a problem, it may be reverted, or you can revert it yourself. If you don't get any reports, no action is required from you. Your changes are working as expected, well done! |
Thanks a lot again for working on this! I expect it will be faster to get the other functions in. |
…9982) Summary: Implementing the Hermite polynomials which are part of C++17's mathematical special functions. The goal is to get early feedback which will make implementing the other functions easier. Integration of functions in chunks (e.g. `std::hermite` at first, then `std::laguerre`, etc.) might make sense as well (also see note on boost.math below). I started out from this abandoned merge request: https://reviews.llvm.org/D58876 . The C++23 standard defines them in-terms of `/* floating-point type */` arguments. I have not looked into that. Note, there is still an ongoing discussion on discourse whether importing boost.math is an option. Test Plan: Reviewers: Subscribers: Tasks: Tags: Differential Revision: https://phabricator.intern.facebook.com/D60251212
The HEX float on z/OS does not have infinity nor NaN. In addition, the limits are smaller before the overflow occurs in mathematical calculations. This PR accounts for this. FYI, this LIT test was recently added in PR [89982](#89982)
The HEX float on z/OS does not have infinity nor NaN. In addition, the limits are smaller before the overflow occurs in mathematical calculations. This PR accounts for this. FYI, this LIT test was recently added in PR [89982](llvm/llvm-project#89982) NOKEYCHECK=True GitOrigin-RevId: f343fee8c5c9526b3cf62ee6d450c44b69a47e87
Implementing the Hermite polynomials which are part of C++17's mathematical special functions. The goal is to get early feedback which will make implementing the other functions easier. Integration of functions in chunks (e.g.
std::hermite
at first, thenstd::laguerre
, etc.) might make sense as well (also see note on boost.math below).I started out from this abandoned merge request: https://reviews.llvm.org/D58876 .
The C++23 standard defines them in-terms of
/* floating-point type */
arguments. I have not looked into that.Note, there is still an ongoing discussion on discourse whether importing boost.math is an option.