对于分数,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;