測定単位にアクセスするためのデータ構造


17

TL; DR-最適なデータ構造を設計して、測定単位内の単位を定義しようとしています。


A Unit of measureは、本質的ににvalue関連付けられた(または数量)unitです。 SIユニットには7つのベースまたはディメンションがあります。すなわち:長さ、質量、時間、電流、温度、物質の量(モル)、および光度。

これは十分に簡単ですが、多くの派生ユニットと頻繁に使用するレートがあります。結合されたユニットの例はNewton:でkg * m / s^2あり、レートの例はですtons / hr

暗黙のユニットに大きく依存するアプリケーションがあります。変数または列名に単位を埋め込みます。しかし、異なる単位で測定単位を指定する必要がある場合、これは問題を引き起こします。はい、入力および表示時に値を変換できますが、これにより、独自のクラス内にカプセル化する多くのオーバーヘッドコードが生成されます。

コードプレックスやその他の共同作業環境には多くのソリューションがあります。プロジェクトのライセンスは同意できますが、プロジェクト自体は通常、非常に軽量または重すぎます。「ちょうどいい」というユニコーンを追いかけています。

理想的には、次のようなものを使用して新しい測定単位を定義できます。

UOM myUom1 =新しいUOM(10、ボルト);
UOM myUom2 = new UOM(43.2、Newtons);

もちろん、クライアントのニーズに基づいて、ImperialユニットとSIユニットを組み合わせて使用​​します。

また、このユニットの構造を将来のデータベーステーブルと同期させて、データ内で同じ程度の一貫性を提供する必要があります。


測定単位クラスを作成するために使用する必要がある単位、派生単位、およびレートを定義する最良の方法は何ですか?1つ以上の列挙型を使用していることはわかりましたが、他の開発者にとってはイライラする可能性があります。単一の列挙型は200以上のエントリで巨大になりますが、複数の列挙型はSIと帝国のユニットに基づいて混乱し、ユニット自体の分類に基づいた追加の内訳があります。

私の懸念のいくつかを示す列挙型の例:

myUnits.Volt
myUnits.Newton
myUnits.meter

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

使用中のユニットのセットはかなり明確に定義されており、有限のスペースです。クライアントから需要がある場合、新しい派生ユニットまたはレートを拡張および追加する機能が必要です。このプロジェクトはC#で作成されていますが、より広範な設計の側面は複数の言語に適用できると思います。


私が調べたライブラリの1つでは、文字列を介したユニットの自由形式の入力が可能です。次に、UOMクラスは文字列を解析し、それに応じてスロットを割り当てました。このアプローチの課題は、正しい文字列形式が何であるかを開発者に考えさせ、記憶させることです。そして、コンストラクターで渡される文字列を検証するためにコード内に追加のチェックを追加しないと、ランタイムエラー/例外のリスクが発生します。

別のライブラリは、開発者が作業しなければならないクラスを本質的に多く作成しました。同等のUOMとともに、DerivedUnitなどRateUnitを提供しました。基本的に、コードは、私たちが解決しようとしている問題に対して非常に複雑でした。そのライブラリは基本的にany:anyの組み合わせ(ユニットの世界では合法です)を許可しますが、可能なすべての組み合わせを許可しないことで問題の範囲を広げることができます(コードを簡素化します)。

他のライブラリはとてつもなくシンプルで、たとえば演算子のオーバーロードも考慮していませんでした。

さらに、誤った変換(たとえば:ボルトからメートル)の試みについても心配していません。この時点でこのレベルでアクセスできるのは開発者だけであり、これらのタイプの間違いから保護する必要は必ずしもありません。


見つけたライブラリがどのようにニーズに合わないのか説明していただけますか?
svick


1
@MainMa-そのリンクをありがとう。問題のスペースが十分に小さいため、許可された変換を宣言するだけなので、ディメンション分析を行う必要はありません。生成するのはスローですが、それは1回限りのコストです。

1
必要なコンバージョンの種類を説明できますか?それはスケーリング変換(例:メートルからセンチメートル)だけですか、それとも交差次元変換(例:質量から力)ですか?
バートヴァンインゲンシェナウ

1
コードの一部をF#に移動することを検討しましたか?その言語には、測定単位build intがあります。
ピート

回答:


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ではなく個別のタイプ(例:)として表されます。この記事では、最初に説明のためにconstアプローチを紹介しています(おそらく、私はそれをうまく説明しなかったでしょう)。constを使用すると、代わりに機能します(コンパイル時の型安全性が失われます)。名前の汚染を避けるために名前空間を使用することは、確かにオプションです。
ジョシュケリー

8

Units.NETをGithubNuGetでリリースしました。

それはあなたにすべての一般的なユニットとコンバージョンを提供します。軽量で単体テスト済みで、PCLをサポートしています。

あなたの質問に向かって:

  • これは、実装の軽量化です。焦点は、測定単位の明確な表現、変換、構築を支援することです。
  • 方程式ソルバーはありません。計算から新しい単位を自動的に導出しません。
  • ユニットを定義するための1つの大きな列挙。
  • ユニット間で動的に変換するための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

