带刻度的 C++ 维度分析(Barnes 和 Nackman)

Posted

技术标签:

【中文标题】带刻度的 C++ 维度分析(Barnes 和 Nackman)【英文标题】:C++ Dimensional Analysis (Barnes and Nackman) with Scale 【发布时间】:2015-04-26 05:44:14 【问题描述】:

我最近在阅读关于 C++ 源代码的系列文章“反思的暂停:五个列表中的五个”。在Part V 中,Scott Meyers 讨论了单位问题的 Barton 和 Nackman 解决方案。作为航空航天工业的嵌入式软件工程师,这个特别的啊哈!瞬间让我兴奋。到目前为止,我还没有听说过这种方法(也没有听说过这些作者)。

我进行了研究,试图找到有关该解决方案的更多信息。我在这里看到了这个演示文稿:http://se.ethz.ch/~meyer/publications/OTHERS/scott_meyers/dimensions.pdf

我想我理解我所读到的关于这个解决方案的所有内容。但我觉得缺少一块拼图。这个美丽、优雅的解决方案无法解决任何问题。具体来说,我对不仅仅是乘法因子的转换感兴趣。例如,开尔文、摄氏和华氏之间的温度转换。我希望能够交替使用这些温度。

我的问题:

    我错过了什么吗?是否在参考我忽略的单位解决方案讨论的地方讨论了比例?

    如果没有,我该如何进一步解决这个问题?是否存在可以与 B&N 方法结合使用来完成解决方案的现有模式?

我的目标是能够在不进行过多计算的情况下编写类似于以下示例的代码。在距离的情况下,我希望能够声明一个定义为英里的对象,并以英里为单位执行所有相关计算,而不必不断地来回转换为米。

例子:

typedef Units<double, miles>      uMiles;
typedef Units<double, kilometers> uKilometers;

uMiles      d1 (1.0);
uKilometers d2 (1.60934);

d1 += d2;
if (d1.val(miles) == 2.0) // PASS
if (d1.val(kilometers) == 3.21869) // PASS

注意: 我已经看到了 BOOST UNITS 解决问题的方法,但我不喜欢它。对我来说,这是非常不可读的。我也不允许使用外部库,例如 boost。

备份数据:

描述的单位类:

template<class T, // Precision
    int m, // Mass
    int l, // Length
    int t, // Time
    int q, // Charge
    int k, // Temperature
    int i, // Luminous Intensity
    int a> // Angle

    class Units
    
    public:
    // ------------------------------------------------------
    explicit
    Units (T initVal = 0)
        : val (initVal)
    
    

    // --------------------------------------------------------------------
    // Operator: Assignment from type T
    Units<T, m, l, t, q, k, i, a>&
    operator= (const T rhs)
    
        val = rhs;
        return *this;
    

    // --------------------------------------------------------------------
    // Operator: Type Converstion to T
    operator T () const
    
        return val;
    

    // --------------------------------------------------------------------
    // Operator: +=
    Units<T, m, l, t, q, k, i, a>&
    operator+= (const Units<T, m, l, t, q, k, i, a>& rhs)
    
        val += rhs.val;
        return *this;
    

    // --------------------------------------------------------------------
    Units<T, m, l, t, q, k, i, a>&
    operator-= (const Units<T, m, l, t, q, k, i, a>& rhs)
    
        val -= rhs.val;
        return *this;
    

    // --------------------------------------------------------------------
    Units<T, m, l, t, q, k, i, a>&
    operator*= (T rhs)
    
        val *= rhs;
        return *this;
    

    // --------------------------------------------------------------------
    Units<T, m, l, t, q, k, i, a>&
    operator/= (T rhs)
    
        val /= rhs;
        return *this;
    

    // --------------------------------------------------------------------
    // Get Reference
    T&
    Val ()
    
        return val;
    

    // --------------------------------------------------------------------
    // Get Value
    const T&
    Val () const
    
        return val;
    

    private:
    T val;
    ;

