用于访问计量单位的数据结构


17

TL; DR-我正在尝试设计一种最佳的数据结构,以定义度量单位内的单位。


A Unit of measure本质上是value与关联的(或数量)unitSI单位有七个基准或尺寸。即:长度,质量,时间,电流,温度,物质量(摩尔)和发光强度。

这足够简单,但是我们经常使用许多派生单位和费率。示例组合单位为牛顿:kg * m / s^2示例比率为tons / hr

我们的应用程序严重依赖隐含单位。我们将把单元嵌入变量或列名中。但这在我们需要指定具有不同单位的度量单位时会产生问题。是的,我们可以在输入和显示时转换值,但这会生成很多开销代码,我们希望将其封装在自己的类中。

在Codeplex和其他协作环境上有许多解决方案。项目的许可是可以接受的,但是项目本身通常最终会变得太轻或太重。我们正在追逐自己的“正当”独角兽。

理想情况下,我可以使用以下方法定义新的度量单位:

UOM myUom1 =新的UOM(10伏);
UOM myUom2 =新的UOM(43.2,牛顿);

当然,我们会根据客户的需求混合使用英制和SI单位。

我们还需要使这种单位结构与将来的数据库表保持同步,以便我们也可以在数据中提供相同程度的一致性。


定义创建计量单位类别所需的单位,派生单位和费率的最佳方法是什么?我可以看到使用了一个或多个枚举,但这对于其他开发人员可能会感到沮丧。一个单一的枚举包含200多个条目,将是巨大的,而基于SI与英制单位的多个枚举可能会造成混淆,而基于单位本身的分类的其他细分可能会造成混淆。

枚举示例显示了我的一些担忧:

myUnits.Volt
myUnits.Newton
myUnits.meter

SIUnit.meter
ImpUnit.foot DrvdUnit.Newton
DrvdUnitSI.Newton
DrvdUnitImp.FtLbs

我们正在使用的单位集非常明确,而且空间有限。当我们有客户需求时,我们确实需要能够扩展和添加新的派生单位或费率。尽管我认为更广泛的设计方面适用于多种语言,但是该项目使用C#。


我看过的一个库允许通过字符串自由输入单位。然后,他们的UOM类解析该字符串,并相应地添加内容。这种方法的挑战在于,它迫使开发人员思考并记住正确的字符串格式是什么。如果我们不在代码内添加其他检查以验证在构造函数中传递的字符串,则会冒运行时错误/异常的风险。

实质上,另一个库创建了太多开发人员必须使用的类。随着等效值单位它提供了一个DerivedUnitRateUnit等。本质上,对于我们要解决的问题,代码过于复杂。该库实际上允许任何组合(在单位世界中是合法的),但是我们很高兴通过不允许所有可能的组合来扩大问题范围(简化代码)。

其他库非常简单,甚至都没有考虑过运算符重载。

另外,我也不担心尝试进行错误的转换(例如:伏特到米)。开发人员是目前唯一可以在此级别访问的人员,我们不一定需要防止这些类型的错误。


您能以什么方式解释发现的库不符合您的需求?
svick


1
@MainMa-感谢您的链接。我们不需要进行维度分析,因为我们的问题空间足够小,我们只需要声明允许的转换即可。这将是一个产生的口号,但这是一次性的成本。

1
您能解释一下您需要哪种转换吗?它是仅缩放转换(例如,米到厘米)还是跨维度转换(例如,质量到力)?
巴特·范·英根·谢瑙

1
您是否考虑过将部分代码移至F#?该语言具有计量单位build int。
皮特2014年

Answers:


11

用于C ++的Boost库包含有关维度分析的文章,该文章介绍了处理度量单位的示例实现。

总结一下:测量单位表示为向量,向量的每个元素代表一个基本维度:

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};

派生单位是这些的组合。例如,力(质量*距离/时间^ 2)将表示为

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

英制和SI单位可以通过添加转换因子来处理。

此实现依赖于C ++特定的技术(使用模板元编程可以轻松地将不同的度量单位转换为不同的编译时类型),但是这些概念应转移到其他编程语言。


