我目前正在研究基于模板元编程的浮点算法实现。表示编译时浮点值的模板如下:
template<bool S , std::int16_t E , std::uint64_t M>
struct number{};
由于使用硬编码的尾数,指数等来初始化这些值是一个麻烦且容易出错的过程,因此我编写了一个模板,用于将十进制值转换为浮点数:
template<std::int64_t INT , std::uint64_t DECS>
struct decimal{};
其中第一个参数代表整数部分,第二个参数代表小数位。我认为这是一种常见且众所周知的方式。
但是,这种模式存在一些问题(如何输入负的小于一的数字?),其中最让我烦恼的是这样一个事实,即在逗号后无法输入零数字,即像
0.00032
。我知道C++ 11,我在考虑一种用户定义的文字+
decltype()
方法(即使使用宏#define FLOAT(x) decltype(x_MY_LITERAL)
),但我不确定该方法是否在所有情况下都可行,我的意思是,如果字面量+ decltype可在模板参数的上下文中求值。即使可行,我也想知道是否还有其他解决此问题的方法。那么,在通过tmp进行编译时,像浮点一样的初始化有哪些替代方案?
尝试的替代方法:
仅出于完整性考虑,我将描述我已实现的替代方案,它们如何工作以及其const和优点。问题本身仍然是开放的,以允许任何人添加更多替代方案。
一些背景
首先,我将描述我所使用的功能,以确保每个人都了解代码。
我的库The Turbo Metaprogramming Library基于三个原则:
tml::eval
元函数,该函数接受任何类型的表达式并返回(评估)其值。 typename ::type
构造时。我的约定是在嵌套的impl
命名空间上定义实现模板(真正起作用的元函数),并在当前命名空间上为结果定义C++ 11模板别名。由于此类别名直接返回结果,因此无法在复杂表达式上求值(请考虑元函数实例化add<X,Y>
,其中X
和Y
是lambda的变量。如果add
是结果的别名,那将不起作用,因为求值没有意义,如果我们需要表达式(元函数)而不是直接返回结果,我的约定是在func
嵌套 namespace 上为元函数添加别名。这里有些例子:
using bits = tml::util::sizeof_bits<int>; //bits is a size_t integral constant with the
//size on bits of an int
//A metafunction which returns the size on bits of a type doubled
using double_size = tml::lambda<_1 , tml::mul<tml::util::func::sizeof_bits<_1>,tml::Int<2>> >;
using int_double_size = tml::eval<double_size,int>; //Read as "double_size(int)"
tml
是该库的主要 namespace ,并且浮点功能在tml::floating
namespace 中公开。TL; DR
tml::eval
接受任何表达式并对其求值,并返回其值。它是C++ 11模板别名,因此不需要typename ::type
。 tml::integral_constant
(只是std::integral_constant
的别名)是实际值包装器,用于通过装箱将值参数作为类型参数传递。该库仅使用类型参数作为惯例(模板模板参数也有包装器,请参见 tml::lazy
和 tml::bind
)。 尝试1:从整数开始
在这里,我们定义一个元函数
integer
,该函数从整数1返回浮点值:template<std::int64_t mantissa , sign_t S = (sign_t)(mantissa >= 0)>
struct integer
{
using m = tml::floating::number<S,0,static_cast<mantissa_t>((mantissa >= 0) ? mantissa : -mantissa)>;
using hsb = tml::floating::highest_set_bit<m>;
static constexpr const exponent_t exp = hsb::value - 31;
using result = tml::floating::number<S,exp,(m::mantissa << (31 - hsb::value))>; //Note the number is normalized
};
它的作用是直接取整数值,将其用作尾数,然后对数字进行标准化,以明确计算最高(最高有效)设置位,从而相应地移动尾数。
其用法的一个示例可能是:
using ten = tml::floating::integer<10>;
优点:
highest_set_bit
的调用。 缺点:
尝试2:十进制初始化
此替代方法使用一对整数值分别表示数字的整数和小数部分:
template<std::int64_t INTEGRAL , std::uint64_t FRACTIONAL>
struct decimal{ ... };
using pi = decimal<3,141592654>;
它的作用是计算整数部分的值(只需调用
integer
,即先前的尝试)和小数部分的值。小数部分的值是调整的整数值,直到小数点位于数字的开头。换一种说法:
integer<fractional_part>
fractional_value = ________________________________
10^number_of_digits
那么数字的值就是两个值的总和:
result = integer_part_value + fractional_value
整数的位数是
log10(number) + 1
。我最后得到了log10
元函数,该整数函数不需要递归:template<typename N>
struct log10
{
using result = tml::Int<(0 <= N::value && N::value < 10) ? 0 :
(10 <= N::value && N::value < 100) ? 1 :
...
>;
}
因此它具有O(1)复杂度(当然,是测量模板实例化深度)。
使用此元功能,以上公式变为:
//First some aliases, to make the code more handy:
using integral_i = tml::integral_constant<std::int64_t,INTEGRAL>;
using integral_f = tml::floating::integer<INTEGRAL>;
using fractional_f = tml::floating::integer<FRACTIONAL>;
using ten = tml::floating::integer<10>;
using one = tml::Int<1>;
using fractional_value = tml::eval<tml::div<fractional_f ,
tml::pow<ten,
tml::add<tml::log10<integral_i>,
one
>
>
>
>
然后的结果是:
using result = tml::eval<tml::add<integral_f,fractional_value>>;
优点
12.123
。 缺点:
tml::pow
是递归的,复杂度为O(n)。浮点值的tml::div
实现为分子乘以分母的倒数。该倒数是通过牛顿-拉夫森近似法计算的(默认为五次迭代)。 13.0004
无效,因此无法指定点后带有尾随零的数字,例如0004
。 尝试3(3.1和3.2):十进制科学计数法
代替使用硬编码数字写数字,我们使用十进制(10的幂)科学计数法来初始化浮点数:
using pi = tml::floating::decimal_sci<3141592654,-9>; //3141592654 x 10^-9
要计算数字,您只需要取有效数字的值,然后乘以相应的10的幂即可:
template<std::int64_t S , std::int64_t E>
struct decimal_sci
{
using significant = tml::floating::integer<S>;
using power = tml::eval<tml::pow<tml::floating::integer<10>,tml::Int<E>>>;
using result = tml::eval<tml::mul<significant,power>>;
};
此尝试有一个改进,如果仅将给定的有效位标准化为一个整数,就可以对其进行处理。因此,值
0.0034565432
可以写为(34565432 , -3)
而不是(34565432 , -11)
。我称之为
tml::floating::decimal_scinorm
:template<std::int64_t S , std::int64_t E = 0>
struct decimal_scinorm
{
using significant_i = tml::integral_constant<std::int64_t,S>;
using exponent_i = tml::integral_constant<std::int64_t,E>;
using adjust = tml::eval<tml::log10<significant_i>>;
using new_exp = tml::eval<tml::sub<exponent_i,adjust>>;
using result = typename decimal_sci<S,new_exp::value>::result;
};
using pi = tml::floating::decimal_scinorm<3141592654>; //3.141592654
using i = tml::floating::decimal_scinorm<999999,-4>; //0.000999999
优点
缺点
tml::pow
,tml::div
等的使用)。 最佳答案
您可能要使用用户定义的文字。根据cppreference.com,它
Allows integer, floating-point, character, and string literals to produce objects of user-defined type by defining a user-defined suffix.
(另请参见http://en.cppreference.com/w/cpp/language/user_literal)。这样,您可以表达
123.456_mysuffix
如果为_mysuffix定义文字运算符,则产生所需的任何类型。使用该运算符,您可以作为(标准c++)浮点数访问输入123.456,也可以自己将原始字符串作为const char *进行必要的转换。
编辑:阅读完您编辑过的问题并意识到您正在谈论的模板元编程类型之后,我只是想强调一下,也可以将文字作为
char
模板参数的参数包进行访问。您可能可以将其集成到编译时框架中。
关于c++ - 编译时浮点初始化的替代方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25272844/