Boost Units 库。






4.92/5 (19投票s)
对 boost units 库的介绍。
引言
不久前,我开始编写一个发动机调谐应用程序。开发过程很顺利,但随着时间的推移,出现了一些烦人的错误——例如,将毫巴(millibars)的空气压力传递给了期望帕斯卡(pascals)的函数。函数输入并非总是完全文档化(好吧……代码是为我个人使用的),有时文档也是错误的。我突然想到,如果能利用编译器捕获概念上的类型不匹配错误,那将是更好的。
Boost Units 库似乎正是解决我问题的最佳方案。
量纲分析
量纲分析假设量具有潜在的量纲。这在大学工程和科学课程中作为检查公式正确性的方法进行教学。长度、时间、质量、温度是“基本”量纲。其他量纲可以从它们组合而成。例如,速度是米/秒(meters/sec)测量的派生量纲。能量也是派生量纲,以千克·米²/秒²(kilogram*meters2/second2)为单位测量。
基本量纲的选择有些随意。Boost Units 库为 SI(公制)系统定义了以下基本量纲和单位:
- 电流(安培)
- 光强度(坎德拉)
- 温度(开尔文)
- 质量(千克)
- 时间(秒)
- 长度(米)
- 原子/分子数(摩尔)
SI 系统并非唯一的可能系统。例如,可以定义一个使用更少量纲和不同单位的系统,如英尺、秒、磅和华氏度。
公式必须在量纲上一致——将一英里与一千克相加是没有意义的。Boost Units 库为具有相同量纲的单位定义了加、减、乘和除。以下代码可以正常工作:
quantity<mass> m = 10.0 * kilograms; // dim(mass) = M
quantity<acceleration> a = 9.8 * meter_per_second_squared; // dim(acceleration) = L.T^-2
quantity<force> f = m * a; // dim(force) = M.L.T^-2
以下代码在量纲上不正确,将无法编译:
quantity<mass> m = 10.0 * kilograms;
quantity<acceleration> a = 9.8 * meter_per_second_squared;
quantity<force> f = m * a * a; // <== error! Won't compile. dim(LHS) does not equal dim(RHS)
那么,所有这些魔法是如何实现的呢?答案是 Boost 元编程库(MPL)。幸运的是,像所有好的库一样,使用它并不需要理解其实现的所有细节。然而,像所有好的库一样,如果你真的想充分利用它,你确实需要熟悉它的内部构造。
SI 系统定义了广泛的派生量纲和相关的单位,只需包含si.hpp 即可访问。
#include <boost/units/systems/si.hpp>
...
void example_declarations()
{
quantity<length> length = 2.5 * meters;
quantity<velocity> velocity = 2.5 * meters_per_second;
quantity<mass> m = 2.5 * kilogram;
quantity<si::time> t(2.5 * si::seconds); // name clash with std::time, std::second.
quantity<pressure> pressure(101300.0 * pascals);
quantity<mass_density> density(50.0 * kilogrammes_per_cubic_metre);
quantity<temperature> temp(285.15 * kelvin);
}
Units 提供的更强的类型检查也可以捕获如下所示的错误。
double density(double T, double P, double humidity); // prototype
double T = 20.0; // celsius
double P = 101300.0; // pascals
double air_density = density(P, T, humidity); // <== T and P are swapped around!
使用 Units...
quantity<mass_density> density(quantity<temperature> T, quantity<pressure> P, double humidity); // prototype
quantity<temperature> T = 292.15 * kelvin;
quantity<pressure> P = 101300.0 * pascals;
quantity<mass_density> air_density = density(P, T, humidity); // error! Wont compile. T and P are swapped around!
新类型
通常不需要超出提供的量纲/单位,但这样做相对直接。代码包含一个支持阿伏伽德罗常数的类型定义。标准过程是:
- 定义派生量纲,
- 定义相关单位,以及
- 定义类型。
typedef derived_dimension<length_base_dimension, 2,
time_base_dimension, -2,
temperature_base_dimension, -1>::type Avogadros_constant_dim;
typedef unit<Avogadros_constant_dim, si::system> Avogadros_constant_unit;
typedef quantity<Avogadros_constant_unit> Avogadros_constant_type;
或者,可以根据实例定义一个类型:
const auto Rd = 287.05 * joules/(kilogram * kelvin); // dry air
const auto Rv = 461.495 * joules/(kilogram * kelvin); // water vapour
typedef decltype(Rd) Avogadros_constant_type;
新单位
有时使用非 SI 单位可能很方便,这些单位几乎总是与 SI 单位相差一个常数因子。该库提供了一个简单的模板来定义缩放单位。
// Definition of millibars (1 millibar = 100 pascals)
typedef make_scaled_unit<si::pressure, scale<10, static_rational<2> > >::type millibars;
const auto mbars = 100 * pascals;
quantity<millibars> mb(10 * mbars);
quantity<pressure> P(mb);
std::cout << "P = " << P << "\n";
std::cout << "P = " << mb << "\n";
assert(is_equal(P, mb)); // passes OK.
上面的代码产生以下输出:
1000 m^-1 kg s^-2
10 h(m^-1 kg s^-2)
“h”表示异构系统。缩放单位可以显式转换为基本单位,这使得向期望不同单位数量的函数传递数量变得容易且安全。
quantity<mass_density> density(quantity<temperature> T,
quantity<pressure> P, // pascals
double humidity); // prototype
quantity<temperature> T = 292.15 * kelvin;
quantity<millibars> P = 1013.0 * mbars;
quantity<mass_density> density1 = density(T, P, humidity); // error! Wont compile!
quantity<mass_density> density2 = density(T, quantity<pressure>(P), humidity); // converted.
火星探测器气候轨道器(Mars Climate Orbiter)可能极大地受益于这类功能。
相对单位与绝对单位
对于大多数量,无需区分相对和绝对测量。“零”值对于单位通常是零。例如,如果没有质量存在,则质量 = 0 千克 = 0 克 = 0 磅。一个常见的例外是温度。出于历史原因,华氏度和摄氏度的“零”值不是零。这会导致歧义。如果将 0 开尔文(Kelvin)的温度传递到公式中,它指的是 0 开尔文 = 0 华氏度 = 0 摄氏度的温度差?还是指 0 开尔文 = -459.67 华氏度 = -273.15 摄氏度的绝对温度?
该库通过定义 2 种独立类型来处理此问题,如下所示。
typedef absolute<celsius::temperature> abs_celsius; // saves typing...
quantity<celsius::temperature> boiling_pt = 100 * celsius::degrees; // relative temperature - the default.
quantity<temperature> T_kelvin(boiling_pt); // convert to Kelvin
std::cout << T_kelvin << "\n";
quantity<abs_celsius> boiling_pt2(100 *abs_celsius()); // absolute temperature.
quantity<temperature> T2_kelvin(boiling_pt); // convert to Kelvin
std::cout << T2_kelvin << "\n";
上面的代码产生以下输出:
100 K
373.15 Absolute K
示例代码
示例代码包括使用 `double` 和 `quantity
该示例在 Mint 17 上使用 NetBeans 构建。使用 VS 2013 在 Windows 8 上测试了一个略有不同的代码版本。
注意事项
零开销(几乎)
Boost Units 库的一个重要优点是它应该具有零开销。示例代码也证明了这一点……几乎。唯一的例外是 `dew_point__Newton_Raphson` 函数。MSVC 编译器尤其糟糕:Boost Units 版本比使用 `double` 值的代码慢 100 多倍。
为什么?原因并不特别清楚。可能是优化器无法胜任。也可能是编译器添加了昂贵的对象展开代码——通过添加抛出异常的代码可以实现类似的减慢。这种效果非常依赖于编译器。无论如何,“零开销”的说法并不完全属实。
用法
如果您检查 `saturation_pressure` 的代码,您可能会注意到它调用了多项式函数 `p`,该函数接受一个 `double`。并非所有内容都能实现量纲安全:多项式函数本质上是 X + X2 + X3 + ... 的形式,因此没有定义量纲。将 Units 库用于使所有 `public` 函数/方法尽可能安全,这可能是一个很好的目标。
结论
Boost Units 库是模板和强类型强大功能的绝佳演示。希望本文能提高人们的认识,并鼓励其他人使用它。