Структура даних для доступу до одиниць вимірювання


17

TL; DR - Я намагаюся створити оптимальну структуру даних для визначення одиниць у межах одиниці вимірювання.


A Unit of measureпо суті є value(або кількістю), пов'язаною з a unit. Одиниці SI мають сім основ або розмірів. А саме: довжина, маса, час, електричний струм, температура, кількість речовини (молі) та інтенсивність світла.

Це було б досить просто, але є ряд похідних одиниць, а також ставки, які ми часто використовуємо. Прикладом комбінованої одиниці може бути Ньютон: kg * m / s^2і приклад ставки tons / hr.

У нас є додаток, який в значній мірі покладається на маються на увазі одиниці. Ми вбудуємо одиниці в назву змінної або стовпця. Але це створює проблеми, коли нам потрібно вказати одиницю виміру з різними одиницями. Так, ми можемо конвертувати значення при введенні та відображенні, але це генерує багато накладних кодів, які ми хотіли б інкапсулювати в межах свого власного класу.

Існує ряд рішень для кодеплексу та інших середовищ для спільної роботи. Ліцензування проектів є приємним, але сам проект зазвичай закінчується занадто легким або занадто важким. Ми переслідуємо власного єдинорога "просто правильно".

В ідеалі я міг би визначити нову одиницю вимірювання, використовуючи щось подібне:

UOM myUom1 = новий UOM (10, вольт);
UOM myUom2 = новий UOM (43,2, ньютонів);

Звичайно, ми використовуємо поєднання імперських та SI-одиниць на основі потреб наших клієнтів.

Нам також потрібно зберегти цю структуру одиниць синхронізованою з майбутнім таблицею баз даних, щоб ми могли забезпечити однакову ступінь узгодженості і в наших даних.


Який найкращий спосіб визначення одиниць, похідних одиниць і ставок, які нам потрібно використовувати для створення нашого класу одиниць вимірювання? Я міг бачити використання одного або декількох перерахунків, але це може неприємно для інших розробників. Один перерахунок був би величезним із 200+ записів, тоді як декілька перерахунків можуть бути заплутаними на основі СІ проти Імператорських одиниць та додаткового розбиття на основі категоризації самої одиниці.

Приклади переліків, які показують деякі мої проблеми:

myUnits.Volt
myUnits.Newton
myUnits.meter

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

Наш набір одиниць, що використовуються, досить чітко визначений і це обмежений простір. Нам потрібна можливість розширювати та додавати нові похідні одиниці або ставки, коли у нас є попит клієнта на них. Проект є на C #, хоча я думаю, що більш широкі аспекти дизайну застосовні до декількох мов.


Одна з бібліотек, яку я розглядав, дозволяє вводити одиниці у вільній формі через рядок. Їх клас UOM потім розбирав рядок і відповідно розрізував речі. Завдання такого підходу полягає в тому, що він змушує розробника думати і пам’ятати, які правильні формати рядків. І я ризикую помилкою / винятком виконання, якщо ми не додамо додаткові перевірки в код, щоб перевірити рядки, що передаються в конструктор.

Інша бібліотека по суті створила занадто багато класів, з якими розробнику доведеться працювати. Поряд з еквівалентної UoM він надав , DerivedUnitі RateUnitі так далі. По суті, код був надмірно складним для проблем, які ми вирішуємо. Ця бібліотека по суті дозволить дозволити будь-які: будь-які комбінації (що є законним у світі одиниць), але ми раді розширити нашу проблему (спростити наш код), не допускаючи будь-якої можливої ​​комбінації.

Інші бібліотеки були смішно простими, наприклад, навіть не вважали перевантаження оператора.

Крім того, мене не так хвилюють спроби неправильних перетворень (наприклад: вольт до метрів). Диви - це єдині, хто отримає доступ на цьому рівні в цей момент, і нам не обов’язково потрібно захищати від таких типів помилок.


Чи можете ви пояснити, якими бібліотеками, які ви знайшли, не відповідають вашим потребам?
svick

1
Дивіться також stackoverflow.com/q/348853/240613
Arseni Mourzenko

