Constexpr maths functions

1,636 views
Skip to first unread message

David Brown

unread,
Oct 6, 2017, 6:45:16 AM10/6/17
to std-pr...@isocpp.org
In a recent test I was doing about using constexpr for generating
compile-time tables, I discovered that expressions like this:

constexpr auto pi = 4 * std::atan(1);

are accepted by gcc, but not by clang. It seems that the standards
don't require functions like std::atan, std::sin, etc., to be constexpr.
In the context of embedded systems, being able to make compile-time
tables of things like sin functions can be an extremely useful feature.

Is there any reason why this could not be supported in the C++ standards?


Niall Douglas

unread,
Oct 6, 2017, 11:48:08 AM10/6/17
to ISO C++ Standard - Future Proposals, da...@westcontrol.com

Is there any reason why this could not be supported in the C++ standards?

Breaks compatibility with C I would imagine.

Niall 

Vishal Oza

unread,
Oct 6, 2017, 12:18:41 PM10/6/17
to ISO C++ Standard - Future Proposals
I am not sure but exception handling, altering global variable and dynamicly create variables could be other reasons. I do not think that there is anything in the standard that prevents calulated results from being stored in a static lookup table. Although I could be wrong.

Jens Maurer

unread,
Oct 6, 2017, 2:04:26 PM10/6/17
to std-pr...@isocpp.org
Some of these math functions are required to set errno for domain
errors. All of these math functions should consider the rounding
mode, which is essentially a thread-local variable.

Either fact prevents math functions from being "constexpr".

Yes, a purely functional approach to math functions (no global
state) would be nice to have. Proposals welcome.

Jens

Ville Voutilainen

unread,
Oct 6, 2017, 2:32:36 PM10/6/17
to ISO C++ Standard - Future Proposals
Additionally, vectorized implementations don't (yet) work with
constexpr, so we want to avoid
pessimizing implementations just to make them constexpr. And due to
portability concerns, the
standard doesn't allow an implementation to make something constexpr
if it hasn't been so specified.

There's ongoing work to fix some or all of those problems, so there's hope.

Myriachan

unread,
Oct 6, 2017, 5:02:13 PM10/6/17
to ISO C++ Standard - Future Proposals
On Friday, October 6, 2017 at 11:32:36 AM UTC-7, Ville Voutilainen wrote:
Additionally, vectorized implementations don't (yet) work with
constexpr, so we want to avoid
pessimizing implementations just to make them constexpr. And due to
portability concerns, the
standard doesn't allow an implementation to make something constexpr
if it hasn't been so specified.

There's ongoing work to fix some or all of those problems, so there's hope.

This is one reason to allow overloading on constexpr parameters.  It allows things like separating compile-time and runtime implementations.

Melissa

Ville Voutilainen

unread,
Oct 6, 2017, 5:03:17 PM10/6/17
to ISO C++ Standard - Future Proposals
There are other possible solutions than overloading on constexpr. This
paper http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0595r0.html
shows one of them.

Myriachan

unread,
Oct 6, 2017, 6:44:58 PM10/6/17
to ISO C++ Standard - Future Proposals
Would also work!

David Brown

unread,
Oct 6, 2017, 8:24:46 PM10/6/17
to Niall Douglas, ISO C++ Standard - Future Proposals
That would be a reasonable (but not great) justification - though I
don't see how it would. Perhaps errno support would be the problem?

It certainly should not be too difficult for compilers to support a
second set of maths functions (in a separate namespace) with constexpr
declarations. Most major compilers support these sorts of functions
with builtins and already have support for versions that ignore errno.

Niall Douglas

unread,
Oct 7, 2017, 7:01:54 PM10/7/17
to ISO C++ Standard - Future Proposals, nialldo...@gmail.com, da...@westcontrol.com
On Saturday, October 7, 2017 at 1:24:46 AM UTC+1, David Brown wrote:

On 06/10/17 17:48, Niall Douglas wrote:
>
>     Is there any reason why this could not be supported in the C++
>     standards?
>
> Breaks compatibility with C I would imagine.
>

That would be a reasonable (but not great) justification - though I
don't see how it would.  Perhaps errno support would be the problem?

It's not even that.

A very common use case for C++ is to compile straight-C programs for the improved optimisation.

