Estrutura de dados para acessar unidades de medida

17

TL; DR - Estou tentando projetar uma estrutura de dados ideal para definir unidades dentro de uma unidade de medida.


A Unit of measureé essencialmente uma value(ou quantidade) associada a a unit. As unidades SI possuem sete bases ou dimensões. A saber: comprimento, massa, tempo, corrente elétrica, temperatura, quantidade de substância (moles) e intensidade luminosa.

Isso seria bastante direto, mas há várias unidades derivadas e taxas que usamos com frequência. Um exemplo de unidade combinada seria o Newton: kg * m / s^2e uma taxa de exemplo seria tons / hr.

Temos um aplicativo que depende muito de unidades implícitas. Incorporaremos as unidades no nome da variável ou coluna. Mas isso cria problemas quando precisamos especificar uma unidade de medida com unidades diferentes. Sim, podemos converter os valores na entrada e na exibição, mas isso gera muitos códigos gerais que gostaríamos de encapsular dentro de sua própria classe.

Existem várias soluções no codeplex e em outros ambientes colaborativos. O licenciamento para os projetos é agradável, mas o próprio projeto geralmente acaba sendo muito leve ou muito pesado. Estamos perseguindo nosso próprio unicórnio de "exatamente certo".

Idealmente, eu poderia definir uma nova unidade de medida usando algo como isto:

UOM myUom1 = nova UOM (10 volts);
UOM myUom2 = nova UOM (43,2, Newtons);

Obviamente, usamos uma mistura de unidades imperiais e SI com base nas necessidades de nossos clientes.

Também precisamos manter essa estrutura de unidades sincronizada com uma tabela futura do banco de dados para que também possamos fornecer o mesmo grau de consistência em nossos dados.


Qual é a melhor maneira de definir as unidades, unidades derivadas e taxas que precisamos usar para criar nossa classe de unidade de medida? Eu pude ver usando uma ou mais enumerações, mas isso pode ser frustrante para outros desenvolvedores. Uma única enumeração seria enorme com mais de 200 entradas, enquanto várias enumerações poderiam ser confusas com base nas unidades SI vs Imperial e em detalhes adicionais com base na categorização da própria unidade.

Exemplos enum mostrando algumas das minhas preocupações:

myUnits.Volt
myUnits.Newton
myUnits.meter

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

Nosso conjunto de unidades em uso é muito bem definido e é um espaço finito. Precisamos da capacidade de expandir e adicionar novas unidades ou taxas derivadas quando houver demanda dos clientes por elas. O projeto está em C #, embora eu ache que os aspectos mais amplos do design sejam aplicáveis ​​a vários idiomas.


Uma das bibliotecas que eu analisei permite a entrada de unidades de forma livre via string. A classe UOM deles analisou a sequência e colocou as coisas de acordo. O desafio dessa abordagem é que ela força o desenvolvedor a pensar e lembrar quais são os formatos corretos de string. E corro o risco de um erro / exceção de tempo de execução, se não adicionarmos verificações adicionais no código para validar as seqüências que estão sendo passadas no construtor.

Outra biblioteca criou essencialmente muitas classes com as quais o desenvolvedor teria que trabalhar. Juntamente com uma UOM equivalente forneceu um DerivedUnite RateUnite assim por diante. Essencialmente, o código era excessivamente complexo para os problemas que estamos resolvendo. Essa biblioteca permitiria essencialmente qualquer: qualquer combinação (o que é legítimo no mundo das unidades), mas estamos felizes em analisar nosso problema (simplificar nosso código), não permitindo todas as combinações possíveis.

Outras bibliotecas eram ridiculamente simples e nem sequer consideravam a sobrecarga do operador, por exemplo.

Além disso, não estou tão preocupado com tentativas de conversões incorretas (por exemplo: volts em metros). Os desenvolvedores são os únicos que acessarão neste nível neste momento e não precisamos necessariamente proteger contra esses tipos de erros.