1
@MainMa - дякую за це посилання. Нам не потрібно робити розмірний аналіз, оскільки наш проблемний простір досить малий, щоб ми просто оголосили дозволені перетворення. Це буде гаслом, але це разова вартість.

1
Чи можете ви пояснити, які саме конверсії вам потрібні? Це лише масштабне перетворення (наприклад, метр у сантиметр) або також перемірне перетворення (наприклад, маса для примусу)?
Барт ван Іґен Шенау

1
Ви розглядали можливість переміщення частини коду до F #? Ця мова має одиниці вимірювання побудови int.
Піт

Відповіді:


11

Бібліотеки Boost для 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};

Отримані одиниці є комбінаціями цих. Наприклад, сила (маса * відстань / час ^ 2) буде представлена ​​як

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

Імперські та SI одиниці можна обробити, додавши коефіцієнт перетворення.

Ця реалізація покладається на специфічні для C ++ методи (використовуючи шаблон метапрограмування, щоб легко перетворити різні одиниці вимірювання в різні типи часу компіляції), але концепції повинні переноситися на інші мови програмування.


Отже, всі похідні одиниці еквівалентні C ++ consts? Я припускаю, що вони загорнуті в простір імен, щоб уникнути забруднення речей?

1
@ GlenH7 - це потрапляє в шаблон метапрограмування шаблону. Вони фактично представлені як окремі типи (наприклад, mpl::vector_c<int,1,0,0,0,0,0,0>) замість consts; У статті вперше представлений підхід consts за допомогою пояснень (і я, мабуть, не добре це пояснив). Використання consts працювало б як альтернатива (ви втратите певну безпеку типу компіляції). Використання простору імен, щоб уникнути забруднення імен, безумовно, є варіантом.
Джош Келлі

8

Щойно я випустив Units.NET на Github та на NuGet .

Це дає всі загальні одиниці та перетворення. Це невелика вага, перевірена одиниця і підтримує PCL.

До вашого питання:

  • Це на більш легкому кінці реалізації. Основна увага приділяється допомозі в однозначному поданні, перетворенні та побудові одиниць вимірювання.
  • Розв’язувача рівнянь немає, він не виводить нові одиниці з обчислень.
  • Один великий перелік для визначення одиниць.
  • UnitConverter клас для динамічного перетворення між одиницями.
  • Незмінні структури даних для явного перетворення між одиницями.
  • Перевантажені оператори для простої арифметики.
  • Розширення на нові одиниці та перетворення - це питання додавання нового перерахунку для динамічного перетворення та додавання одиниці класу вимірювання, наприклад Довжина, для визначення явних властивостей перетворення та перевантаження оператора.

Мені ще не доводиться бачити святий грааль рішень у цій галузі. Як ви заявляєте, це може легко стати занадто складним або занадто багатослівним для роботи. Іноді, краще, щоб все було просто, і для моїх потреб цей підхід виявився достатнім.

Явна конверсія

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

Не впевнений, що ви маєте на увазі, для кожної одиниці зберігається лише одне значення. Для Довжини він містить метр поле типу подвійне, а для Масса - кілограми. При перетворенні в інші одиниці він виконує це значення за допомогою функції перетворення. Ці зразки зараз трохи застаріли, але застосовується та ж концепція.
angularsen

Я здогадуюсь, як пропустив так само, як і прийшов до висновків ... я мав на увазі Tuple. Я не можу бачити ваш UnitConverterклас, але IMO, схоже, він може поділяти функціонал, подібний Tupleкласу.
Chef_Code

Ще не впевнений у порівнянні Tuple, але на оновлених прикладах використання див. Сторінку github .
angularsen

3

Якщо ви можете перетягнути на F # замість цього, використовуючи C #, 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? В обох прикладах обидва UOMs мали б однакове unit. Чи буде віднімання здійснюватися безпосередньо?

@MattFenwick: Результати будуть відповідно 10 Fі 5 C. Розрахунки виконуються безпосередньо, якщо це можливо, щоб уникнути зайвих перетворень. Додано тривіальне додавання методів перетворення одиниць до UOM, але для перетворення Цельсія-Фаренгейта unitклас слід було б розширити з можливістю зміщення на додаток до коефіцієнта масштабування.
Барт ван Інген Шенау

