C++中编写物理公式

物理计算中,数值都不是独立的,大多数物理量都附带有量纲。随着计算变得越来越复杂,维持物理量的正确量纲能够避免诸如“将质量赋给长度”之类不经意间犯下的错误。我们接下来就要为数值建立一个类型系统。

国际标准量纲制规定了物理量的基本量纲为:质量,长度,时间,电荷,温度,密度以及物质的量。其他量纲都是在此基础上复合而成的。我们将要实现的类型系统必须能够表示这七种以及其组合的量纲。

一个复合量纲可以看成是几个基本量纲的乘积。我们打算用一个数组表示他们,其值表示对应量纲的幂次:

typedef int dimension[7]; // m l t ...
dimension const mass    = {1, 0, 0, 0, 0, 0, 0};
dimension const length  = {0, 1, 0, 0, 0, 0, 0};
dimension const time    = {0, 0, 1, 0, 0, 0, 0};
...

根据这种表示法,力的量纲表示如下:

dimension const force = {1, 1, -2, 0, 0, 0, 0};

即\(mlt^{-2}\)。然而,这些数组的使用并不能达到我们的目的:它们的类型都是相同的,而质量和长度的类型不同。

使用MPL中的vector可以创建一组不同类型的数组。

#include <boost/mpl/vector.hpp>

typedef boost::mpl::vector<signed char, short, int, long> signed_types;

使用整形外覆器可以用类型表示数量。 MPL提供了int_<N>类模板,它以一个內嵌的::value来表示他的整形参数N:

#include <boost/mpl/int.hpp>

namespace mpl = boost::mpl;
static int const five = mpl::int_<5>::value;

MPL还提供了不同的vector让我们写出简短的代码:

#include <boost/mpl/vector_c.hpp>

typedef mpl::vector_c<int, 1, 0, 0, 0, 0, 0, 0> mass;
typedef mpl::vector_c<int, 0, 1, 0, 0, 0, 0, 0> length;
typedef mpl::vector_c<int, 0, 0, 1, 0, 0, 0, 0> time;
typedef mpl::vector_c<int, 0, 0, 0, 1, 0, 0, 0> charge;
typedef mpl::vector_c<int, 0, 0, 0, 0, 1, 0, 0> temperature;
typedef mpl::vector_c<int, 0, 0, 0, 0, 0, 1, 0> intensity;
typedef mpl::vector_c<int, 0, 0, 0, 0, 0, 0, 1> amount_of_substance;

如果我们愿意,还可以定义一些复合量纲:

typedef mpl::vector_c<int, 0, 1, -1, 0, 0, 0, 0> velocity;       // l/t
typedef mpl::vector_c<int, 0, 1, -2, 0, 0, 0, 0> acceleration;   // l/(t^2)
typedef mpl::vector_c<int, 1, 1, -1, 0, 0, 0, 0> momentum;       // ml/t
typedef mpl::vector_c<int, 1, 1, -2, 0, 0, 0, 0> force;          // ml/(t^-2)
typedef mpl::vector_c<int, 0, 0, 0, 0, 0, 0, 0> scalar;

我们要进行真实的计算,还要进行类型检查,这需要一个简单的外覆类:

template<class T, class Dimension>
struct quantity {
    explicit quantity(T x): m_value(x) {}
    T value() const {return m_value;}
    private:
    T m_value;
};

现在,我们把数值和量纲联系起来了:

quantity<float, length> l(1.0f);
quantity<float, mass> m(2.0f);

m = l; // compile error

仅仅这样我们还不能对数据进行计算。下面我们先来实现加法和减法:

template<class T, class D>
quantity<T, D>
operator+(quantity<T, D>x, quantity<T, D>y) {
    return quantity<T, D>(x.value() + y.value());
}

template<class T, class D>
quantity<T, D>
operator-(quantity<T,D> x, quantity<T, D> y) {
    return quantity<T, D>(x.value() - y.value());
}

