Daubechies wavelet and scaling filters: Is there a better way?

I have computed the Daubechies wavelet and scaling filters in float, double, long double, and quad precision, and now want to expose these filters in a usable API. My problem is as follows: I want to make sure that these filters are correct to every single bit, so I wrote them as hexadecimal floating point literals. Then I wanted to get them to parse in the correct format, so I need to append literals like f, L, or Q. But to get them to parse as the correct type, I need to duplicate them over and over, resulting in a monstrous #include file, given here

The file is too big to place in the question, but here's a short sample:

#ifndef BOOST_MATH_FILTERS_DAUBECHIES_HPP
#define BOOST_MATH_FILTERS_DAUBECHIES_HPP
#include <array>
#ifdef BOOST_HAS_FLOAT128
#include <boost/multiprecision/float128.hpp>
#endif
namespace boost::math::filters {

template <typename Real, unsigned p>
constexpr std::array<Real, 2*p> daubechies_scaling_filter()
{
static_assert(sizeof(Real) <= 16, "Filter coefficients only computed up to 128 bits of precision.");
static_assert(p < 25, "Filter coefficients only implemented up to 24.");
if constexpr (p == 1) {
if constexpr (std::is_same_v<Real, float>) {
return {0x1.6a09e6p-1f, 0x1.6a09e6p-1f};
}
if constexpr (std::is_same_v<Real, double>) {
return {0x1.6a09e667f3bcdp-1, 0x1.6a09e667f3bcdp-1};
}
if constexpr (std::is_same_v<Real, long double>) {
return {0xb.504f333f9de6484p-4L, 0xb.504f333f9de6484p-4L};
}
#ifdef BOOST_HAS_FLOAT128
if constexpr (std::is_same_v<Real, boost::multiprecision::float128>) {
return {0x1.6a09e667f3bcc908b2fb1366ea95p-1Q, 0x1.6a09e667f3bcc908b2fb1366ea95p-1Q};
}
#endif
}
if constexpr (p == 2) {
if constexpr (std::is_same_v<Real, float>) {
return {0x1.ee8dd4p-2f, 0x1.ac4bdep-1f, 0x1.cb0bfp-3f, -0x1.0907dcp-3f};
}
if constexpr (std::is_same_v<Real, double>) {
return {0x1.ee8dd4748bf15p-2, 0x1.ac4bdd6e3fd71p-1, 0x1.cb0bf0b6b7109p-3, -0x1.0907dc193069p-3};
}
if constexpr (std::is_same_v<Real, long double>) {
return {0xf.746ea3a45f8a62ap-5L, 0xd.625eeb71feb8557p-4L, 0xe.585f85b5b8845bdp-6L, -0x8.483ee0c9834834cp-6L};
}
#ifdef BOOST_HAS_FLOAT128
if constexpr (std::is_same_v<Real, boost::multiprecision::float128>) {
return {0x1.ee8dd4748bf14c548b969de58fap-2Q, 0x1.ac4bdd6e3fd70aae9f48d8a63d1bp-1Q, 0x1.cb0bf0b6b7108b79b4bf11d08b16p-3Q, -0x1.0907dc1930690697b13714fd4a15p-3Q};
}
#endif
}
...

template<class Real, size_t p>
std::array<Real, 2*p> daubechies_wavelet_filter() {
std::array<Real, 2*p> g;
auto h = daubechies_scaling_filter<Real, p>();
for (size_t i = 0; i < g.size(); i += 2)
{
g[i] = h[g.size() - i - 1];
g[i+1] = -h[g.size() - i - 2];
}
return g;
}


Is there a way to only state the filter coefficients once, with the maximum allowed precision, and have the compiler parse it as the user's desired type, and preserve the constexpr?

Is keep the filter coefficients constexpr a sensible desideratum here? (I'm not even sure that I should make p a template parameter. . .) If so, note that the scaling filters are constexpr, but the wavelet filters are not. Is there a way around that?

C++17 solutions are acceptable; C++20 solutions are . . . less so, but if they can be shown to dramatically improve the code that's what language updates are for correct?

• @Emma: I already know they are correct. – user14717 Oct 27 '19 at 23:09