// ----------------------------------------------------------------------------
// Operator: Addition
template<class T, int m, int d, int t, int q, int k, int i, int a>
    const Units<T, m, d, t, q, k, i, a>
    operator+ (const Units<T, m, d, t, q, k, i, a> & lhs,
           const Units<T, m, d, t, q, k, i, a> & rhs)
    
    Units<T, m, d, t, q, k, i, a> result (lhs);
    return result += rhs;
    

// ----------------------------------------------------------------------------
// Operator: Subtraction
template<class T, int m, int d, int t, int q, int k, int i, int a>
    const Units<T, m, d, t, q, k, i, a>
    operator- (const Units<T, m, d, t, q, k, i, a> & lhs,
           const Units<T, m, d, t, q, k, i, a> & rhs)
    
    Units<T, m, d, t, q, k, i, a> result (lhs);
    return result -= rhs;
    

// ----------------------------------------------------------------------------
// Operator: Multiplication
template<class T, int m, int d, int t, int q, int k, int i, int a>
    const Units<T, m, d, t, q, k, i, a>
    operator* (const Units<T, m, d, t, q, k, i, a> & lhs,
           const Units<T, m, d, t, q, k, i, a> & rhs)
    
    Units<T, m, d, t, q, k, i, a> result (lhs);
    return result *= rhs;
    

// ----------------------------------------------------------------------------
// Operator: Division
template<class T, int m, int d, int t, int q, int k, int i, int a>
    const Units<T, m, d, t, q, k, i, a>
    operator/ (const Units<T, m, d, t, q, k, i, a> & lhs,
           const Units<T, m, d, t, q, k, i, a> & rhs)
    
    Units<T, m, d, t, q, k, i, a> result (lhs);
    return result /= rhs;
    

// ----------------------------------------------------------------------------
// Operator: Multiplication (Creates New Type)
template<class T,
        int m1, int d1, int t1, int q1, int k1, int i1, int a1,
    int m2, int d2, int t2, int q2, int k2, int i2, int a2>

    // Return Type
    Units<T, m1 + m2, d1 + d2, t1 + t2, q1 + q2, k1 + k2, i1 + i2, a1 + a2>
    operator* (const Units<T, m1, d1, t1, q1, k1, i1, a1>& lhs,
           const Units<T, m2, d2, t2, q2, k2, i2, a2>& rhs)
    
        // New Return type
    typedef Units<T,
        m1 + m2,
        d1 + d2,
        t1 + t2,
        q1 + q2,
        k1 + k2,
        i1 + i2,
        a1 + a2> ResultType;

    return ResultType (lhs.Val() * rhs.Val());
    

// ----------------------------------------------------------------------------
// Operator: Division (Creates New Type)
template<class T,
        int m1, int d1, int t1, int q1, int k1, int i1, int a1,
    int m2, int d2, int t2, int q2, int k2, int i2, int a2>

    // Return Type
    Units<T, m1 - m2, d1 - d2, t1 - t2, q1 - q2, k1 - k2, i1 - i2, a1 - a2>
    operator/ (const Units<T, m1, d1, t1, q1, k1, i1, a1>& lhs,
           const Units<T, m2, d2, t2, q2, k2, i2, a2>& rhs)
    
        // New Return type
    typedef Units<
        T,
        m1 - m2,
        d1 - d2,
        t1 - t2,
        q1 - q2,
        k1 - k2,
        i1 - i2,
        a1 - a2> ResultType;

    return ResultType (lhs.Val() / rhs.Val());
    

这个类允许我们编写如下代码:

//  Base Types
typedef Units<double, 1,0,0,0,0,0,0> uMass;
typedef Units<double, 0,1,0,0,0,0,0> uLength;
typedef Units<double, 0,0,1,0,0,0,0> uTime;
typedef Units<double, 0,0,0,1,0,0,0> uCharge;
typedef Units<double, 0,0,0,0,1,0,0> uTemperature;
typedef Units<double, 0,0,0,0,0,1,0> uIntensity;
typedef Units<double, 0,0,0,0,0,0,1> uAngle;

