编译时浮点初始化的替代方法

时间:2014-08-12 19:46:46

标签: c++ templates c++11 floating-point template-meta-programming

我目前正致力于基于模板元编程的浮点运算实现。表示编译时浮点值的模板如下:

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进行浮点式初始化有哪些替代方法?


尝试替代方案:

为了完整性,我将描述我已经实施的替代方案,它们如何运作,以及它的功能和优点。问题本身仍然是开放的,允许任何人添加更多的选择。

一些背景

首先,我将介绍我使用过的功能,以确保每个人都能理解代码。

我的图书馆The Turbo Metaprogramming Library基于三个原则:

  • 仅键入模板参数:完全通用的混合类型参数,值参数和模板模板参数非常难(几乎不可能),因此该库仅使用类型参数。每当需要使用值或模板时,库提供包装器以通过装箱传递这些参数。

  • 统一表达式评估:在编程语言中工作时的首要任务之一是评估表达式并获取其值的方法。 Turbo提供tml::eval元函数,它接受任何类型的表达式并返回(计算)其值。

  • 通过模板专业化定制的通用算法和元函数:每当我可以使用C ++ 11模板别名来避免繁琐的typename ::type构造。我的惯例是在嵌套的impl命名空间上定义实现模板(真正完成工作的元函数),并在当前命名空间上定义结果的C ++ 11模板别名。由于此类别名直接返回结果,因此无法对复杂表达式求值(考虑元函数瞬时add<X,Y>,其中XY是lambda的变量。如果{{1} }是结果的别名,因为评估没有意义,所以不起作用。如果我们需要表达式(元函数)而不是直接的结果,我的惯例就是在{{{{{{{ 1}}嵌套命名空间。

以下是一些例子:

add

func是库的主命名空间,浮点功能在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)" 命名空间中公开。

TL; DR

  • tml接受任何表达式并对其求值,并返回其值。它是C ++ 11模板别名,因此不需要tml::floating

  • tml::eval(只是typename ::type的别名)是事实上的值包装器,用于通过装箱将值参数作为类型参数传递。该库具有仅使用类型参数的约定(模板模板参数也有包装,请参阅tml::lazytml::bind)。

尝试1:从整数

这里我们定义一个元函数tml::integral_constant,它从整数1返回一个浮点值:

std::integral_constant

它的作用是直接取整数值,将其用作尾数,并将显式计算最高(最重要)设定位的数字标准化,相应地改变尾数。

它的一个例子可能是:

integer

优点:

  • 效率:无需额外的复杂计算即可获得等效的浮点数。唯一相关的操作是调用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 };

  • 默认情况下,该数字是标准化的(关于效率也是如此)。此外,没有精确问题(至少不是小值)。

缺点:

  • 仅适用于整数值。

尝试2:十进制初始化

此替代方法使用一对整数值分别表示数字的整数部分和小数部分:

using ten = tml::floating::integer<10>;

它的作用是计算整数部分的值(只需调用highest_set_bit,前一次尝试)和小数部分的值。
小数部分的值是调整的整数值,直到小数点位于数字的开头。换句话说:

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

因此它具有O(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 :
                            ...
                           >;
} 

优点

  • 允许实例化非整数值,例如//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>>; 是递归的,复杂度为O(n)。浮点值的12.123实现为分子乘以分母的倒数。该倒数通过Newton-Raphson近似计算(默认为五次迭代)。

  • 精确度问题:为计算功率而进行的连续乘法可能会导致累积的次要精度问题。用于计算除法的Newton-Raphson近似值相同。

  • 符号有限:无法指定点之后带尾随零的数字,例如tml::pow,因为整数文字tml::div不是有效。

尝试3(3.1和3.2):十进制科学记数法

我们使用十进制(10次幂)科学记数法来初始化浮点数,而不是使用硬编码数字来编写数字:

13.0004

要计算数字,您只需要取有效值的值,然后乘以相应的10的幂:

0004

此尝试有一项改进,如果将其标准化为仅一个整数,则会对给定的有效进行处理。因此,值using pi = tml::floating::decimal_sci<3141592654,-9>; //3141592654 x 10^-9 可以写为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_scinormtemplate<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 等的使用情况。)

1 个答案:

答案 0 :(得分:8)

您可能希望使用用户定义的文字。根据cppreference.com,它

  

允许整数,浮点,字符和字符串文字通过定义用户定义来生成用户定义类型的对象   后缀。

(另见http://en.cppreference.com/w/cpp/language/user_literal)。这样,你就可以制作表达式

123.456_mysuffix

如果为_mysuffix定义文字运算符,则会生成所需的任何类型。使用该运算符,您可以作为(标准c ++)浮点数访问输入123.456,或者您可以自己从原始字符串转换为const char *。

编辑:在阅读您编辑的问题并实现您正在讨论的模板元编程之后,我只想强调文字也可以作为char模板参数的参数包进行访问。您可以将它集成到编译时框架中。