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

对于分数,C++的标准库并没有提供这样的一个类,如果需要完全可以自己去实现,但是C++11提供了一个编译期常量分数类std::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;

你可能感兴趣的:(C/C++技巧)