Thus a C program must run exactly the same in C++ as it would in C. Making the cmath functions go constexpr in C++ breaks that, badly.

Niall

oliver...@googlemail.com

unread,
Oct 8, 2017, 3:39:22 AM10/8/17
to ISO C++ Standard - Future Proposals, da...@westcontrol.com
Just came across this thread. Have not yet had time to digest the various points made, but wanted to draw attention to


Please note that the second draft of this paper is currently being completed and will hopefully be submitted soon. The main change is a discussion of rounding mode.

David Brown

unread,
Oct 8, 2017, 6:45:09 AM10/8/17
to Niall Douglas, ISO C++ Standard - Future Proposals
/What/ would break? In all the code I have written, there is nothing
about "sin" or other <math.h> functions that cannot be pre-calculated.
"sin(x)" gives the same result for compile-time constant x whether it is
done at run time or at compile time. You sound so convinced that it
would "break badly" to do this calculation at run time (though compilers
certainly /do/ handle this at compile time - gcc has no problem with
using sin() in constexpr functions and initialisation). What am I missing?

Niall Douglas

unread,
Oct 8, 2017, 8:27:32 PM10/8/17
to ISO C++ Standard - Future Proposals, nialldo...@gmail.com, da...@westcontrol.com
> It's not even that.
>
> A very common use case for C++ is to compile straight-C programs for the
> improved optimisation.
>
> Thus a C program must run exactly the same in C++ as it would in C.
> Making the cmath functions go constexpr in C++ breaks that, badly.
>

/What/ would break?  In all the code I have written, there is nothing
about "sin" or other <math.h> functions that cannot be pre-calculated.

In all the code that you have written it may have appeared to have been safe.

Standards committees decide on the basis of code they will never and can never see. Retrospectively changing the behaviour of a language or library feature, apart from fixing defects, very rarely occurs.
 

"sin(x)" gives the same result for compile-time constant x whether it is
done at run time or at compile time.  You sound so convinced that it
would "break badly" to do this calculation at run time (though compilers
certainly /do/ handle this at compile time - gcc has no problem with
using sin() in constexpr functions and initialisation).  What am I missing?

You also appear to be conflating constant folding during optimisation and constant expressions evaluation at compile time.

They are not the same. GCC may mark the cmath functions as constexpr as part of its choice of quality of implementation i.e. as an extension. But it's not standardisable unless C adds the constexpr facility from C++, and then implements a breaking change to the spec for the math.h functions. Which would be very hard to imagine happening.

Nobody is disputing that we need new math functions which more closely match current hardware. And people are working on that, both in C++ and in C. But these things take a few years. Indeed until just very recently a major CPU architecture didn't implement FP NaNs according to spec. That put a kibosh on any reasonable attempt to standardise "fast-math" until it was fixed, which it is now in their most recent architecture.

I don't doubt that at some future point, SIMD and fast-math FP will get standardised both in C and C++. It'll be a different API to cmath because it's incompatible. But with that you'll get what you want, constexpr execution. Until then you'll need to rely on compiler extensions or use third party libraries which implement FP in constexpr.

Niall

David Brown

unread,
Oct 9, 2017, 5:58:30 AM10/9/17
to oliver...@googlemail.com, ISO C++ Standard - Future Proposals

On 08/10/17 09:39, oliver...@googlemail.com wrote:
> Just came across this thread. Have not yet had time to digest the
> various points made, but wanted to draw attention to
>
> http://open-std.org/JTC1/SC22/WG21/docs/papers/2017/p0533r0.pdf
>
> Please note that the second draft of this paper is currently being
> completed and will hopefully be submitted soon. The main change is a
> discussion of rounding mode.

Thanks for that link.

Unfortunately, to me, that proposal mostly misses the point. It is
great that functions such as "trunc" and "fmod" can me made constexpr.
It is /long/ overdue that functions like "abs" and "fmin" are made
constexpr - these can be implemented with a one-line template function
which every mainstream compiler will optimise smoothly.

However, the proposal does not go far enough to suit me.

