Boost C++ 库
...这是世界上最受尊敬、设计最精良的 C++ 库项目之一。
— Herb Sutter 和 Andrei Alexandrescu, C++ Coding Standards
Boost.Ratio 是 Boost 的一部分,因此您可以在 Boost 发布版中获得它。
Boost.Ratio 是一个仅头文件库,因此无需编译任何内容,只需 include <boost/ratio.hpp> 即可。
无需链接。
库中的所有函数都是异常中立的,并提供强大的异常安全保证。
除非另有明确说明,库中的所有函数都是线程不安全的。
Boost.Ratio 应与任何符合 C++11 标准的编译器一起使用。
ratio 是一个通用实用程序,灵感来自 Walter Brown,允许用户在编译时轻松安全地计算有理数值。ratio 类会在编译时捕获所有错误(如除以零和溢出)。它用于 duration 和 time_point 类,以有效地创建时间单位。它也可以用于其他“数量”库或任何已知编译时有理数常数的地方。使用此实用程序可以大大降低运行时溢出的可能性,因为 ratio(以及由 ratio 算术产生的任何 ratio)始终被简化为最低项。
ratio 是一个模板,接受两个 intmax_ts,第二个默认值为 1。除了拷贝构造函数和赋值运算符,它只有两个公共成员,这两个成员都是 static const intmax_t。一个是 ratio 的分子,另一个是分母。ratio 始终被规范化,使其表示为最低项,并且分母始终为正。当分子为 0 时,分母始终为 1。
示例
typedefratio<5, 3> five_thirds; // five_thirds::num == 5, five_thirds::den == 3 typedefratio<25, 15> also_five_thirds; // also_five_thirds::num == 5, also_five_thirds::den == 3 typedef ratio_divide<five_thirds, also_five_thirds>::type one; // one::num == 1, one::den == 1
此功能还包括用于 SI 前缀的便捷类型别名,从 atto 到 exa,对应于它们在国际上公认的定义(以 ratio 的形式)。这是一个巨大的语法便利。它将防止在指定常量时出错,因为在尝试写百万或十亿时,用户不再需要重复计算零的数量。
示例
typedef ratio_multiply<ratio<5>, giga>::type _5giga; // _5giga::num == 5000000000, _5giga::den == 1 typedef ratio_multiply<ratio<5>, nano>::type _5nano; // _5nano::num == 1, _5nano::den == 200000000
对于每个 ratio<N, D>,都存在一个 ratio_string<ratio<N, D>, CharT>,您可以通过它查询两个字符串:symbol 和 prefix。对于那些对应于 SI 前缀 的 ratio,prefix 对应于国际公认的前缀,存储为 basic_string<CharT>。例如,ratio_string<mega, char>::prefix() 返回 string("mega")。对于那些对应于 SI 前缀 的 ratio,symbol 对应于国际公认的符号,存储为 basic_string<CharT>。例如,ratio_string<mega, char>::symbol() 返回 string("M")。对于所有其他 ratio,prefix() 和 symbol() 都返回一个包含 "[ratio::num/ratio::den]" 的 basic_string。
ratio_string<ratio<N, D>, CharT> 仅对四种字符类型定义:
char:UTF-8char16_t:UTF-16char32_t:UTF-32wchar_t:UTF-16(如果 wchar_t 是 16 位)或 UTF-32当字符为 char 时,将使用 UTF-8 编码名称。当字符为 char16_t 时,将使用 UTF-16 编码名称。当字符为 char32_t 时,将使用 UTF-32 编码名称。当字符为 wchar_t 时,如果 wchar_t 是 16 位,则编码为 UTF-16,否则为 UTF-32。
symbol(希腊字母 mu 或 μ)用于微,由 Unicode 定义为 U+00B5。
示例
#include <boost/ratio/ratio_io.hpp> #include <iostream> int main() { using namespace std; using namespace boost; cout << "ratio_string<deca, char>::prefix() = " << ratio_string<deca, char>::prefix() << '\n'; cout << "ratio_string<deca, char>::symbol() = " << ratio_string<deca, char>::symbol() << '\n'; cout << "ratio_string<giga, char>::prefix() = " << ratio_string<giga, char>::prefix() << '\n'; cout << "ratio_string<giga, char>::symbol() = " << ratio_string<giga, char>::symbol() << '\n'; cout << "ratio_string<ratio<4, 6>, char>::prefix() = " << ratio_string<ratio<4, 6>, char>::prefix() << '\n'; cout << "ratio_string<ratio<4, 6>, char>::symbol() = " << ratio_string<ratio<4, 6>, char>::symbol() << '\n'; }
输出将是:
ratio_string<deca, char>::prefix() = deca ratio_string<deca, char>::symbol() = da ratio_string<giga, char>::prefix() = giga ratio_string<giga, char>::symbol() = G ratio_string<ratio<4, 6>, char>::prefix() = [2/3] ratio_string<ratio<4, 6>, char>::symbol() = [2/3]
此示例演示了如何使用类型安全的物理代码与 boost::chrono::duration 类型进行互操作,并利用 Boost.Ratio 的基础设施和设计理念。
让我们从定义一个 length 类模板开始,该模板模仿 boost::chrono::duration,它表示各种单位的时间持续时间,但将表示限制为 double,并使用 Boost.Ratio 进行长度单位转换。
template <class Ratio> class length { private: double len_; public: typedef Ratio ratio; length() : len_(1) {} length(const double& len) : len_(len) {} template <class R> length(const length<R>& d) : len_(d.count() * boost::ratio_divide<Ratio, R>::type::den / boost::ratio_divide<Ratio, R>::type::num) {} double count() const {return len_;} length& operator+=(const length& d) {len_ += d.count(); return *this;} length& operator-=(const length& d) {len_ -= d.count(); return *this;} length operator+() const {return *this;} length operator-() const {return length(-len_);} length& operator*=(double rhs) {len_ *= rhs; return *this;} length& operator/=(double rhs) {len_ /= rhs; return *this;} };
这里是一些长度单位的小样本
typedef length<boost::ratio<1> > meter; // set meter as "unity" typedef length<boost::centi> centimeter; // 1/100 meter typedef length<boost::kilo> kilometer; // 1000 meters typedef length<boost::ratio<254, 10000> > inch; // 254/10000 meters
请注意,由于 length 的模板参数实际上是一个通用的 ratio 类型,因此我们可以使用 boost::ratio,从而支持更复杂的长度单位。
typedef length<boost::ratio_multiply<boost::ratio<12>, inch::ratio>::type> foot; // 12 inchs typedef length<boost::ratio_multiply<boost::ratio<5280>, foot::ratio>::type> mile; // 5280 feet
现在我们需要基于浮点数的秒的定义。
typedef boost::chrono::duration<double> seconds; // unity
我们甚至可以支持亚纳秒的持续时间。
typedef boost::chrono::duration<double, boost::pico> picosecond; // 10^-12 seconds typedef boost::chrono::duration<double, boost::femto> femtosecond; // 10^-15 seconds typedef boost::chrono::duration<double, boost::atto> attosecond; // 10^-18 seconds
最后,我们可以编写一个 SI 单位库的概念验证,该库专门用于米和浮点秒,尽管它也会接受其他单位。
template <class R1, class R2> class quantity { double q_; public: typedef R1 time_dim; typedef R2 distance_dim; quantity() : q_(1) {} double get() const {return q_;} void set(double q) {q_ = q;} }; template <> class quantity<boost::ratio<1>, boost::ratio<0> > { double q_; public: quantity() : q_(1) {} quantity(seconds d) : q_(d.count()) {} // note: only User1::seconds needed here double get() const {return q_;} void set(double q) {q_ = q;} }; template <> class quantity<boost::ratio<0>, boost::ratio<1> > { double q_; public: quantity() : q_(1) {} quantity(meter d) : q_(d.count()) {} // note: only User1::meter needed here double get() const {return q_;} void set(double q) {q_ = q;} }; template <> class quantity<boost::ratio<0>, boost::ratio<0> > { double q_; public: quantity() : q_(1) {} quantity(double d) : q_(d) {} double get() const {return q_;} void set(double q) {q_ = q;} };
这使我们可以创建一些有用的基于 SI 的单位类型。
typedef quantity<boost::ratio<0>, boost::ratio<0> > Scalar; typedef quantity<boost::ratio<1>, boost::ratio<0> > Time; // second typedef quantity<boost::ratio<0>, boost::ratio<1> > Distance; // meter typedef quantity<boost::ratio<-1>, boost::ratio<1> > Speed; // meter/second typedef quantity<boost::ratio<-2>, boost::ratio<1> > Acceleration; // meter/second^2
为了使数量有用,我们需要能够进行算术运算。
template <class R1, class R2, class R3, class R4> quantity<typename boost::ratio_subtract<R1, R3>::type, typename boost::ratio_subtract<R2, R4>::type> operator/(const quantity<R1, R2>& x, const quantity<R3, R4>& y) { typedef quantity<typename boost::ratio_subtract<R1, R3>::type, typename boost::ratio_subtract<R2, R4>::type> R; R r; r.set(x.get() / y.get()); return r; } template <class R1, class R2, class R3, class R4> quantity<typename boost::ratio_add<R1, R3>::type, typename boost::ratio_add<R2, R4>::type> operator*(const quantity<R1, R2>& x, const quantity<R3, R4>& y) { typedef quantity<typename boost::ratio_add<R1, R3>::type, typename boost::ratio_add<R2, R4>::type> R; R r; r.set(x.get() * y.get()); return r; } template <class R1, class R2> quantity<R1, R2> operator+(const quantity<R1, R2>& x, const quantity<R1, R2>& y) { typedef quantity<R1, R2> R; R r; r.set(x.get() + y.get()); return r; } template <class R1, class R2> quantity<R1, R2> operator-(const quantity<R1, R2>& x, const quantity<R1, R2>& y) { typedef quantity<R1, R2> R; R r; r.set(x.get() - y.get()); return r; }
有了以上所有基础架构,我们现在可以编写一个类型安全的物理函数示例。
Distance compute_distance(Speed v0, Time t, Acceleration a) { return v0 * t + Scalar(.5) * a * t * t; // if a units mistake is made here it won't compile }
最后,我们可以测试我们创建的内容,甚至可以使用自定义时间持续时间(User1::seconds)以及 Boost 时间持续时间(boost::chrono::hours)。输入可以是任意的、类型安全的单位,输出始终是 SI 单位。(当然,一个完整的单位库会支持其他单位。)
int main() { typedef boost::ratio<8, BOOST_INTMAX_C(0x7FFFFFFFD)> R1; typedef boost::ratio<3, BOOST_INTMAX_C(0x7FFFFFFFD)> R2; typedef User1::quantity<boost::ratio_subtract<boost::ratio<0>, boost::ratio<1> >::type, boost::ratio_subtract<boost::ratio<1>, boost::ratio<0> >::type > RR; typedef boost::ratio_subtract<R1, R2>::type RS; std::cout << RS::num << '/' << RS::den << '\n'; std::cout << "*************\n"; std::cout << "* testUser1 *\n"; std::cout << "*************\n"; User1::Distance d( User1::mile(110) ); User1::Time t( boost::chrono::hours(2) ); RR r=d / t; //r.set(d.get() / t.get()); User1::Speed rc= r; User1::Speed s = d / t; std::cout << "Speed = " << s.get() << " meters/sec\n"; User1::Acceleration a = User1::Distance( User1::foot(32.2) ) / User1::Time() / User1::Time(); std::cout << "Acceleration = " << a.get() << " meters/sec^2\n"; User1::Distance df = compute_distance(s, User1::Time( User1::seconds(0.5) ), a); std::cout << "Distance = " << df.get() << " meters\n"; std::cout << "There are " << User1::mile::ratio::den << '/' << User1::mile::ratio::num << " miles/meter"; User1::meter mt = 1; User1::mile mi = mt; std::cout << " which is approximately " << mi.count() << '\n'; std::cout << "There are " << User1::mile::ratio::num << '/' << User1::mile::ratio::den << " meters/mile"; mi = 1; mt = mi; std::cout << " which is approximately " << mt.count() << '\n'; User1::attosecond as(1); User1::seconds sec = as; std::cout << "1 attosecond is " << sec.count() << " seconds\n"; std::cout << "sec = as; // compiles\n"; sec = User1::seconds(1); as = sec; std::cout << "1 second is " << as.count() << " attoseconds\n"; std::cout << "as = sec; // compiles\n"; std::cout << "\n"; return 0; }
请参阅源文件 example/si_physics.cpp
该库最权威的参考资料是 C++ 标准委员会当前的《工作草案》(WP)。20.6 编译时有理数算术“ratio”
来自 Howard E. Hinnant、Walter E. Brown、Jeff Garland 和 Marc Paterno。内容非常丰富,并为关键设计决策提供了动机。
来自 Vicente Juan Botet Escriba。