Але 95F - 85F! = 10F.

1
@MattFenwick: Будь ласка, просвіти мене. Як холодно це отримати , якщо ви знизити температуру з 95Fдопомогою 85F? Наскільки мені відомо, Фаренгейт все ще є лінійною шкалою.
Барт ван Інген Шенау

2
Давайте зробимо приклад Цельсія, тому що його легше перетворити на Кельвіна: якщо ми говоримо 20C - 15C = 5C, то ми говоримо 293.15K - 288.15K = 278.15K, що явно неправильно.

2

Подумайте, що робить ваш код і що він дозволить. Просте перерахування з усіма можливими одиницями в ньому дозволяє мені зробити щось на кшталт перетворення вольт у метри. Це очевидно не стосується людини, але програмне забезпечення із задоволенням постарається.

Я колись робив щось неясно подібне до цього, і моя реалізація мала абстрактні базові класи (довжина, вага тощо), які всі реалізовували IUnitOfMeasure. Кожен клас абстрактних баз визначав тип за замовчуванням (клас Lengthмав реалізацію класу за замовчуванням Meter), який він буде використовувати для всіх робіт з перетворення. Отже, IUnitOfMeasureреалізовані два різні методи, ToDefault(decimal)і FromDefault(decimal).

Дійсна кількість, яку я хотів завершити, була загальним типом, приймаючи IUnitOfMeasureїї загальний аргумент. Говорячи щось подібне, Measurement<Meter>(2.0)ви забезпечуєте безпеку автоматичного типу. Реалізація належних неявних перетворень та методів математики на цих класах дозволяє робити такі речі, як Measurement<Meter>(2.0) * Measurement<Inch>(12)і повертати результат у типі за замовчуванням ( Meter). Я ніколи не розробляв похідні одиниці, такі як Ньютон; Я просто залишив їх як кілограм * метр / секунду / секунду.


Мені подобається підхід, який ви пропонуєте, із використанням родових типів.

1

Я вважаю, що відповідь полягає у відповіді MarioVW на стек переповнення на:

Практичний приклад Де Tuple можна використовувати в .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. Вибір UoM (Одиниця виміру) як отримання / встановлення власності
    Довжина len = нова довжина ();
    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. Перетворення в обидва кінці (незначні втрати округлення, дозволені подвійною точністю)
    Довжина lenRT = length.FromMeters (Length.FromFeet (Length.FromMeters (len.Meters) .Feet) .Meters);
    
  5. Перевантаження оператора (але відсутня перевірка розмірного типу)
    // Досить безладний, баггі, небезпечний і може бути неможливим без використання MP # F # або C ++.
    // Він продовжує говорити , що Розмірний аналіз є не додаткові функцією для UoM -
    // використовуєте ви це чи ні. Це потрібно .
    

0

Існує гарна стаття в журналі, що не є німецькою: http://www.dotnetpro.de/articles/onlinearticle1398.aspx

Основна ідея полягає у тому, щоб мати клас одиниць типу довжини з базовим вимірюванням. Клас містить коефіцієнт перетворення, перевантаження оператора, перевантаження ToString, аналізатор рядків та реалізацію як індексатор. Ми навіть реалізували навіть архітектурний вигляд, але він не випускається як бібліотека.

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

...

Отже, ви бачите використання оператора тиску або просто:

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

Це раціональнаunits команда команди Unix , яка робить все це за допомогою підходу, керованого файлами даних для визначення взаємозв'язків.


Дякую за згадування units. Основна причина, чому одиниці не працюватимуть для мого більш широкого рішення, - це рядки вільної форми. Звичайно, він надає повідомлення про помилки натомість, але цей підхід орієнтований на розробників, які інтегруватимуть цей код у наш додаток. Рядки вільної форми представляють занадто багато можливостей для помилок.

1
Ви повинні подивитися на unitsфайл даних. Те, як воно визначає співвідношення між кількостями, є дуже чистим і може бути корисним для вашої проблеми.
Росс Паттерсон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.