这样就可以正确地实现两个物理量的加法了:

quantity<float, length> len1(1.0f);
quantity<float, length> len2(2.0f);

len1 = len1 + len2; // ok
len1 = len2 + quantity<float, mass>(3.7);// compile error

接下来,为了实现乘法和除法,我们需要对量纲进行计算,获得计算结果的量纲。plus和minus两个boost提供的模板类可以满足要求:

#include <boost/mpl/plus.hpp>
#include <boost/mpl/int.hpp>
#include <iostream>

typedef mpl::plus< mpl::int_<2>, mpl::int_<3> >::type pls2_3;
int main() {
    std::cout << pls2_3::value << std::endl;
    return 0;
}

到这里应该就可以了。我想读者可以很容易猜到transform的用途。

#include <boost/mpl/int.hpp>
#include <boost/mpl/vector_c.hpp>
#include <boost/mpl/plus.hpp>
#include <boost/mpl/int.hpp>
#include <boost/mpl/equal.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/static_assert.hpp>
#include <iostream>

namespace mpl = boost::mpl;

typedef mpl::vector_c<int, 1, 0, 0, 0, 0, 0, 0> mass;
typedef mpl::vector_c<int, 0, 1, 0, 0, 0, 0, 0> length;
typedef mpl::vector_c<int, 0, 0, 1, 0, 0, 0, 0> time_;
typedef mpl::vector_c<int, 0, 0, 0, 1, 0, 0, 0> charge;
typedef mpl::vector_c<int, 0, 0, 0, 0, 1, 0, 0> temperature;
typedef mpl::vector_c<int, 0, 0, 0, 0, 0, 1, 0> intensity;
typedef mpl::vector_c<int, 0, 0, 0, 0, 0, 0, 1> amount_of_substance;

typedef mpl::vector_c<int, 0, 1, -1, 0, 0, 0, 0> velocity;       // l/t
typedef mpl::vector_c<int, 0, 1, -2, 0, 0, 0, 0> acceleration;   // l/(t^2)
typedef mpl::vector_c<int, 1, 1, -1, 0, 0, 0, 0> momentum;       // ml/t
typedef mpl::vector_c<int, 1, 1, -2, 0, 0, 0, 0> force;          // ml/(t^2)
typedef mpl::vector_c<int, 0, 0, 0, 0, 0, 0, 0> scalar;

template<class T, class Dimensions>
struct quantity {
    explicit quantity(T x): m_value(x) {}
    template<class OtherDimensions>
    quantity(quantity<T, OtherDimensions>const& rhs): m_value(rhs.value()) {
        BOOST_STATIC_ASSERT((mpl::equal<Dimensions, OtherDimensions>::type::value));
    }
    T value() const {return m_value;}
    private:
    T m_value;
};

template<class T, class D>
quantity<T, D>
operator+(quantity<T, D>x, quantity<T, D>y) {
    return quantity<T, D>(x.value() + y.value());
}

template<class T, class D>
quantity<T, D>
operator-(quantity<T,D> x, quantity<T, D> y) {
    return quantity<T, D>(x.value() - y.value());
}

struct plus_f {
    template<class T1, class T2>
    struct apply: mpl::plus<T1, T2> {};
};
struct minus_f {
    template<class T1, class T2>
    struct apply: mpl::minus<T1, T2> {};
};

template<class T, class D1, class D2>
quantity<T, typename mpl::transform<D1, D2, plus_f>::type >
operator*(quantity<T, D1> x, quantity<T, D2> y) {
    typedef typename mpl::transform<D1, D2, plus_f>::type dim;
    return quantity<T, dim>(x.value() * y.value);
}

template<class T, class D1, class D2>
quantity<T, typename mpl::transform<D1, D2, plus_f>::type >
operator/(quantity<T, D1> x, quantity<T, D2> y) {
    typedef typename mpl::transform<D1, D2, minus_f>::type dim;
    return quantity<T, dim>(x.value() / y.value);
}

By .