fonte
Você poderia explicar de que maneira as bibliotecas que você encontrou não atendem às suas necessidades?
precisa
1
Veja também stackoverflow.com/q/348853/240613
Arseni Mourzenko 8/13
1
@ MainMa - obrigado por esse link. Não precisamos fazer análise dimensional, pois nosso espaço de problemas é pequeno o suficiente para declararmos as conversões permitidas. Será um trabalho árduo para gerar, mas isso é um custo único.
1
Você pode explicar que tipo de conversão você precisa? É apenas a conversão de escala (por exemplo, metro para centímetro) ou também conversões multidimensionais (por exemplo, massa em força)?
Bart van Ingen Schenau
1
Você já pensou em mudar parte do código para F #? Essa linguagem possui unidades de medida build int.
Pete

Respostas:

11

As bibliotecas Boost para C ++ incluem um artigo sobre análise dimensional que apresenta uma implementação de exemplo de unidades de medida de manipulação.

Para resumir: As unidades de medida são representadas como vetores, com cada elemento do vetor representando uma dimensão fundamental:

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

Unidades derivadas são combinações delas. Por exemplo, força (massa * distância / tempo ^ 2) seria representada como

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

As unidades imperiais versus unidades SI podem ser tratadas adicionando um fator de conversão.

Essa implementação depende de técnicas específicas do C ++ (usando a metaprogramação de modelos para transformar facilmente diferentes unidades de medida em diferentes tipos de tempo de compilação), mas os conceitos devem ser transferidos para outras linguagens de programação.

Josh Kelley
fonte
Então, todas as unidades derivadas são equivalentes a consts em C ++? Eu presumo que eles estão envolvidos em um espaço para nome para evitar poluir as coisas?
1
@ GlenH7 - Isso entra no modelo de metaprogramação. Na verdade, eles são representados como tipos separados (por exemplo, mpl::vector_c<int,1,0,0,0,0,0,0>) em vez de consts; o artigo apresenta a abordagem de consts primeiro por meio de explicação (e eu provavelmente não expliquei isso muito bem). Usar consts funcionaria como uma alternativa (você perderia alguma segurança do tipo em tempo de compilação). Usar um espaço para nome para evitar a poluição de nomes é certamente uma opção.
Josh Kelley #
8

Acabei de lançar o Units.NET no Github e no NuGet .

Ele fornece todas as unidades e conversões comuns. É leve, testado em unidade e suporta PCL.

Para sua pergunta:

  • Este é o ponto mais leve das implementações. O foco é auxiliar na representação inequívoca, conversão e construção de unidades de medida.
  • Não existe um solucionador de equações, ele não deriva automaticamente novas unidades dos cálculos.
  • Um grande enum para definir unidades.
  • Classe UnitConverter para converter dinamicamente entre unidades.
  • Estruturas de dados imutáveis ​​para converter explicitamente entre unidades.
  • Operadores sobrecarregados para aritmética simples.
  • Estender para novas unidades e conversões é uma questão de adicionar uma nova enumeração para conversão dinâmica e adicionar uma classe de unidade de medida, como Comprimento, para definir propriedades explícitas de conversão e sobrecarga do operador.

Ainda estou para ver o Santo Graal das soluções neste domínio. Como você declara, pode facilmente se tornar muito complexo ou muito detalhado para trabalhar. Às vezes, é melhor manter as coisas simples e, para minhas necessidades, essa abordagem se mostrou suficiente.

Conversão explícita

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

Conversão dinâmica

// 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);
angularsen
fonte
Seu exemplo essencialmente parece utilizar umTruple<T1, T2, T3>(x, y, z)
Chef_Code
Não sei ao certo o que você quer dizer, existe apenas um único valor armazenado para cada unidade. Para Comprimento, ele contém um campo de metros do tipo double e, para Massa, é quilogramas. Ao converter para outras unidades, ele executa esse valor por meio de uma função de conversão. Essas amostras estão um pouco desatualizadas agora, mas o mesmo conceito se aplica.
Angularsen
Acho que errei e tirei conclusões precipitadas ... quis dizer Tuple. Não consigo ver sua UnitConverterturma, mas parece que o IMO pode compartilhar uma funcionalidade semelhante à da Tupleturma.
21416 Chef_Code
Ainda não tenho certeza sobre a comparação da Tupla, mas consulte a página do github para obter exemplos atualizados sobre o uso.
Angularsen
3