意味がわかりませんが、ユニットごとに保存される値は1つだけです。長さには、double型のメートルフィールドを保持し、質量にはキログラムを保持します。他の単位に変換するとき、変換関数を介してその値を実行します。これらのサンプルは現在少し時代遅れですが、同じ概念が適用されます。
アンギュラーセン

私は同様の結論にジャンプして...私は意味misspoke推測しますTupleUnitConverterクラスは表示されませんが、IMOはTupleクラスと同様の機能を共有しているようです。
Chef_Code

Tupleの比較についてはまだわかりませんが、使用法の最新の例についてはgithubページご覧ください
アンギュラーセン

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同じの2つに対して実行される場合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);

互換性のないユニットでの操作は問題と見なされないため、その点でデザインをタイプセーフにしようとしませんでした。2つのユニットが同じベースユニットを参照していることを確認することにより、ランタイムチェックを追加することができます。


温度について言及したので、何95F - 85Fですか?なに20C - 15C?両方の例では、両方UOMのが同じになりunitます。減算は直接実行されますか?

@MattFenwick:結果はそれぞれ10 Fとになり5 Cます。不要な変換を回避するため、可能であれば計算は直接実行されます。に単位変換メソッドを追加するのはかなり簡単ですUOMが、摂氏と華氏の変換ではunit、スケーリング係数に加えてオフセットの可能性を使用してクラスを拡張する必要があります。
バートヴァンインゲンシェナウ

しかし95F - 85F!= 10F

1
@MattFenwick:教えてください。あなたがの温度を下げた場合、それはどのように風邪を取得しない95F85F?私の知る限り、華氏はまだ線形スケールです。
バートヴァンインゲンシェナウ

2
それはケルビンに変換する方が簡単ですので、のは、摂氏例をやってみましょう:私たちが言うならば20C - 15C = 5C、我々は言っている293.15K - 288.15K = 278.15K、明らかに間違っているいます、。

2

あなたのコードが何をしていて、それが何を許可するかを考えてください。可能なすべての単位を含む単純な列挙型を使用すると、ボルトをメートルに変換するようなことができます。これは明らかに人間には当てはまりませんが、ソフトウェアは喜んで試みます。

私はこれに漠然と似たようなことを一度しましたが、私の実装には抽象ベースクラス(長さ、重さなど)がすべて実装されていましたIUnitOfMeasure。各抽象基本クラスは、すべての変換作業に使用するデフォルト型(クラスにLengthはclass のデフォルト実装がありますMeter)を定義しました。したがってIUnitOfMeasure、2つの異なるメソッドを実装ToDefault(decimal)しましたFromDefault(decimal)

ラップしたい実際の数は、IUnitOfMeasureジェネリック引数として受け入れるジェネリック型でした。ようなことMeasurement<Meter>(2.0)を言うと、自動タイプセーフが得られます。これらのクラスに適切な暗黙の変換および数学メソッドを実装するとMeasurement<Meter>(2.0) * Measurement<Inch>(12)、デフォルトのタイプ(Meter)で結果を返すなどのことができます。ニュートンのような派生ユニットを作成したことはありません。私は単にそれらをキログラム*メートル/秒/秒として残しました。


ジェネリック型を使用して提案しているアプローチが気に入っています。

1

答えは、MarioVWのStack Overflowの応答にあると考えています。

.Net 4-0でTupleを使用できる実際の例

タプルを使用すると、2次元辞書(または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. プロパティの取得/設定としてのUoM(測定単位)の選択
    長さlen = new Length();
    len.Meters = 2.0;
    Console.WriteLine(len.Feet);
    
  2. UoMを選択するための名前付きコンストラクター
    長さlen = Length.FromMeters(2.0);
    
  3. UoMのToStringサポート
    Console.WriteLine(len.ToString( "ft"));
    Console.WriteLine(len.ToString( "F15"));
    Console.WriteLine(len.ToString( "ftF15"));
    
  4. ラウンドトリップ変換(倍精度で許容される無視できる丸め損失)
    長さlenRT = Length.FromMeters(Length.FromFeet(Length.FromMeters(len.Meters).Feet).Meters);
    
  5. 演算子のオーバーロード(ただし次元の型チェックはありません)
    //かなり乱雑でバグが多く、安全ではないため、F#またはC ++ MPLを使用しないと不可能な場合があります。
    // 次元分析はUoMのオプション機能ではないということです -
    //直接使用するかどうか。それはされて必要

0

雑誌にはふさわしくないドイツ語の記事がありますhttp : //www.dotnetpro.de/articles/onlinearticle1398.aspx

基本的な考え方は、BaseMeasurementを持つLengthのようなUnitクラスを持つことです。このクラスには、変換係数、演算子オーバーロード、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のデータファイルを。数量間の関係を定義する方法は非常に簡潔であり、問​​題に役立つ可能性があります。
ロスパターソン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.