65.9K
CodeProject 正在变化。 阅读更多。
Home

Boost Units 库。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (19投票s)

2015年5月6日

CPOL

5分钟阅读

viewsIcon

29386

downloadIcon

143

对 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!

新类型

通常不需要超出提供的量纲/单位,但这样做相对直接。代码包含一个支持阿伏伽德罗常数的类型定义。标准过程是:

  1. 定义派生量纲,
  2. 定义相关单位,以及
  3. 定义类型。
    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 库是模板和强类型强大功能的绝佳演示。希望本文能提高人们的认识,并鼓励其他人使用它。

© . All rights reserved.