//  Derived Types
typedef Units<double, 0,2, 0,0,0,0,0> uArea;
typedef Units<double, 0,3, 0,0,0,0,0> uVolume;
typedef Units<double, 0,1,-1,0,0,0,0> uVelocity;
typedef Units<double, 0,1,-2,0,0,0,0> uAcceleration;
typedef Units<double, 1,1,-2,0,0,0,0> uForce;


uMass         mass;
uTime         time;
uForce        force;
uLength       length;
uVelocity     velocity;
uAcceleration acceleration;

// This will compile
mass = 7.2;
acceleration = 3.5;
force = mass * acceleration;

// These will not compile ** Enforcing Dimensional Unit Correctness
force = 7.2 * acceleration;
force = mass; 
force *= acceleration;

【问题讨论】:

我确实想指出我已经购买了 Scott Meyers 引用的 Barton 和 Nackman 的书。我希望这本书也能提供更多的见解。 amazon.com/… 单位是可以乘法组合的东西。度数不是(因为它们有一个任意的零点,并不意味着“没有”),所以我不希望“单位”库来处理它们。 @MikeSeymour 我不同意。所有单位都有隐含的比例。我只是在寻找一种明确的方式并提供一种转换方式。 Boost Units 也提供了规模,尽管方式很丑。 @MikeSeymour 我同意。实际上,您可以对在任何不同单位系统之间进行转换说同样的话。由于操作员超负荷,您甚至无法使用上述代码在厘米和英寸或毫升和液量盎司之间进行转换。除非你创建的“单位”实际上只是像 Unit&lt;double, 0, 0, 0, 0, 0, 0, 0&gt; constant(243); 这样的常量,然后你可能会做像 celsius=kelvin-constant 这样的事情 @mstbaum 也许我的问题不清楚?我知道我无法使用上述库在任何单位的任何比例之间进行转换。温度对我来说只是一个特别的兴趣点,因为它不是直接的乘法转换。我的问题旨在帮助我将上述设计转化为支持比例与单位结合的新事物。最终目标是让解决方案简单且可扩展,这就是我想从 Barnes and Nackman 解决方案开始的原因。对于不关心规模的直接 DA,它非常优雅并强制编译时一致性。 【参考方案1】:

从我从您的代码和您的解释中了解到,您似乎可以定义转换常量“Units”,例如

Units<double,0,0,0,0,0,0,0> K2C(243.15);
Units<double,0,0,0,0,1,0,0> uCelsius;
Units<double,0,0,0,0,1,0,0> uKelvin;

uCelsius = uKelvin - K2C;

上面的代码与重载运算符一起使用,同时保持模板单元一致。您必须为要使用的任何常量创建伪Units。

我可以看到的另一种工作方式是编写一个类似的函数

typdef enum 
    CELSIUS,
    KELVIN,
    FAHRENHEIT
 temp_t;

void Units::convertTemp(const temp_t from, const temp_t to) 

    switch(from) 
        case KELVIN:
            val -= 243.15;
        case CELSIUS:
            if(to == FAHRENHEIT)
                //conversion
            else if(to == KELVIN)
                val += 243.15;
            break;
        case FAHRENHEIT:
            // convert to celsius
            if(to == KELVIN)
                //convert to Kelvin
     

【讨论】:

这不符合我对能够互换使用这些单元的期望。您提供的两种解决方案都需要显式转换。【参考方案2】:

在物理学中,具有单位的值(如速度)乘以标量时仍保留其单位。这意味着这样说:

1.6 * 7 kilometers per hour = 11.2 kilometers per hour

不改变单位。我们实际上想要做的是将每小时公里数转换为每小时英里数,但我们只是将每小时公里数乘以一个因子。仅仅因为你得到的数字等于每小时英里数并不意味着你实际上代表每小时英里数。

在您的模板定义中,您只允许每种基本单位中的一种

到目前为止,您已经区分了不同种类的物理量,但没有区分不同种类的单位系统。在上面的代码中,您的单位/类型是Speed,而不是milesPerHour,并且您仍然手动记住您使用的实际单位并手动在它和SI单位之间进行转换(不使用类型系统)。实际上,如果您将一个变量初始化为每小时英里数,而将另一个变量初始化为每小时公里数,它们将具有相同的类型还是不同的类型?我会争辩说它们应该是不同的类型。