那么所有派生单元都等同于C ++ const?我认为它们被包装到命名空间中以避免污染东西?

1
@ GlenH7-这进入模板元编程内容。实际上,它们被表示为单独的类型(例如mpl::vector_c<int,1,0,0,0,0,0,0>),而不是const;本文首先通过解释的方式介绍了consts方法(我可能对此并没有很好地解释)。使用const可以替代(您将失去一些编译时类型的安全性)。使用名称空间避免名称污染当然是一种选择。
乔什·凯利

8

我刚刚在GithubNuGet上发布了Units.NET 。

它为您提供所有常见的单位和转换。它重量轻,经过单元测试并支持PCL。

针对您的问题:

  • 这是实现的较轻端。重点是帮助计量单位的明确表示,转换和构建。
  • 没有方程式求解器,它不会自动从计算中得出新的单位。
  • 定义单位的一个大枚举。
  • UnitConverter类,用于在单元之间动态转换。
  • 不可变的数据结构,用于在单位之间进行显式转换。
  • 重载运算符可简化运算。
  • 扩展到新的单位和转换是要添加一个用于动态转换的新枚举,并添加一个度量单位类(例如Length),以定义显式转换属性和运算符重载。

我还没有看到该领域解决方案的圣杯。如您所说,它很容易变得太复杂或太冗长而无法使用。有时,最好保持简单,对于我的需求,这种方法已被证明足够了。

显式转换

Length meter = Length.FromMeters(1);
double cm = meter.Centimeters; // 100
double yards = meter.Yards; // 1.09361
double feet = meter.Feet; // 3.28084
double inches = meter.Inches; // 39.3701

Pressure p = Pressure.FromPascal(1);
double kpa = p.KiloPascals; // 1000
double bar = p.Bars; // 1 × 10-5
double atm = p.Atmosphere; // 9.86923267 × 10-6
double psi = p.Psi; // 1.45037738 × 10-4

动态转换

// Explicitly
double m = UnitConverter.Convert(1, Unit.Kilometer, Unit.Meter); // 1000
double mi = UnitConverter.Convert(1, Unit.Kilometer, Unit.Mile); // 0.621371
double yds = UnitConverter.Convert(1, Unit.Meter, Unit.Yard); // 1.09361

// Or implicitly.
UnitValue val = GetUnknownValueAndUnit();

// Returns false if conversion was not possible.
double cm;
val.TryConvert(LengthUnit.Centimeter, out cm);

您的示例似乎基本上利用了Truple<T1, T2, T3>(x, y, z)
Chef_Code

不知道您的意思是什么,每个单位仅存储一个值。对于“长度”,它具有一个double类型的米字段;对于“质量”,它是公斤。转换为其他单位时,它将通过转换函数运行该值。这些示例现在有些过时了,但适用相同的概念。
angularsen '16

我想我误会及得出结论了……我的意思是Tuple。我看不到您的UnitConverter课程,但IMO似乎可能与该Tuple课程具有相似的功能。
Chef_Code

仍然不确定元组的比较,但请参阅github页以获取有关用法的更新示例。
angularsen 2016年

3

如果可以改为使用C#切换到F#,则F#具有一个度量单位系统(使用值上的元数据实现),看起来像它可以满足您的要求:

http://en.wikibooks.org/wiki/F_Sharp_Programming/Units_of_Measure

值得注意的是:

// Additionally, we can define types measures which are derived from existing measures as well:

[<Measure>] type m                  (* meter *)
[<Measure>] type s                  (* second *)
[<Measure>] type kg                 (* kilogram *)
[<Measure>] type N = (kg * m)/(s^2) (* Newtons *)
[<Measure>] type Pa = N/(m^2)       (* Pascals *)

很好的建议,我们已经考虑了。我不相信F#使我们能够控制单元最终如何在输出中显示的能力。

