使用整数模板参数创建编译时双精度

6

是否可以创建一个double类型的变量,它的值为1*10^x,其中x是基于整数模板参数的。例如:

template < int exp >
struct DoubleValue
{
    static constexpr double value = ????;
}

double d = DoubleValue<20>::value; // = 1e20
double d = DoubleValue<-20>::value; // = 1e-20

由于它可以使用字面量创建,因此似乎应该有可能做到这一点。

我希望该值在编译时计算(因此据我所知,std::pow将无法实现)。 此外,如果可能的话,我希望能够避免实际的迭代计算((可能是毫无根据的)担心精度问题)。我还希望能够使用更大的指数值,例如200,这使得将该值存储在标准整数类型中变得不可能。


3
或许是 std::pow(10, exp) - iBug
1
@iBug 需要一个在编译时就能确定的东西,所以 std::pow 不行,因为它不是 constexpr - Marek R
3
可能是[c++整数的幂,模板元编程]的重复问题。 - Ken Y-N
为了提供一些背景,我想创建一种解析输入数字的方法,例如100M、100K、0.1m,其中字符代表公制前缀(https://en.m.wikipedia.org/wiki/Metric_prefix)。但它也应该适用于我们的内部类型(我可以使用指数来创建)。因此,我想创建一个模板专业化,以创建具有这种指数的双精度。 - Rubix Cube
1
@RubixCube,你是否在寻找用户自定义字面值?(C++11) - user268396
显示剩余4条评论
3个回答

6

假设你的编译器支持C++14或更高版本(在2019年应该是一个合理的假设),使用constexpr函数非常简单:

constexpr double myPow(double x, int exp)
{
    double pow = 1.0;
    for (int i = 0; i < exp; ++i)
        pow *= x;
    for (int i = 0; i > exp; --i)
        pow /= x;
    return pow;
}

template < int exp >
struct DoubleValue
{
    static constexpr double value = myPow(10.0, exp);
};

请查看此处以验证其工作,即使没有进行优化,该值也是在编译时生成的。

根据您的用例,您甚至可能不需要DoubleValue结构体,而可以直接使用myPow()


更新

正如评论中@Bob__指出的那样,可能存在比此处介绍的更好的关于数字精度的算法。但是,自从C++14以来,许多基本语言特性可以在constexpr函数的主体中使用。因此,只要您不需要任何外部库,就可以实现适合您需求的任何算法。


离题:「假设你的编译器支持C++14或更高版本(在2019年应该是一个合理的假设)」;当写新项目时,当然可以。但是,在我正在工作的项目中,我们仍然有一些代码片段使用VS2003进行编译...传统项目万岁! - Algirdas Preidžius
1
幸运的是,我想要使用这个项目的时候它刚刚更新了 :). 我选择这个答案是因为它在意图上比递归模板解决方案更清晰。 - Rubix Cube
1
请注意,此处使用的算法比其他替代方案更容易出现数字误差:https://wandbox.org/permlink/tiupixvpbtwo9Zwd - Bob__

1
如果你想在编译时而不使用 std::pow,可以尝试以下代码:
#include <iostream>

template <int e>
struct DoubleValue {
    static constexpr double value = 10.0 * DoubleValue<e - 1>::value;
};
template <>
struct DoubleValue<0> {
    static constexpr double value = 1.0;
};
int main() {
    std::cout << DoubleValue<20>::value << '\n'; //1e+20
}

C++弹跳


std::cout << DoubleValue<-10>::value << std::endl; 这种情况怎么处理? - Algirdas Preidžius
@AlgirdasPreidžius 升级代码以处理负幂并不难,其思路保持不变。 - lisyarus
1
@lisyarus 的想法是对的,但不能简单地在其中加入 if 或三元表达式,因为两个分支都需要被解析,这会导致无限递归。为了使同一模板能够处理正数和负数,需要进行更多的操作,而不仅仅是所示的内容。 - Algirdas Preidžius
@AlgirdasPreidžius 你说得完全正确,这需要一些模板机制来工作,虽然是相当标准的机制,但可能对OP不熟悉。 - lisyarus

0

由于您需要在编译时可用的值,我脑海中唯一想到的解决方法就是递归模板。然而,您需要让所述模板根据传递值的符号执行不同的操作,这使事情变得复杂。首先想到的是编写这样一个递归模板:

template <int exp>
struct DoubleValue
    {
    static constexpr double value = (exp < 0
                                     ? DoubleValue<exp+1>::value / 10
                                     : 10 * DoubleValue<exp-1>::value);
    };

// Default case
template <>
struct DoubleValue<0>
    {
    static constexpr double value = 1;
    };

然而,这样的解决方案行不通,因为三元表达式的两个分支都需要被解析,这将始终导致无限递归,因为其中一个分支不会趋近于0。然后,SFINAE浮现在脑海中:

// Base case.
template <int exp, class Enable = void>
struct DoubleValue
    {
    };

// Case when exp is positive
template <int exp>
struct DoubleValue<exp, typename std::enable_if<(exp > 0)>::type>
    {
    static constexpr double value = 10 * DoubleValue<exp-1>::value;
    };

// Case when exp is negative
template <int exp>
struct DoubleValue<exp, typename std::enable_if<(exp < 0)>::type>
    {
    static constexpr double value = DoubleValue<exp+1>::value / 10;
    };

// Default case.
template <>
struct DoubleValue<0>
    {
    static constexpr double value = 1;
    };

演示现场


这绝不是唯一的方式 - 请参考@sebrockm的回答。 - lisyarus
@lisyarus 当然,我的意思是“我能想到的唯一方法”。编辑以澄清。 - Algirdas Preidžius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接