为了使所有单位(在不同的测量系统中)不同类型,我们可以为SI units system 编写一个Units 模板,并为Imperial units system 编写一个等效的模板。

template<class T, // Precision
    int m, // Mass
    int l, // Length
    int t, // Time
    int q, // Charge
    int k, // Temperature
    int i, // Luminous Intensity
    int a> // Angle

    class SIUnits
    
    // ...

template<class T, // Precision
    int m, // Mass
    int l, // Length
    int t, // Time
    int q, // Charge
    int k, // Temperature
    int i, // Luminous Intensity
    int a> // Angle

    class ImperialUnits
    
    // ...

现在我们需要找到一种好方法,让我们的类型系统自动在两个单位系统之间映射。

以下是一种可能的(但很糟糕)解决方案:

template<class T, // Precision
    int m, // Mass
    int l, // Length
    int t, // Time
    int q, // Charge
    int k, // Temperature
    int i, // Luminous Intensity
    int a> // Angle

    ImperialUnits<T, m, l, t, q, k, i, a>
    convert(SIUnits<T, m, l, t, q, k, i, a> value)
    
        T conversionFactor = 1.0;
        for (int x = 0; x < m; ++x)
        
            // This is some function that maps from one to the other.
            conversionFactor *= siMassToImperialMassFactor;
            conversionFactor += siMassToImperialMassOffset;
        

        for (int x = m; x < 0; ++x)
        
            // This is some function that maps from one to the other.
            conversionFactor *= siMassToImperialMassFactor;
            conversionFactor += siMassToImperialMassOffset;
        

        // Do the same for other dimensions as well...

您在开尔文、华氏和摄氏中看到的问题与您没有更改基础测量系统这一事实有关,这需要您创建一种新类型,而只是手动记住小提琴假装在两个系统之间转换所需的因素。

本质上,如果我们删除模板并使用纯类,我们会得到类似的结果:

class Celsius;
class Kelvin;
class Fahrenheit

    // ...
    Fahrenheit(Celsius t); // Auto-convert from celsius
    Fahrenheit(Kelvin t);  // Auto-convert from Kelvin
    // ...

这些是不同的类型,因此我们可以使用类型系统来定义它们之间的转换。

最后,我可以解释一下我的实际提议。在您的Units 模板中,您拥有每个物理尺寸的基础类型和标识符,其中一种解决方案是再添加一项来表示所使用的测量系统,即

template<class T, // Precision
    int SystemOfUnits, // Denotes the system of units used, SI or Imperial.
    int m, // Mass
    int l, // Length
    int t, // Time
    int q, // Charge
    int k, // Temperature
    int i, // Luminous Intensity
    int a> // Angle

    class Units
    
        // etc.

但是,我不喜欢上述解决方案,因为SystemOfUnits 模板参数的值包含了太多关于您的模板的信息。我可能会在我的定义中更明确地说明我的测量系统使用的单位,即

template<class T, // Precision
    int m, // Mass
    int l, // Length
    int t, // Time
    int q, // Charge
    int k, // Temperature
    int i, // Luminous Intensity
    int a, // Angle
    class M, // Mass unit type
    class L, // Length unit type
    class T, // Time unit type
    class Q, // Charge unit type
    class K, // Temperature unit type
    class I, // Luminous Intensity unit type
    class A> // Angle unit type


    class Units
    
        // etc.

这会迫使您使用一致的测量系统进行维度分析,代价是模板参数增加一倍。

【讨论】:

以上是关于带刻度的 C++ 维度分析(Barnes 和 Nackman)的主要内容,如果未能解决你的问题,请参考以下文章

使用 Barnes-Hut 进行图形放置的优化问题

初步认识c++之命名空间详解(千字长文带你刨析你命名空间的细节)

[数据分析]excel带名称的四象限散点图制作

Android双向seekbar(带刻度)

老司机带带我:数仓建模架构|维度建模剖析与案例演示

老司机带带我:数仓建模架构|维度建模剖析与案例演示