2
@ GlenH7我相信您是对的:Important: Units of measure look like a data type, but they aren't. .NET's type system does not support the behaviors that units of measure have, such as being able to square, divide, or raise datatypes to powers. This functionality is provided by the F# static type checker at compile time, **but units are erased from compiled code**. Consequently, it is not possible to determine value's unit at runtime.
保罗

3

基于所有必需的转换都是比例转换的事实(除非您必须支持温度转换。转换涉及偏移量的计算要复杂得多),我将设计“测量单位”系统,如下所示:

  • 一个类,unit包含缩放因子,用于单位文本表示的字符串以及要unit缩放到的引用。此处有文本表示形式,用于显示目的,它是对基本单位的引用,以便在对具有不同单位的值进行数学运算时知道结果位于哪个单位。

    对于每个受支持的单元, unit都提供该类。

  • 一类UOM包含对值的一个值和一个参考unit。的UOM类提供重载操作用于加/减另一个UOM和相乘/与无量纲值除以。

    如果对两个UOM相同的对象执行加/减unit,则直接执行。否则,这两个值都将转换为各自的基本单位并进行加/减。结果报告为在基础中unit

用法就像

unit volts = new unit(1, "V"); // base-unit is self
unit Newtons = new unit(1, "N"); // base-unit is self
unit kiloNewtons = new unit(1000, "kN", Newtons);
//...
UOM myUom1 = new UOM(10, volts);
UOM myUom2 = new UOM(43.2, kiloNewtons);

由于不兼容单元上的操作不被认为是问题,因此我在这方面没有尝试过使设计类型安全。可以通过验证两个单元引用相同的基本单元来添加运行时检查。


既然您提到温度:什么是温度95F - 85F?什么20C - 15C啊 在两个示例中,两个UOM都具有相同的unit。减法会直接执行吗?

@MattFenwick:结果分别为10 F5 C。如果可能,将直接执行计算,以避免不必要的转换。向中添加单位转换方法将是微不足道的UOM,但是对于摄氏度-华氏转换,unit必须扩展该类,除了比例因子外,还可能存在偏移。
巴特·范·英根·谢瑙

但是95F - 85F!= 10F

1
@MattFenwick:请赐教。有多冷,如果你更低的温度是否得到95F通过85F?据我所知,华氏度仍然是线性标度。
巴特·范·英根·谢瑙

2
让我们以Celsius为例,因为转换为Kelvin更为容易:如果说20C - 15C = 5C,那么我们说293.15K - 288.15K = 278.15K,这显然是错误的。

2

考虑一下您的代码在做什么以及它将允许什么。对其中所有可能的单位进行简单的枚举使我可以执行将伏特转换为米的操作。这显然对人类无效,但是该软件很乐意尝试。

我曾经做过与此类似的事情,并且我的实现具有全部实现的抽象基类(长度,权重等)IUnitOfMeasure。每个抽象基类都定义了一个默认类型(该类Length具有class 的默认实现Meter),它将用于所有转换工作。因此IUnitOfMeasure实现了两种不同的方法,ToDefault(decimal)并且FromDefault(decimal)

我想包装的实际数字是接受IUnitOfMeasure作为其通用参数的通用类型。像这样说可以Measurement<Meter>(2.0)为您提供自动类型安全性。通过在这些类上实现适当的隐式转换和数学方法,您可以执行类似的操作Measurement<Meter>(2.0) * Measurement<Inch>(12)并以默认类型(Meter)返回结果。我从不算出牛顿这样的派生单位。我只是将其保留为公斤*米/秒/秒。


我喜欢您建议使用泛型类型的方法。

1

我相信答案就在于MarioVW对以下内容的堆栈溢出响应

实际示例在.Net 4-0中可以使用元组的地方?

使用元组,您可以轻松实现二维字典(或与此相关的n维)。例如,您可以使用这样的字典来实现货币兑换映射:

