Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Constructing const array out of constexpr function

51 views
Skip to first unread message

bitrex

unread,
Mar 18, 2016, 7:18:28 PM3/18/16
to
Suppose for the sake of argument I have following private constexpr in a
class:

static constexpr uint16_t square_it(uint16_t x)
{
return std::pow(x, 2);
}

Then I want to construct a static constant array of these values for the
integers up to 255 in the same section of the same class using the above
constexpr:

static const uint16_t array_of_squares[256] =
{
//something
};

What is the "proper" way to do this?

Öö Tiib

unread,
Mar 19, 2016, 7:50:34 AM3/19/16
to
On Saturday, 19 March 2016 01:18:28 UTC+2, bitrex wrote:
> Suppose for the sake of argument I have following private constexpr in a
> class:
>
> static constexpr uint16_t square_it(uint16_t x)
> {
> return std::pow(x, 2);
> }

I do not understand what argument here can be. AFAIK it is ill-formed
code since 'std::pow' is not 'constexpr'. Standard-conformant
implementation must diagnose it as defective.
Almost none of the math functions can be 'constexpr', since these
contain the legacy requiment of setting 'errno' on various error
conditions, on case of 'pow' it is 'EDOM' AFAIK.

> Then I want to construct a static constant array of these values for the
> integers up to 255 in the same section of the same class using the above
> constexpr:
>
> static const uint16_t array_of_squares[256] =
> {
> //something
> };
>
> What is the "proper" way to do this?

What means "proper"? I would accept someone writing them all out manually,
making a tiny script for not doing it manually and perhaps even usage of preprocessor metaprogramming for generating the array.

bitrex

unread,
Mar 19, 2016, 8:10:52 AM3/19/16
to
So there isn't a way to do this internally? The idea is that I don't
want to use an external script to generate the values, as then if
someone wants to modify the code for their own purposes the script needs
to be delivered along with it.

I know it's possible to do something like this with macro expansion
using the preprocessor:

http://lolengine.net/blog/2011/12/20/cpp-lookup-table-generation

but my table function requires more complicated math operations than
just shifts and bitwise ANDs, i.e. using the power function.

So I was wondering if there was a way to incorporate the latter with the
former "constexpr" feature.

Jorgen Grahn

unread,
Mar 19, 2016, 9:01:49 AM3/19/16
to
On Sat, 2016-03-19, bitrex wrote:
> On 03/19/2016 07:50 AM, 嘱 Tiib wrote:
>> On Saturday, 19 March 2016 01:18:28 UTC+2, bitrex wrote:
>>> Suppose for the sake of argument I have following private constexpr in a
>>> class:
>>>
>>> static constexpr uint16_t square_it(uint16_t x)
>>> {
>>> return std::pow(x, 2);
>>> }
>>
>> I do not understand what argument here can be. AFAIK it is ill-formed
>> code since 'std::pow' is not 'constexpr'. Standard-conformant
>> implementation must diagnose it as defective.
>> Almost none of the math functions can be 'constexpr', since these
>> contain the legacy requiment of setting 'errno' on various error
>> conditions, on case of 'pow' it is 'EDOM' AFAIK.
>>
>>> Then I want to construct a static constant array of these values for the
>>> integers up to 255 in the same section of the same class using the above
>>> constexpr:
>>>
>>> static const uint16_t array_of_squares[256] =
>>> {
>>> //something
>>> };
>>>
>>> What is the "proper" way to do this?
>>
>> What means "proper"? I would accept someone writing them all out
>> manually, making a tiny script for not doing it manually and
>> perhaps even usage of preprocessor metaprogramming for generating
>> the array.
>
> So there isn't a way to do this internally?

He didn't say that -- he just criticized your suggested solution for
not working, and asked you to explain better.

