细说C++11中ratio编译期分数(一)

xiaoxiao2021-04-18  74

对于分数,C++的标准库并没有提供这样的一个类,如果需要完全可以自己去实现,但是C++11提供了一个编译期常量分数类std::ratio,这个类定义于<ratio>头文件中。

这个类允许你具体指定编译期分数,并允许对它们执行编译期运算(可以化简为最简式),而且,它是编译期安全的,这个在源码中可以看到,类似分母为0这种异常在编译期就能捕获。

先来看看如何使用这个std::ratio:

template<long long N, long long D> std::ostream & operator << (std::ostream & os, const std::ratio<N, D> && r) { return os << r.num << " / " << r.den << std::endl; } int main() { std::cout << std::ratio<5, 3>(); std::cout << std::ratio<2, 2>(); std::cout << std::ratio<0, 3>(); std::cout << std::ratio<4, 6>(); std::cout << std::ratio<-4, 6>(); std::cout << std::ratio<4, -6>(); std::cout << std::ratio<-4, -6>(); return 0; }

这个Demo可以详细显示出std::ratio的特性。

那么ratio是如何实现的呢,先看源码:

/** * @brief Provides compile-time rational arithmetic. * * This class template represents any finite rational number with a * numerator and denominator representable by compile-time constants of * type intmax_t. The ratio is simplified when instantiated. * * For example: * @code * std::ratio<7,-21>::num == -1; * std::ratio<7,-21>::den == 3; * @endcode * */ template<intmax_t _Num, intmax_t _Den = 1> struct ratio { static_assert(_Den != 0, "denominator cannot be zero"); static_assert(_Num >= -__INTMAX_MAX__ && _Den >= -__INTMAX_MAX__, "out of range"); // Note: sign(N) * abs(N) == N static constexpr intmax_t num = _Num * __static_sign<_Den>::value / __static_gcd<_Num, _Den>::value; static constexpr intmax_t den = __static_abs<_Den>::value / __static_gcd<_Num, _Den>::value; typedef ratio<num, den> type; }; template<intmax_t _Num, intmax_t _Den> constexpr intmax_t ratio<_Num, _Den>::num; template<intmax_t _Num, intmax_t _Den> constexpr intmax_t ratio<_Num, _Den>::den;

可以看到,ratio只有两个静态常量成员变量:

分子num分母den

然后看看它们的值:

static constexpr intmax_t num = _Num * __static_sign<_Den>::value / __static_gcd<_Num, _Den>::value; static constexpr intmax_t den = __static_abs<_Den>::value / __static_gcd<_Num, _Den>::value;

可以看到,分子分母都有化简,化简的原理就是同时除以它们的最大公约数;

而且,从源码中可以看出来,ratio化简时会把分母的负号移到分子上。

因此,类外有这两个变量的定义:

template<intmax_t _Num, intmax_t _Den> constexpr intmax_t ratio<_Num, _Den>::num; template<intmax_t _Num, intmax_t _Den> constexpr intmax_t ratio<_Num, _Den>::den;

之前有提过ratio是编译期安全的,这多亏C++11的新特性static_assert,编译期断言。

static_assert(_Den != 0, "denominator cannot be zero"); static_assert(_Num >= -__INTMAX_MAX__ && _Den >= -__INTMAX_MAX__, "out of range");

这两条语句就在编译期检查了分母是否为零,数值是否溢出。

ratio到这个地方似乎就完了,但是代码中出现的__static_gcd等还没有解释,一起来看看这一连串源码:

首先声明,这里大量用到type_traits的基石——integral_constant,具体可以参考C++11中type_traits中的基石 - integral_constant。这些都是integral_constant的应用,可以好好体会体会。

__static_sign

编译期求出符号

template<intmax_t _Pn> struct __static_sign : integral_constant<intmax_t, (_Pn < 0) ? -1 : 1> { };

__static_abs

编译期求出绝对值,利用了__static_sign求出符号,然后利用负负得正的性质得到绝对值。

template<intmax_t _Pn> struct __static_abs : integral_constant<intmax_t, _Pn * __static_sign<_Pn>::value> { };

__static_gcd

编译期求出两个常量的最大公约数。

这个是最有味的,利用了模板的递归继承加上欧几里得递归算法实现,递归的终点偏特化模板,继承自integral_constant,最终__static_gcd<6, 4>::value == 2。

template<intmax_t _Pn, intmax_t _Qn> struct __static_gcd : __static_gcd<_Qn, (_Pn % _Qn)> { }; /// partial specialization template<intmax_t _Pn> struct __static_gcd<_Pn, 0> : integral_constant<intmax_t, __static_abs<_Pn>::value> { }; template<intmax_t _Qn> struct __static_gcd<0, _Qn> : integral_constant<intmax_t, __static_abs<_Qn>::value> { };

到这里,对ratio介绍才真正告一段落,但是,标准库定义出ratio,那就必然还会有一些辅助类来为ratio添砖加瓦。例如,分数可以加减乘除,这里留在细说C++11中ratio编译期分数(一)再细说。

还有最后一点东西,就是下面这个表格了,这是标准库中为了方便用户做出来的类型重定义:

名称单位 y o c t o yocto yocto 1 1 , 000 , 000 , 000 , 000 , 000 , 000 \cfrac{1}{1,000,000,000,000,000,000} 1,000,000,000,000,000,0001 f e m t o femto femto 1 1 , 000 , 000 , 000 , 000 , 000 \cfrac{1}{1,000,000,000,000,000} 1,000,000,000,000,0001 p i c o pico pico 1 1 , 000 , 000 , 000 , 000 \cfrac{1}{1,000,000,000,000} 1,000,000,000,0001 n a n o nano nano 1 1 , 000 , 000 , 000 \cfrac{1}{1,000,000,000} 1,000,000,0001 m i c r o micro micro 1 1 , 000 , 000 \cfrac{1}{1,000,000} 1,000,0001 m i l l i milli milli 1 1 , 000 \cfrac{1}{1,000} 1,0001 c e n t i centi centi 1 100 \cfrac{1}{100} 1001 d e c i deci deci 1 10 \cfrac{1}{10} 101 d e c a deca deca 10 10 10 h e c t o hecto hecto 100 100 100 k i l o kilo kilo 1 , 000 1,000 1,000 m e g a mega mega 1 , 000 , 000 1,000,000 1,000,000 g i g a giga giga 1 , 000 , 000 , 000 1,000,000,000 1,000,000,000 t e r a tera tera 1 , 000 , 000 , 0000 , 000 1,000,000,0000,000 1,000,000,0000,000 p e t a peta peta 1 , 000 , 000 , 0000 , 000 , 000 1,000,000,0000,000,000 1,000,000,0000,000,000 e x a exa exa 1 , 000 , 000 , 0000 , 000 , 000 , 000 1,000,000,0000,000,000,000 1,000,000,0000,000,000,000

代码如下:

typedef ratio<1, 1000000000000000000> atto; typedef ratio<1, 1000000000000000> femto; typedef ratio<1, 1000000000000> pico; typedef ratio<1, 1000000000> nano; typedef ratio<1, 1000000> micro; typedef ratio<1, 1000> milli; typedef ratio<1, 100> centi; typedef ratio<1, 10> deci; typedef ratio< 10, 1> deca; typedef ratio< 100, 1> hecto; typedef ratio< 1000, 1> kilo; typedef ratio< 1000000, 1> mega; typedef ratio< 1000000000, 1> giga; typedef ratio< 1000000000000, 1> tera; typedef ratio< 1000000000000000, 1> peta; typedef ratio< 1000000000000000000, 1> exa;
转载请注明原文地址: https://www.6miu.com/read-4820237.html

最新回复(0)