The restriction on "the function being closed on the set of extended
rational numbers" is not necessary, and not useful. Clearly it is not
necessary - gcc manages happily to have functions like "sin" defined as
constexpr. Clearly it is not useful, as it excludes important and
popular functions. It is not even sensibly defined - there is no such
thing as a "set of extended rational numbers" and no definition of it in
the proposal. Either you consider floating point as an approximation
for real numbers (in which case these functions /are/ closed, at least
for a useful subdomain), or you consider them as functions on the set of
numbers representable as computer floating point numbers of various
types, in which case the functions are /still/ closed (on a useful
subdomain). Meanwhile, functions like "division" are constexpr without
being closed on the field (you can't divide by 0), "abs" is to be
constexpr without being well-defined over the whole domain (you can't
take the abs value of the lowest possible integer on most systems).
Basic arithmetic can overflow. The whole thing is inconsistent, and too
limited to be useful in many cases where you really want compile-time
generation of results (such as for trig and power operations). All it
gives you is constexpr for functions that are almost never used (who
uses "islessequal(x, y)" rather than "x < y" ?) except in very niche
cases, and functions that are trivial to implement as constexpr template
functions.


In my opinion, the dependency of floating point operations on a diffuse
"floating point environment" is something that should have been
deprecated eons ago. The function "sin" should return an approximation
to the sine of its argument. It is never going to be mathematically
correct (except for occasional values, like 0). The IEEE standards give
reasonable criteria for accuracy, supported by lots of hardware and
software implementations. But functions like this should be state-free
- they are /functions/, in the mathematical sense. They should not
depend on the state. Sure, there will be /some/ people who want to
specify rounding mode FE_DOWNWARD at some points in their code, and
FE_UPWARD at other points. IMHO, they should be using types
double_round_downward and double_round_upward, not type double with a
set state. That would give them better control of exactly what they are
doing - while the rest of us, almost every user of floating point maths,
can use double in FE_DONT_KNOW_AND_DONT_CARE_AT_ALL rounding mode.


Compatibility with C, and compatibility with existing programs, is of
course paramount. I would propose a better compromise would be to
introduce a nested namespace std::ce:: for constexpr compatible
functions like std::ce::sin, std::ce::pow, std::ce::abs. Rounding modes
would be fixed - a compiler would either support a single rounding mode,
or make it configurable with compiler switches, pragmas, etc. Ideally,
these functions would give compile-time errors on failure (like
std::ce::sqrt(-1) ), but I would be happy enough with a "garbage in,
garbage out" solution. And when used outside constexpr context, the
nice C++ solution would be exceptions - again, I would be happy with
undefined behaviour, and I think adoption would be easier if this is
implementation defined. This namespace would include a variety of
useful functions from <cmath>, but would certainly avoid the unnecessary
name variants that are left-overs from C before <tgmath.h>.

David Brown

unread,
Oct 9, 2017, 6:20:24 AM10/9/17
to Niall Douglas, ISO C++ Standard - Future Proposals


On 09/10/17 02:27, Niall Douglas wrote:
> > It's not even that.
> >
> > A very common use case for C++ is to compile straight-C programs
> for the
> > improved optimisation.
> >
> > Thus a C program must run exactly the same in C++ as it would in C.
> > Making the cmath functions go constexpr in C++ breaks that, badly.
> >
>
> /What/ would break?  In all the code I have written, there is nothing
> about "sin" or other <math.h> functions that cannot be pre-calculated.
>
>
> In all the code that /you/ have written it may have appeared to have
> been safe.

Agreed. That is why I am asking you what you see would break so badly -
precisely because I /don't/ see it.

>
> Standards committees decide on the basis of code they will never and can
> never see. Retrospectively changing the behaviour of a language or
> library feature, apart from fixing defects, very rarely occurs.
>
>
> "sin(x)" gives the same result for compile-time constant x whether
> it is
> done at run time or at compile time.  You sound so convinced that it
> would "break badly" to do this calculation at run time (though
> compilers
> certainly /do/ handle this at compile time - gcc has no problem with
> using sin() in constexpr functions and initialisation).  What am I
> missing?
>
> You also appear to be conflating constant folding during optimisation
> and constant expressions evaluation at compile time.

No, I don't think I am. I expect compilers to be able to do
optimisations above and beyond what they accept as valid syntax. A
compiler can happily pre-calculate "sin(0.5)" for optimisation purposes
even though it rejects its use in a constexpr.

>
> They are not the same. GCC may mark the cmath functions as constexpr as
> part of its choice of quality of implementation i.e. as an extension.
> But it's not standardisable unless C adds the constexpr facility from
> C++, and then implements a breaking change to the spec for the math.h
> functions. Which would be very hard to imagine happening.

I cannot see that as a requirement. There is nothing in the C++
standards to require <cmath> to match <math.h> that closely. In fact,
the standard explicitly requires that <cmath> makes /additions/ to
<math.h>, such as overloads for a number of functions as an alternative
to asking for different function versions by name like C. I fail to see
why an implementation might happily retain "double sin(double x);" in
<math.h> and have "constexpr double sin(double x);" in <cmath>.

>
> Nobody is disputing that we need new math functions which more closely
> match current hardware. And people are working on that, both in C++ and
> in C. But these things take a few years. Indeed until just very recently
> a major CPU architecture didn't implement FP NaNs according to spec.
> That put a kibosh on any reasonable attempt to standardise "fast-math"
> until it was fixed, which it is now in their most recent architecture.

"fast-math", at least the term I know (the gcc optimisation),
specifically excludes any worry about NaNs. There are quite a few
embedded processors with limited floating point hardware that does not
handle that sort of thing properly (in the IEEE sense). These hardware
units either give some sort of default result, give garbage results, or
through a hardware exception. That is /fine/ for a great deal of
software - I expect that very little code has any interest in NaNs and
other complications in floating point. I feel frustration that the
needs of the many - who just want to divide 1.0 by 3.0 and get 3.3333333
- are given such low priority because of the tiny few who want to argue
if it should be 3.3333333333333333333333 or 3.3333333333333333333334.

>
> I don't doubt that at some future point, SIMD and fast-math FP will get
> standardised both in C and C++. It'll be a different API to cmath
> because it's incompatible. But with that you'll get what you want,
> constexpr execution. Until then you'll need to rely on compiler
> extensions or use third party libraries which implement FP in constexpr.
>

I started this thread after being in a discussion on Usenet about
pre-calculating sin tables in C++, especially after C++14 allowed loops
and such in constexpr functions. A search on google shows a way to
handle this in standard C++ - using recursive templates to define your
own sine function from Taylor series. That is /ridiculous/. There is
no good excuse for that. I was able to make a vastly simpler solution,
using std::sin, but limited to gcc where std::sin is constexpr.

> Niall

Oliver Rosten

unread,
Oct 9, 2017, 8:41:13 AM10/9/17
to David Brown, ISO C++ Standard - Future Proposals
Hi David,

Thanks for the feedback.

I think that our endgame is the same: we both want to see pretty much every function in cmath declared, one way or another, constexpr. Indeed, our latest working draft includes a section 'Future Directions' in which we state our desire to extend what we've done to almost all of cmath.

However, we were advised - and in hindsight this advice has proven excellent - not to be this ambitious yet. Focusing just on the functions declared constexpr in our paper revealed several subtleties, not all of which we appreciated when the first draft was submitted. 

David Brown

unread,
Oct 9, 2017, 8:57:01 AM10/9/17
to Oliver Rosten, ISO C++ Standard - Future Proposals

On 09/10/17 14:41, Oliver Rosten wrote:
> Hi David,
>
> Thanks for the feedback.
>
> I think that our endgame is the same: we both want to see pretty much
> every function in cmath declared, one way or another, constexpr. Indeed,
> our latest working draft includes a section 'Future Directions' in which
> we state our desire to extend what we've done to almost all of cmath.
>
> However, we were advised - and in hindsight this advice has proven
> excellent - not to be this ambitious yet. Focusing just on the functions
> declared constexpr in our paper revealed several subtleties, not all of
> which we appreciated when the first draft was submitted.

That sounds like the best I can hope for here. I appreciate that things
that appear simple and obvious from the point of view of a user like me
can be far more complicated in the big picture of the standards - and
that something that might be easy for one implementation to support
could be far more difficult for other implementations. I am glad you
understand what I am looking for here, and I am very glad that you are
working towards it - even if progress is slower than a naïve user like
myself would like.

One thing that might be useful for your proposal paper is to look at the
kinds of use-cases for making these functions constexpr. For many uses,
it is not actually that important to have the functions constexpr as
long as compilers can optimise them and pre-compute the results. The
real gains (for me, anyway) are for pre-computed tables. I work mostly
with small embedded systems - on these targets, something like "sin" is
going to be very slow to calculate, and require libraries taking a
significant amount of program space. When you need sine functions, as
you regularly do (such as for motor control), you use lookup tables and
interpolation of various sorts. Being able to calculate these
efficiently within C++ at compile, instead of using separate table
generator scripts, cut-and-paste from spreadsheets, and other such
methods - it's just /lovely/ to be able to write it all in the C++ code.
Here is a simple example I wrote earlier for a Usenet posting. It
works fine in gcc (which is the compiler I use most), but it would be
great to see it supported by the standard.

mvh.,

David


#include <stdint.h>
#include <cmath>

constexpr double const_sine(double x) {
return std::sin(x);
}

const auto pi = 3.14159265358979323846264338327;
const auto pi2 = 2 * pi;

template<class T, int N> struct array {
T elems[N];

constexpr T& operator[](size_t i) {
return elems[i];
}
constexpr const T& operator[](size_t i) const {
return elems[i];
}
};

const auto size = 128;
using Table = array<int16_t, size>;

constexpr Table makeTable() {
Table t{};
for (auto i = 0; i < size; i++) {
const auto angle = (i * pi2 / size);
const auto s = const_sine(angle);
t[i] = 32767 * s;
}
return t;
}

extern const Table sine_table = makeTable();

Hyman Rosen

unread,
Oct 9, 2017, 11:48:59 AM10/9/17
to std-pr...@isocpp.org
On Mon, Oct 9, 2017 at 8:56 AM, David Brown <da...@westcontrol.com> wrote:
const auto pi = 3.14159265358979323846264338327;

If you are targeting systems that use IEEE-754 floating point, a decimal with no
more than 17 significant digits is sufficient to specify every possible double value.
For this value, a 16-digit number suffices.  So,

    const auto pi = 3.141592653589793;

Specifying more digits does not change the value stored in pi, and sends a poor
message to readers of the code, implying that specifying so many digits is good
and useful, when it is not.

FrankHB1989

unread,
Oct 9, 2017, 9:54:13 PM10/9/17
to ISO C++ Standard - Future Proposals, oliver...@googlemail.com, da...@westcontrol.com


在 2017年10月9日星期一 UTC+8下午8:57:01,David Brown写道:
Is it rounded?.

IIRC it is 3.141592653589793238462643383279502884197169399...

dgutson .

unread,
Oct 9, 2017, 10:06:15 PM10/9/17
to std-proposals
Another interesting approach is to support (integral) fixed point in the STL, and use something like

    constexpr auto x = std::atan_constexpr(std::fp_constant_q<A,B>);

or in general

    constexpr auto x = std::some_function_consexpr(std::integral_constant<X>);

as an alternative to constexpr args; functions are actually templates

template <class T>
auto some_function(T)
{
    return ...expression(T::value);
}
 

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/fcea9062-9094-498e-8b30-98bc9186e85d%40isocpp.org.



--
Who’s got the sweetest disposition?
One guess, that’s who?
Who’d never, ever start an argument?
Who never shows a bit of temperament?
Who's never wrong but always right?
Who'd never dream of starting a fight?
Who get stuck with all the bad luck?

Hyman Rosen

unread,
Oct 10, 2017, 11:47:02 AM10/10/17
to std-pr...@isocpp.org, oliver...@googlemail.com, da...@westcontrol.com
On Mon, Oct 9, 2017 at 9:54 PM, FrankHB1989 <frank...@gmail.com> wrote:
Is it rounded?.
IIRC it is 3.141592653589793238462643383279502884197169399...

The requirement (or perhaps hope) is that the double value contains the
closest representable value to the decimal.  If the decimal value is exactly
halfway between two representable values, there is a tiebreaker rule (I
think the value with LSB 0 is chosen.)

For each possible double value, there is a decimal value with no more than
17 significant digits that is closer to that double value than to any other, so
there is no need to provide decimal constants written with more than 17
significant digits.  Supplying extra digits gives the illusion of more precision
than the format supports.  It's misleading to code readers.

Going in the other direction, every decimal value with no more than 15
significant digits can be represented as a unique double value.

For floats, the equivalent numbers are 9 and 6; that is, every float value
has a decimal value with no more than 9 significant digits that is closer
to it than to any other value, and every decimal value with no more than
6 significant digits can be represented as a unique float value.
Reply all
Reply to author
Forward
0 new messages