Se você pode alternar para F # em vez de usar C #, o F # possui um sistema de unidades de medida (implementado usando metadados nos valores) que parece se encaixar no que você está tentando fazer:

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

Notavelmente:

// 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 *)
Paulo
fonte
Boa sugestão, e nós a consideramos. Não acredito que o F # nos permita controlar como as unidades acabam sendo exibidas na saída.
2
@ GlenH7 Eu acredito que você está certo: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.
paul
3

Com base no fato de que todas as conversões necessárias são dimensionadas (exceto se você tiver que suportar conversões de temperatura. Os cálculos em que a conversão envolve um deslocamento são significativamente mais complexos), eu projetaria meu sistema de 'unidade de medida' da seguinte forma:

  • Uma classe que unitcontém um fator de escala, uma sequência para a representação textual da unidade e uma referência à qual a unitescala é dimensionada. A representação textual existe para fins de exibição e a referência à unidade base para saber em qual unidade está o resultado ao fazer cálculos em valores com unidades diferentes.

    Para cada unidade suportada, unité fornecida uma instância estática da classe.

  • Uma classe que UOMcontém um valor e uma referência aos valores unit. A UOMclasse fornece operadores sobrecarregados para adicionar / subtrair outro UOMe para multiplicar / dividir com um valor adimensional.

    Se a adição / subtração for realizada em duas UOMcom o mesmo unit, é realizada diretamente. Caso contrário, ambos os valores são convertidos em suas respectivas unidades base e os adicionados / subtraídos. O resultado é relatado como estando na base unit.

O uso seria como

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

Como as operações em unidades incompatíveis não são consideradas um problema, não tentei tornar o tipo de design seguro nesse aspecto. É possível adicionar uma verificação de tempo de execução, verificando se duas unidades se referem à mesma unidade base.

Bart van Ingen Schenau
fonte
Desde que você mencionou a temperatura: o que é 95F - 85F? O que é 20C - 15C? Nos dois exemplos, ambos UOMs teriam o mesmo unit. As subtrações seriam executadas diretamente?
@MattFenwick: Os resultados seriam respectivamente 10 Fe 5 C. Os cálculos são realizados diretamente, se possível, para evitar conversões desnecessárias. Seria bastante trivial adicionar métodos de conversão de unidades UOM, mas para a conversão Celsius-Fahrenheit, a unitclasse teria que ser estendida com a possibilidade de um deslocamento além de um fator de escala.
Bart van Ingen Schenau
Mas 95F - 85F! = 10F.
1
@ MattFenwick: Por favor, me esclareça. Quão frio fica se você abaixa a temperatura 95Fpor 85F? Que eu saiba, Fahrenheit ainda é uma escala linear.
Bart van Ingen Schenau
2
Vamos fazer o exemplo Celsius porque é mais fácil converter para Kelvin: se dizemos 20C - 15C = 5C, estamos dizendo 293.15K - 288.15K = 278.15K, o que está claramente errado.
2

Pense no que seu código está fazendo e no que ele permitirá. Ter uma enumeração simples com todas as unidades possíveis permite fazer algo como converter Volts em metros. Obviamente, isso não é válido para um ser humano, mas o software tentará com prazer.

Eu fiz algo vagamente semelhante a isso uma vez, e minha implementação teve classes básicas abstratas (comprimento, peso, etc.) que foram implementadas IUnitOfMeasure. Cada classe de bases abstratas definia um tipo padrão (a classe Lengthtinha uma implementação padrão da classe Meter) que seria usada para todo o trabalho de conversão. Portanto, IUnitOfMeasureimplementou dois métodos diferentes, ToDefault(decimal)e FromDefault(decimal).