var forex = new Dictionary<Tuple<string, string>, decimal>();
forex.Add(Tuple.Create("USD", "EUR"), 0.74850m); // 1 USD = 0.74850 EUR
forex.Add(Tuple.Create("USD", "GBP"), 0.64128m);
forex.Add(Tuple.Create("EUR", "USD"), 1.33635m);
forex.Add(Tuple.Create("EUR", "GBP"), 0.85677m);
forex.Add(Tuple.Create("GBP", "USD"), 1.55938m);
forex.Add(Tuple.Create("GBP", "EUR"), 1.16717m);
forex.Add(Tuple.Create("USD", "USD"), 1.00000m);
forex.Add(Tuple.Create("EUR", "EUR"), 1.00000m);
forex.Add(Tuple.Create("GBP", "GBP"), 1.00000m);
decimal result;
result = 35.0m * forex[Tuple.Create("USD", "EUR")]; // USD 35.00 = EUR 26.20
result = 35.0m * forex[Tuple.Create("EUR", "GBP")]; // EUR 35.00 = GBP 29.99
result = 35.0m * forex[Tuple.Create("GBP", "USD")]; // GBP 35.00 = USD 54.58

我的申请也有类似的需求。 Tuple也是不变的,对于诸如“度量衡”之类的对象也是如此……俗话说“一磅重的世界”。


0

我的原型代码:http : //ideone.com/x7hz7i

我的设计要点:

  1. 选择作为属性获取/设置的计量单位(计量单位)
    长度len =新的Length();
    len.Meters = 2.0;
    Console.WriteLine(len.Feet);
    
  2. 为选择UoM命名的构造函数
    长度len = Length.FromMeters(2.0);
    
  3. ToString对UoM的支持
    Console.WriteLine(len.ToString(“ ft”));
    Console.WriteLine(len.ToString(“ F15”));
    Console.WriteLine(len.ToString(“ ftF15”));
    
  4. 往返转换(双精度允许的舍入损耗可忽略不计)
    Length lenRT = Length.FromMeters(长度.FromFeet(Length.FromMeters(len.Meters).Feet).Meters);
    
  5. 运算符重载(但缺乏维度类型检查)
    //相当混乱,错误,不安全,并且如果不使用F#或C ++ MPL可能无法实现。
    //它接着说,维分析不是一个可选功能的计量单位-
    //是否直接使用它。这是必需的

0

杂志上有一篇很好的文章,用德语是不恰当的:http : //www.dotnetpro.de/articles/onlinearticle1398.aspx

基本思想是使Unit类(如Length)具有BaseMeasurement。该类包含转换因子,运算符重载,ToString重载,字符串解析器和作为索引器的实现。我们甚至已经实现了Architectual视图,但它没有作为库发布。

public class Length : MeasurementBase
    {
        protected static double[] LengthFactors = { 1, 100, 1000, 0.001, 100 / 2.54 };
        protected static string[] LengthSymbols = { "m", "cm", "mm", "km", "in" };
...
      public virtual double this[Units unit]
        {
            get { return BaseValue * LengthFactors[(int)unit]; }
            set { BaseValue = value / LengthFactors[(int)unit]; }
        }
...

        public static ForceDividedByLength operator *(Length length, Pressure pressure1)
        {
            return new ForceDividedByLength(pressure1[Pressure.Units.kNm2] * length[Units.m], ForceDividedByLength.Units.kNm);
        }

...

因此,您可以通过Pressure运算符查看用法,或者只是:

var l = new Length(5, Length.Units.m)    
Area a = l * new Length("5 m");
a.ToString() // -> 25 m^2
double l2 = l[Length.Units.ft];

但是正如您所说,我也没有找到独角兽:)


-1

这是Unix 命令的存在理由units它使用数据文件驱动的方法来指定关系来完成所有工作。


谢谢您的提及units。单元无法使用我的更广泛的解决方案的主要原因是自由格式字符串。当然,它提供了错误消息作为回报,但是这种方法针对的是将这些代码与我们的应用程序集成在一起的开发人员。自由格式字符串会带来太多出错的机会。

1
您应该查看units的数据文件。它定义数量之间关系的方法非常简洁,可能对您的问题很有用。
罗斯·帕特森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.