Структура данных для доступа к единицам измерения


17

TL; DR - я пытаюсь разработать оптимальную структуру данных для определения единиц в единице измерения.


А Unit of measureпо существу является value(или количеством), связанным с unit. Единицы СИ имеют семь основ или размеров. А именно: длина, масса, время, электрический ток, температура, количество вещества (молей) и сила света.

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

У нас есть приложение, которое сильно зависит от подразумеваемых единиц. Мы будем встраивать единицы в имя переменной или столбца. Но это создает проблемы, когда нам нужно указать единицу измерения с разными единицами. Да, мы можем преобразовывать значения при вводе и отображении, но это генерирует много служебного кода, который мы хотели бы инкапсулировать в своем собственном классе.

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

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

UOM myUom1 = новая UOM (10, вольт);
UOM myUom2 = новая UOM (43,2, Ньютоны);

Конечно, мы используем сочетание единиц Imperial и SI в зависимости от потребностей наших клиентов.

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


Как лучше всего определить единицы, производные единицы и ставки, которые нам нужно использовать для создания класса наших единиц измерения? Я мог видеть использование одного или нескольких перечислений, но это может расстраивать других разработчиков. Единственное перечисление было бы огромным с 200+ записями, тогда как множественные перечисления могли бы сбивать с толку, основываясь на СИ против Имперских юнитов, и дополнительную разбивку на основе категоризации самого юнита.

Примеры Enum, показывающие некоторые из моих проблем:

myUnits.Volt
myUnits.Newton
myUnits.meter

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

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


Одна из библиотек, на которую я смотрел, позволяет вводить единицы в свободной форме через строку. Затем их класс UOM проанализировал строку и распределил ее соответственно. Сложность такого подхода заключается в том, что он заставляет разработчика задуматься и запомнить, какие правильные форматы строк. И я рискую ошибкой / исключением во время выполнения, если мы не добавим дополнительные проверки в коде для проверки строк, передаваемых в конструктор.

Другая библиотека создала слишком много классов, с которыми разработчику пришлось бы работать. Наряду с эквивалентной UoM он предоставил , DerivedUnitи RateUnitи так далее. По сути, код был слишком сложным для задач, которые мы решаем. Эта библиотека, по сути, допускает любые: любые комбинации (что является законным в мире единиц), но мы рады охватить нашу проблему (упростить наш код), не допуская каждую возможную комбинацию.

Другие библиотеки были смехотворно просты и даже не рассматривали перегрузку операторов, например.

Кроме того, я не так обеспокоен попытками неправильных преобразований (например: вольт в метры). Разработчики - единственные, кто получит доступ на этом уровне в данный момент, и нам не обязательно защищать от подобных ошибок.


Не могли бы вы объяснить, каким образом библиотеки, которые вы нашли, соответствуют вашим потребностям?
svick


1
@MainMa - спасибо за эту ссылку. Нам не нужно проводить анализ размеров, поскольку наше проблемное пространство достаточно мало, чтобы мы могли просто объявить разрешенные преобразования. Это будет сложность для создания, но это единовременная стоимость.

1
Можете ли вы объяснить, какие конверсии вам нужны? Является ли это только масштабным преобразованием (например, из метра в сантиметр) или также межпространственным преобразованием (например, массой в силу)?
Барт ван Инген Шенау

1
Рассматривали ли вы перенос части кода на F #? Этот язык имеет единицы измерения build 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};

Имперские и СИ единицы могут быть обработаны путем добавления коэффициента пересчета.

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


Таким образом, все производные модули эквивалентны C ++ const? Я полагаю, они заключены в пространство имен, чтобы избежать загрязнения?

1
@ GlenH7 - Это относится к метапрограммированию шаблонов. Они на самом деле представлены в виде отдельных типов (например, mpl::vector_c<int,1,0,0,0,0,0,0>) вместо 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

Не уверен, что вы имеете в виду, для каждой единицы хранится только одно значение. Для длины он содержит поле метров типа double, а для массы - килограммы. При преобразовании в другие единицы это значение пропускается через функцию преобразования. Эти образцы немного устарели, но применяется та же концепция.
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;
    ЕЫпе (len.Feet);
    
  2. Именованный конструктор по выбору UoM
    Длина len = Длина. Из Метров (2.0);
    
  3. Поддержка ToString для UoM
    Console.WriteLine (len.ToString ( "фут"));
    ЕЫпе (len.ToString ( "F15"));
    ЕЫпе (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

Основная идея состоит в том, чтобы иметь класс Unit, например Length, с BaseMeasurement. Класс содержит коэффициент преобразования, операторные перегрузки, перегрузки 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

Это смысл команды Unix units, которая делает все это, используя подход, основанный на файлах данных, для определения отношений.


Спасибо за упоминание units. Основная причина, по которой модули не будут работать для моего более широкого решения - это строки произвольной формы. Конечно, он возвращает сообщения об ошибках, но этот подход нацелен на разработчиков, которые будут интегрировать этот код с нашим приложением. Строки произвольной формы предоставляют слишком много возможностей для ошибок.

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