O número real que eu queria agrupar era um tipo genérico que aceitava IUnitOfMeasurecomo argumento genérico. Dizer algo como Measurement<Meter>(2.0)fornece segurança de tipo automático. A implementação das conversões implícitas e dos métodos matemáticos adequados nessas classes permite que você faça coisas como Measurement<Meter>(2.0) * Measurement<Inch>(12)e retorne um resultado no tipo padrão ( Meter). Eu nunca trabalhei com unidades derivadas como Newtons; Eu simplesmente os deixei como Quilograma * Medidor / Segundo / Segundo.

mgw854
fonte
Eu gosto da abordagem que você está sugerindo com o uso de tipos genéricos.
1

Acredito que a resposta esteja na resposta Stack Overflow do MarioVW para:

Exemplo prático Onde o Tuple pode ser usado no .Net 4-0?

Com as tuplas, você pode implementar facilmente um dicionário bidimensional (ou n-dimensional). Por exemplo, você pode usar esse dicionário para implementar um mapeamento de câmbio:

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

Eu tinha uma necessidade semelhante para o meu aplicativo. Tupletambém é imutável, o que também é válido para objetos como Pesos e Medidas ... Como diz o ditado "um litro a libra ao redor do mundo".

Chef_Code
fonte
0

Meu código de protótipo: http://ideone.com/x7hz7i

Meus pontos de design:

  1. Escolha da UM (unidade de medida) como propriedade / conjunto de propriedades
    Comprimento len = new Length ();
    len.Meters = 2.0;
    Console.WriteLine (len.Feet);
    
  2. Construtor nomeado para escolha de UoM
    Comprimento len = Length.FromMeters (2.0);
    
  3. Suporte ToString para UoM
    Console.WriteLine (len.ToString ("ft"));
    Console.WriteLine (len.ToString ("F15"));
    Console.WriteLine (len.ToString ("ftF15"));
    
  4. Conversão de ida e volta (perda insignificante de arredondamento permitida por dupla precisão)
    Length lenRT = Length.FromMeters (Length.FromFeet (Length.FromMeters (len.Meters) .Feet) .Meters);
    
  5. Sobrecarga do operador (mas falta verificação do tipo dimensional)
    // Bastante bagunçado, com erros, sem segurança e pode não ser possível sem o uso de F # ou C ++ MPL.
    // Continua dizendo que a Análise Dimensional não é um recurso opcional para UoM -
    // se você o usa diretamente ou não. É necessário .
    
rwong
fonte
0

Existe um bom artigo em uma revista, injustamente, em alemão: http://www.dotnetpro.de/articles/onlinearticle1398.aspx

A ideia base é ter uma classe Unit como Length com uma BaseMeasurement. A classe contém o fator de conversão, sobrecargas do operador, sobrecargas ToString, analisador de strings e uma implementação como indexador. Até implementamos a visualização Architectual, mas ela não é lançada como uma biblioteca.

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

...

Então você vê o uso com o operador Pressão ou apenas:

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

Mas como você disse, também não encontrei o unicórnio :)

KCT
fonte
-1

Essa é a razão de ser do unitscomando Unix , que faz tudo usando uma abordagem orientada a arquivos de dados para especificar os relacionamentos.

Ross Patterson
fonte
Obrigado por mencionar units. O principal motivo pelo qual as unidades não funcionam para minha solução mais ampla são as seqüências de forma livre. Concedido, ele fornece mensagens de erro em troca, mas essa abordagem está direcionada aos desenvolvedores que integrarão esse código ao nosso aplicativo. Seqüências de caracteres livres apresentam muitas oportunidades de erro.
1
Você deve dar uma olhada no unitsarquivo de dados. A maneira como define as relações entre quantidades é muito clara e pode ser útil para o seu problema.
Ross Patterson