(Personally I don't see why you chose std::pow(x, 2) instead of x * x,
but maybe I'm missing something.)

/Jorgen

--
// Jorgen Grahn <grahn@ Oo o. . .
\X/ snipabacken.se> O o .

bitrex

unread,
Mar 19, 2016, 9:04:00 AM3/19/16
to
On 03/19/2016 09:01 AM, Jorgen Grahn wrote:
> On Sat, 2016-03-19, bitrex wrote:
Just for the purposes of illustration. In my actual code, I'll need to
use a moderately complicated equation involving e^x...

Öö Tiib

unread,
Mar 19, 2016, 1:11:35 PM3/19/16
to
I did not write that. I wrote that for me it is OK if you type it in
manually, to use a tool or to use preprocessor. If duck typing or using
a tool is not good for *your* purposes, then that leaves only preprocessor.
We can't say that it is more "proper" in general. In my experience a
maintainer can be confused with and mess up C/C++ preprocessor
metaprogramming far easier than anything else that is available in C++.

>
> I know it's possible to do something like this with macro expansion
> using the preprocessor:
>
> http://lolengine.net/blog/2011/12/20/cpp-lookup-table-generation

Rather advanced code generation is possible with C/C++ preprocessor.
For particular purpose perhaps download boost and check how
BOOST_PP_REPEAT is made. If dependency on Boost.Preprocessor is Ok
then use BOOST_PP_REPEAT itself. Otherwise you can still do "derivative
work" of it into your code-base. It works OK on compilers that I have
tried.

> but my table function requires more complicated math operations than
> just shifts and bitwise ANDs, i.e. using the power function.
>
> So I was wondering if there was a way to incorporate the latter with the
> former "constexpr" feature.

Sure, what I meant is that generating code is not issue. Issue is that
we can't use standard library math functions in 'constexpr' functions
without violating standard. GCC has indeed made non-conforming extension to
have standard math functions 'constexpr'. My opinion is that it is better
to use some other 'constexpr' math library instead of such extension
of particular compiler. History has shown that it brings less pain in
long run.

bitrex

unread,
Mar 19, 2016, 6:14:29 PM3/19/16
to
Understood, thanks so much for your advice!

Alf P. Steinbach

unread,
Mar 20, 2016, 12:47:50 AM3/20/16
to
On 19.03.2016 00:18, bitrex wrote:
> Suppose for the sake of argument I have following private constexpr in a
> class:
>
> static constexpr uint16_t square_it(uint16_t x)
> {
> return std::pow(x, 2);
> }

As Öö Tiib have remarked else-thread, “Almost none of the [standard
library's] math functions can be 'constexpr', since these contain the
legacy requiment of setting 'errno' on various error conditions, on case
of 'pow' it is 'EDOM' AFAIK.”.

So the above code just can't be supposed: it's not possible.

But let's suppose

static constexpr auto square( int x )
-> int
{ return x*x; }


> Then I want to construct a static constant array of these values for the
> integers up to 255 in the same section of the same class using the above
> constexpr:
>
> static const uint16_t array_of_squares[256] =
> {
> //something
> };
>
> What is the "proper" way to do this?

You'd have to define “proper”. :)

Anyway, the following code works with Visual C++ 2015 update 2 and MinGW
g++ 5.1.0:

#include <array> // std::array
#include <stddef.h> // size_t
#include <utility> // std::index_sequence

static constexpr auto square( int x )
-> int
{ return x*x; }

class Squares
{
public:
enum{ n = 256 };
using Array = std::array<int, n>;

private:
Array values_;

template< size_t... index >
constexpr Squares( std::index_sequence<index...> )
: values_{ {square(index)...} }
{}

public:
constexpr auto values() const
-> std::array<int, n> const&
{ return values_; }

constexpr Squares()
: Squares( std::make_index_sequence<n>() )
{}
};

#include <iostream>
auto main() -> int
{
constexpr Squares squares;
char a[squares.values()[42]];
using namespace std;
cout << sizeof( a ) << endl;
(void) a;
}

Cheers & hth.,

- Alf

Robert Wessel

unread,
Mar 20, 2016, 4:05:39 PM3/20/16
to
On Sat, 19 Mar 2016 09:03:52 -0400, bitrex
<bit...@de.lete.earthlink.net> wrote:

>On 03/19/2016 09:01 AM, Jorgen Grahn wrote:
>> On Sat, 2016-03-19, bitrex wrote:
Out of curiosity, what's the range and precision of x that you need to
support (including things like NaNs, zeros and Infs), and what
precision can you live with in the result?

It would be possible to construct a constexpr function that would
evaluate a limited range of e**x, although doing it well (FSVO "well")
would be a challenge. Something evaluating the standard Taylor series
(1+x+x**2/2!+x**3/3!...), vaguely like:


#include <iostream>

#define LIMIT 1000000.

constexpr double exp_w(double x, double x2, double d, double d2)
{
return ((d2/x2)>LIMIT) ? 0 : (x2/d2 + exp_w(x, x*x2, d+1, d2*d));
}

constexpr double exp(double x)
{
return x<0 ? 1/exp_w(-x, 1, 1, 1) : exp_w(x, 1, 1, 1);
}

inline void test(double x)
{
double t;
t=exp(x);
std::cout << "e**" << x << " = " << t << std::endl;
}

int main(void)
{
test(0);
test(1);
test(1.5);
test(2);
test(3.14);
test(42);
test(-1);
test(-3.14);
test(19.4);
}


Which produces the following for me:

e**0 = 1
e**1 = 2.71828
e**1.5 = 4.48169
e**2 = 7.38906
e**3.14 = 23.1039
e**42 = 1.73927e+18
e**-1 = 0.367879
e**-3.14 = 0.0432828
e**19.4 = 2.66264e+08


LIMIT should be chosen to generate the required accuracy in the
desired range. It should be about the maximum precision of a double
(~2**53), at that point the last term will contribute less than an ULP
to the sum.

Assume that the error will be about an ULP per term evaluated of the
Taylor series, which in the worst case* should be about 200 ULPs**,
although most values will produce rather less error than that. This
will probably do poorly if the result overflows, or a NaN or Inf is
input. Those limitations could all be improved, but it would be a bit
messy.


*That should occur where x is somewhere in the vicinity of
(200!)**(1/200)

**And in practice some of the errors should be in opposite directions,
but I've not done the analysis.

Juha Nieminen

unread,
Mar 20, 2016, 4:50:35 PM3/20/16
to
Alf P. Steinbach <alf.p.stein...@gmail.com> wrote:
> class Squares
> {
> public:
> enum{ n = 256 };
> using Array = std::array<int, n>;
>
> private:
> Array values_;
>
> template< size_t... index >
> constexpr Squares( std::index_sequence<index...> )
> : values_{ {square(index)...} }
> {}
>
> public:
> constexpr auto values() const
> -> std::array<int, n> const&
> { return values_; }
>
> constexpr Squares()
> : Squares( std::make_index_sequence<n>() )
> {}
> };

I'm not exactly sure why you are using a class here. Or why you are
using an enum.

--- news://freenews.netfront.net/ - complaints: ne...@netfront.net ---

Öö Tiib

unread,
Mar 20, 2016, 6:08:15 PM3/20/16
to
On Sunday, 20 March 2016 22:50:35 UTC+2, Juha Nieminen wrote:
> Alf P. Steinbach <alf.p.stein...@gmail.com> wrote:
> > class Squares
> > {
> > public:
> > enum{ n = 256 };
> > using Array = std::array<int, n>;
> >
> > private:
> > Array values_;
> >
> > template< size_t... index >
> > constexpr Squares( std::index_sequence<index...> )
> > : values_{ {square(index)...} }
> > {}
> >
> > public:
> > constexpr auto values() const
> > -> std::array<int, n> const&
> > { return values_; }
> >
> > constexpr Squares()
> > : Squares( std::make_index_sequence<n>() )
> > {}
> > };
>
> I'm not exactly sure why you are using a class here. Or why you are
> using an enum.

How else to make delegating, constexpr, variadic, template constructor
if not by having a class? Follows ... why to make that thing?
Not sure. Perhaps for trying out where those actually run and how.
2 years old standard must have some mature enough implementations for
usage. I can't use it in over half of code-bases because of tooling
but it will change with years.

Robert Wessel

unread,
Mar 20, 2016, 6:18:22 PM3/20/16
to
On Sun, 20 Mar 2016 15:05:57 -0500, Robert Wessel
<robert...@yahoo.com> wrote:

>((d2/x2)>LIMIT)


The first conditional would be better coded as:

((x2/d2)<(1/LIMIT))

Since x2 can go to zero.

bitrex

unread,
Mar 21, 2016, 12:39:50 PM3/21/16
to
Robert Wessel <robert...@yahoo.com> Wrote in message:
> On Sat, 19 Mar 2016 09:03:52 -0400, bitrex
> <bit...@de.lete.earthlink.net> wrote:
>
>>On 03/19/2016 09:01 AM, Jorgen Grahn wrote:
>>> On Sat, 2016-03-19, bitrex wrote:
The function is for a "truncated exponential" involving e^(-3x).
The precision is not terribly critical, as it's just going to be
used to control the amplitude of other functions (audio
synthesis.) So 1 LSB maybe? There will be linear interpolation
happening as well so it might be good to store the derivative as
well.

I'm mobile and don't have the code in front of me, IIRC the full
function is something like MAGIC_NUMŨ(1 - e^(-3x)).

Input will actually be floats, with a domain between 0 and 1.0 at
256 steps. The result will then be shifted and cast to a unit 16
full with fractional representaton and stored.

Thanks so much for your help.

--


----Android NewsGroup Reader----
http://usenet.sinaapp.com/

bitrex

unread,
Mar 21, 2016, 12:39:59 PM3/21/16
to
Robert Wessel <robert...@yahoo.com> Wrote in message:
> On Sat, 19 Mar 2016 09:03:52 -0400, bitrex
> <bit...@de.lete.earthlink.net> wrote:
>
>>On 03/19/2016 09:01 AM, Jorgen Grahn wrote:
>>> On Sat, 2016-03-19, bitrex wrote:

Robert Wessel

unread,
Mar 22, 2016, 1:15:33 AM3/22/16
to
I'm not quite sure what you mean by one LSB, but if you meant one ULP
(unit in last place) of a float, you'd be OK so long as you did the
computation in a double. In you're converting to an int, you should
be OK doing the initial calculation with doubles as well. My error
estimate was that you'd be looking as something like 200 ULPs worst
case (IOW, of a ~53 bit double, about 45 bits would be correct).

If you mean to one ULP of a *double*, life gets much more difficult.


>I'm mobile and don't have the code in front of me, IIRC the full
> function is something like MAGIC_NUMŨ(1 - e^(-3x)).
>
>Input will actually be floats, with a domain between 0 and 1.0 at
> 256 steps. The result will then be shifted and cast to a unit 16
> full with fractional representaton and stored.


And you're trying to precompute those ~257 values? Something like
this?

#include <iostream>

#define LIMIT 1000000.

constexpr double zexp_w(double x, double x2, double d, double d2)
{
return ((x2/d2)<(1/LIMIT)) ? 0 : (x2/d2 + zexp_w(x, x*x2, d+1,
d2*d));
}

constexpr double zexp(double x)
{
return x<0 ? 1/zexp_w(-x, 1, 1, 1) : zexp_w(x, 1, 1, 1);
}

inline void test(double x)
{
double t;
t=zexp(x);
std::cout << "e**" << x << " = " << t << std::endl;
}


#define MAGIC_NUM 6.28

constexpr float ff(double x)
{
return MAGIC_NUM*(1-zexp(-3*(x/256)));
}

float a[257] =
{
ff(0), ff(1), ff(2), ff(3), ff(4), ff(5), ff(6), ff(7),
ff(8), ff(9),
ff(10), ff(11), ff(12), ff(13), ff(14), ff(15), ff(16), ff(17),
ff(18), ff(19),
ff(20), ff(21), ff(22), ff(23), ff(24), ff(25), ff(26), ff(27),
ff(28), ff(29),
ff(30), ff(31), ff(32), ff(33), ff(34), ff(35), ff(36), ff(37),
ff(38), ff(39),
ff(40), ff(41), ff(42), ff(43), ff(44), ff(45), ff(46), ff(47),
ff(48), ff(49),
ff(50), ff(51), ff(52), ff(53), ff(54), ff(55), ff(56), ff(57),
ff(58), ff(59),
ff(60), ff(61), ff(62), ff(63), ff(64), ff(65), ff(66), ff(67),
ff(68), ff(69),
ff(70), ff(71), ff(72), ff(73), ff(74), ff(75), ff(76), ff(77),
ff(78), ff(79),
ff(80), ff(81), ff(82), ff(83), ff(84), ff(85), ff(86), ff(87),
ff(88), ff(89),
ff(90), ff(91), ff(92), ff(93), ff(94), ff(95), ff(96), ff(97),
ff(98), ff(99),


ff(100),ff(101),ff(102),ff(103),ff(104),ff(105),ff(106),ff(107),ff(108),ff(109),

ff(110),ff(111),ff(112),ff(113),ff(114),ff(115),ff(116),ff(117),ff(118),ff(119),

ff(120),ff(121),ff(122),ff(123),ff(124),ff(125),ff(126),ff(127),ff(128),ff(129),

ff(130),ff(131),ff(132),ff(133),ff(134),ff(135),ff(136),ff(137),ff(138),ff(139),

ff(140),ff(141),ff(142),ff(143),ff(144),ff(145),ff(146),ff(147),ff(148),ff(149),

ff(150),ff(151),ff(152),ff(153),ff(154),ff(155),ff(156),ff(157),ff(158),ff(159),

ff(160),ff(161),ff(162),ff(163),ff(164),ff(165),ff(166),ff(167),ff(168),ff(169),

ff(170),ff(171),ff(172),ff(173),ff(174),ff(175),ff(176),ff(177),ff(178),ff(179),

ff(180),ff(181),ff(182),ff(183),ff(184),ff(185),ff(186),ff(187),ff(188),ff(189),

ff(190),ff(191),ff(192),ff(193),ff(194),ff(195),ff(196),ff(197),ff(198),ff(199),


ff(200),ff(201),ff(202),ff(203),ff(204),ff(205),ff(206),ff(207),ff(208),ff(209),

ff(210),ff(211),ff(212),ff(213),ff(214),ff(215),ff(216),ff(217),ff(218),ff(219),

ff(220),ff(221),ff(222),ff(223),ff(224),ff(225),ff(226),ff(227),ff(228),ff(229),

ff(230),ff(231),ff(232),ff(233),ff(234),ff(235),ff(236),ff(237),ff(238),ff(239),

ff(240),ff(241),ff(242),ff(243),ff(244),ff(245),ff(246),ff(247),ff(248),ff(249),
ff(250),ff(251),ff(252),ff(253),ff(254),ff(255),ff(256)
};

int main(void)
{
int i;

test(0);
test(1);
test(1.5);
test(2);
test(3.14);
test(42);
test(-1);
test(-3.14);
test(19.4);

for (i=0; i<257; i++)
std::cout << MAGIC_NUM << "*(1-e**(" << (-3*(i/256.)) << ") = "
<< a[i] << std::endl;
}


(Again, you'll need to generate LIMIT better.)


Output:

e**0 = 1
e**1 = 2.71828
e**1.5 = 4.48169
e**2 = 7.38906
e**3.14 = 23.1039
e**42 = 1.73927e+18
e**-1 = 0.367879
e**-3.14 = 0.0432828
e**19.4 = 2.66264e+08
6.28*(1-e**(-0) = 0
6.28*(1-e**(-0.0117188) = 0.0731626
6.28*(1-e**(-0.0234375) = 0.145476
6.28*(1-e**(-0.0351563) = 0.216945
6.28*(1-e**(-0.046875) = 0.287581
6.28*(1-e**(-0.0585938) = 0.357393
6.28*(1-e**(-0.0703125) = 0.426396
6.28*(1-e**(-0.0820313) = 0.494593
6.28*(1-e**(-0.09375) = 0.561995
6.28*(1-e**(-0.105469) = 0.628611
6.28*(1-e**(-0.117188) = 0.694451
...
6.28*(1-e**(-2.87109) = 5.92432
6.28*(1-e**(-2.88281) = 5.92846
6.28*(1-e**(-2.89453) = 5.93256
6.28*(1-e**(-2.90625) = 5.93661
6.28*(1-e**(-2.91797) = 5.94061
6.28*(1-e**(-2.92969) = 5.94456
6.28*(1-e**(-2.94141) = 5.94847
6.28*(1-e**(-2.95313) = 5.95233
6.28*(1-e**(-2.96484) = 5.95615
6.28*(1-e**(-2.97656) = 5.95992
6.28*(1-e**(-2.98828) = 5.96365
6.28*(1-e**(-3) = 5.96734

bitrex

unread,
Mar 22, 2016, 10:47:28 AM3/22/16
to
Yes, I believe that's what I meant, sorry I wasn't more clear. My main
gig is electronics/hardware, I'm still learning to code (but hopefully
getting better all the time.)

I don't believe I have the "double" type available for this
processor/codebase, everything I've seen in the libraries that I'm
working with uses single-precision "float."
Yeah! That's the ticket.

0 new messages