Struttura dei dati per l'accesso alle unità di misura


17

TL; DR - Sto cercando di progettare una struttura dati ottimale per definire le unità all'interno di un'unità di misura.


A Unit of measureè essenzialmente una value(o quantità) associata a unit. Le unità SI hanno sette basi o dimensioni. Vale a dire: lunghezza, massa, tempo, corrente elettrica, temperatura, quantità di sostanza (moli) e intensità luminosa.

Questo sarebbe abbastanza semplice, ma ci sono un certo numero di unità derivate e tassi che usiamo frequentemente. Un esempio di unità combinata sarebbe il Newton: kg * m / s^2e un esempio di tasso sarebbe tons / hr.

Abbiamo un'applicazione che si basa fortemente su unità implicite. Incorporeremo le unità all'interno del nome della variabile o della colonna. Ma ciò crea problemi quando è necessario specificare un'unità di misura con unità diverse. Sì, possiamo convertire i valori in input e display ma questo genera un sacco di codice ambientale che vorremmo incapsulare all'interno della sua stessa classe.

Esistono numerose soluzioni su codeplex e altri ambienti collaborativi. Le licenze per i progetti sono gradevoli ma il progetto stesso di solito finisce per essere troppo leggero o troppo pesante. Stiamo inseguendo il nostro unicorno di "giusto".

Idealmente, potrei definire una nuova unità di misura usando qualcosa del genere:

UOM myUom1 = nuovo UOM (10, volt);
UOM myUom2 = new UOM (43.2, Newton);

Naturalmente, usiamo un mix di unità imperiali e SI in base alle esigenze dei nostri clienti.

Dobbiamo anche mantenere questa struttura di unità sincronizzata con una futura tabella del database in modo da poter fornire lo stesso grado di coerenza anche nei nostri dati.


Qual è il modo migliore per definire le unità, le unità derivate e le tariffe che dobbiamo usare per creare la nostra classe di unità di misura? Ho potuto vedere usando uno o più enum, ma questo potrebbe essere frustrante per altri sviluppatori. Un singolo enum sarebbe enorme con oltre 200 voci mentre più enumerazioni potrebbero creare confusione in base alle unità SI vs Imperial e una suddivisione aggiuntiva basata sulla categorizzazione dell'unità stessa.

Esempi di Enum che mostrano alcune delle mie preoccupazioni:

myUnits.Volt
myUnits.Newton
myUnits.meter

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

Il nostro set di unità in uso è abbastanza ben definito ed è uno spazio finito. Abbiamo bisogno della capacità di espandere e aggiungere nuove unità o tariffe derivate quando abbiamo la domanda dei clienti per loro. Il progetto è in C # anche se penso che gli aspetti più ampi del design siano applicabili a più lingue.


Una delle librerie che ho esaminato consente l'immissione in formato libero di unità tramite stringa. La loro classe UOM ha quindi analizzato la stringa e inserito le cose di conseguenza. La sfida con questo approccio è che costringe lo sviluppatore a pensare e ricordare quali sono i formati di stringa corretti. E corro il rischio di un errore / eccezione di runtime se non aggiungiamo ulteriori controlli all'interno del codice per convalidare le stringhe che vengono passate nel costruttore.

Un'altra libreria ha essenzialmente creato troppe classi con cui lo sviluppatore avrebbe dovuto lavorare. Insieme con un equivalente UM ha fornito un DerivedUnited RateUnite così via. In sostanza, il codice era eccessivamente complesso per i problemi che stiamo risolvendo. Quella libreria essenzialmente consentirebbe qualsiasi: qualsiasi combinazione (che è legittima nel mondo delle unità) ma siamo felici di chiarire il nostro problema (semplificare il nostro codice) non permettendo ogni possibile combinazione.

Altre librerie erano ridicolmente semplici e per esempio non avevano nemmeno considerato il sovraccarico dell'operatore.

Inoltre, non sono così preoccupato per i tentativi di conversioni errate (ad esempio: volt in metri). Gli sviluppatori sono gli unici che accederanno a questo livello a questo punto e non abbiamo necessariamente bisogno di proteggerci da questi tipi di errori.


Potresti spiegare in che modo le librerie che hai trovato non soddisfano le tue esigenze?
svick


1
@MainMa - grazie per quel link. Non abbiamo bisogno di fare analisi dimensionali poiché il nostro spazio problematico è abbastanza piccolo da dichiarare semplicemente le conversioni consentite. Sarà uno slog da generare ma è un costo una tantum.

1
Puoi spiegare di che tipo di conversioni hai bisogno? Ridimensiona solo la conversione (ad es. Metro in centimetro) o anche le conversioni trasversali (ad es. Massa in forza)?
Bart van Ingen Schenau,

1
Hai considerato di spostare parte del codice su F #? Quel linguaggio ha unità di misura costruite int.
Pete,

Risposte:


11

Le librerie Boost per C ++ includono un articolo sull'analisi dimensionale che presenta un'implementazione di esempio delle unità di misura di gestione.

Riassumendo: le unità di misura sono rappresentate come vettori, con ogni elemento del vettore che rappresenta una dimensione fondamentale:

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

Le unità derivate sono combinazioni di queste. Ad esempio, la forza (massa * distanza / tempo ^ 2) sarebbe rappresentata come

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

Le unità imperiale contro SI potrebbero essere gestite aggiungendo un fattore di conversione.

Questa implementazione si basa su tecniche specifiche del C ++ (usando la metaprogrammazione del modello per trasformare facilmente diverse unità di misura in diversi tipi di tempo di compilazione), ma i concetti dovrebbero trasferirsi ad altri linguaggi di programmazione.


Quindi tutte le unità derivate sono equivalenti ai cons di C ++? Presumo che siano avvolti in uno spazio dei nomi per evitare di inquinare le cose?

1
@ GlenH7 - Questo entra nella roba di metaprogrammazione del modello. In realtà sono rappresentati come tipi separati (ad es. mpl::vector_c<int,1,0,0,0,0,0,0>) Anziché come contro; l'articolo presenta innanzitutto l'approccio ai consoli come spiegazione (e probabilmente non ho spiegato bene). L'uso dei consts funzionerebbe in alternativa (si perderebbe un po 'di sicurezza del tipo di compilazione). L'uso di uno spazio dei nomi per evitare l'inquinamento dei nomi è certamente un'opzione.
Josh Kelley,

8

Ho appena rilasciato Units.NET su Github e su NuGet .

Ti dà tutte le unità e le conversioni comuni. È leggero, testato dall'unità e supporta PCL.

Verso la tua domanda:

  • Questo è all'estremità più leggera delle implementazioni. L'obiettivo è aiutare nella rappresentazione, conversione e costruzione inequivocabili di unità di misura.
  • Non esiste un risolutore di equazioni, non deriva automaticamente nuove unità dai calcoli.
  • Un grande enum per la definizione delle unità.
  • Classe UnitConverter per la conversione dinamica tra unità.
  • Strutture di dati immutabili per la conversione esplicita tra unità.
  • Operatori sovraccarichi per aritmetica semplice.
  • L'estensione a nuove unità e conversioni è una questione di aggiunta di un nuovo enum per la conversione dinamica e aggiunta di un'unità di classe di misura, come Lunghezza, per definire proprietà di conversione esplicite e sovraccarico dell'operatore.

Devo ancora vedere il santo graal di soluzioni in questo dominio. Come dici, può facilmente diventare troppo complesso o troppo prolisso con cui lavorare. A volte, è meglio mantenere le cose semplici e per le mie esigenze questo approccio si è dimostrato sufficiente.

Conversione esplicita

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

Conversione dinamica

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

Il tuo esempio sembra essenzialmente utilizzare unTruple<T1, T2, T3>(x, y, z)
Chef_Code il

Non sei sicuro di cosa intendi, esiste un solo valore memorizzato per ogni unità. Per Lunghezza contiene un campo di metri di tipo doppio, e per Massa è chilogrammi. Quando si converte in altre unità, esegue quel valore attraverso una funzione di conversione. Questi esempi sono un po 'obsoleti ora, ma si applica lo stesso concetto.
angularsen,

Immagino di aver sbagliato a scrivere e di saltare alle conclusioni ... Volevo dire Tuple. Non riesco a vedere la tua UnitConverterclasse, ma IMO sembra che possa condividere una funzionalità simile alla Tupleclasse.
Chef_Code

Non sei ancora sicuro del confronto Tuple, ma vedi la pagina github per esempi aggiornati sull'uso.
angularsen,

3

Se puoi passare a F # invece usando C #, F # ha un sistema di unità di misura (implementato usando metadati su valori) che sembra adatto a ciò che stai cercando di fare:

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

In particolare:

// 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 *)

Buon consiglio, e l'abbiamo considerato. Non credo che F # ci dia la possibilità di controllare come le unità finiscono per essere visualizzate in output però.

2
@ GlenH7 Credo che tu abbia ragione: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

Sulla base del fatto che tutte le conversioni richieste scalano le conversioni (tranne se è necessario supportare le conversioni di temperatura. I calcoli in cui la conversione comporta un offset sono significativamente più complessi), progetterei il mio sistema di "unità di misura" in questo modo:

  • Una classe unitcontenente un fattore di ridimensionamento, una stringa per la rappresentazione testuale dell'unità e un riferimento a cui viene ridimensionato unit. La rappresentazione testuale è lì per scopi di visualizzazione e il riferimento all'unità base per sapere in quale unità si trova il risultato quando si esegue la matematica su valori con unità diverse.

    Per ogni unità supportata, unitviene fornita un'istanza statica della classe.

  • Una classe che UOMcontiene un valore e un riferimento a quello del valore unit. La UOMclasse fornisce operatori sovraccarichi per l'aggiunta / sottrazione di un altro UOMe per la moltiplicazione / divisione con un valore adimensionale.

    Se l'addizione / sottrazione viene eseguita su due UOMcon lo stesso unit, viene eseguita direttamente. Altrimenti entrambi i valori vengono convertiti nelle rispettive unità base e aggiunti / sottratti. Il risultato è riportato come nella base unit.

L'utilizzo sarebbe come

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

Poiché le operazioni su unità incompatibili non sono considerate un problema, non ho cercato di rendere sicuro il tipo di progetto in questo senso. È possibile aggiungere un controllo di runtime verificando che due unità si riferiscano alla stessa unità base.


Da quando hai menzionato la temperatura: che cos'è 95F - 85F? Che cosa è 20C - 15C? In entrambi gli esempi, entrambi UOMavrebbero lo stesso unit. Le sottrazioni verrebbero eseguite direttamente?

@MattFenwick: i risultati sarebbero rispettivamente 10 Fe 5 C. I calcoli vengono eseguiti direttamente se possibile, per evitare conversioni non necessarie. Sarebbe abbastanza banale aggiungere metodi di conversione unitaria UOM, ma per la conversione Celsius-Fahrenheit, la unitclasse dovrebbe essere estesa con la possibilità di un offset oltre a un fattore di ridimensionamento.
Bart van Ingen Schenau,

Ma 95F - 85F! = 10F.

1
@MattFenwick: per favore, illuminami. Che freddo fa ottiene se si abbassa una temperatura di 95Fda 85F? Per quanto ne sappia, Fahrenheit è ancora una scala lineare.
Bart van Ingen Schenau,

2
Facciamo l'esempio Celsius perché è più facile convertirlo in Kelvin: se diciamo 20C - 15C = 5C, allora stiamo dicendo 293.15K - 288.15K = 278.15K, il che è chiaramente sbagliato.

2

Pensa a cosa sta facendo il tuo codice e a cosa consentirà. Avere una semplice enumerazione con tutte le possibili unità in esso mi permette di fare qualcosa come convertire i Volt in metri. Questo ovviamente non è valido per un essere umano, ma il software proverà volentieri.

Una volta ho fatto qualcosa di vagamente simile a questo e la mia implementazione aveva classi di base astratte (lunghezza, peso, ecc.) Che tutte implementate IUnitOfMeasure. Ogni classe di basi astratte ha definito un tipo predefinito (la classe Lengthaveva un'implementazione predefinita della classe Meter) che avrebbe usato per tutto il lavoro di conversione. Quindi IUnitOfMeasureimplementato due diversi metodi, ToDefault(decimal)e FromDefault(decimal).

Il numero effettivo che volevo racchiudere era un tipo generico che accetta IUnitOfMeasurecome argomento generico. Dire qualcosa di simile Measurement<Meter>(2.0)ti dà la sicurezza automatica del tipo. L'implementazione delle conversioni implicite e dei metodi matematici appropriati su queste classi consente di eseguire operazioni simili Measurement<Meter>(2.0) * Measurement<Inch>(12)e restituire un risultato nel tipo predefinito ( Meter). Non ho mai elaborato unità derivate come Newton; Li ho semplicemente lasciati come chilogrammo * metro / secondo / secondo.


Mi piace l'approccio che stai suggerendo con l'uso di tipi generici.

1

Credo che la risposta stia nella risposta Stack Overflow di MarioVW a:

Esempio pratico Dove è possibile utilizzare Tuple in .Net 4-0?

Con le tuple potresti facilmente implementare un dizionario bidimensionale (o n-dimensionale per quella materia). Ad esempio, è possibile utilizzare un dizionario di questo tipo per implementare un mapping di cambio valuta:

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

Avevo un bisogno simile per la mia domanda. Tupleè anche immutabile, il che vale anche per oggetti come Pesi e Misure ... Come dice il proverbio "una pinta a una sterlina il mondo intorno".


0

Il mio codice prototipo: http://ideone.com/x7hz7i

I miei punti di progettazione:

  1. Scelta di UoM (unità di misura) come proprietà get / set
    Lunghezza len = new Lunghezza ();
    len.Meters = 2.0;
    Console.WriteLine (len.Feet);
    
  2. Costruttore nominato per la scelta di UoM
    Lunghezza len = Length.FromMeters (2.0);
    
  3. Supporto ToString per UoM
    Console.WriteLine (len.ToString ( "ft"));
    Console.WriteLine (len.ToString ( "F15"));
    Console.WriteLine (len.ToString ( "ftF15"));
    
  4. Conversione di andata e ritorno (perdita di arrotondamento trascurabile consentita dalla doppia precisione)
    Lunghezza lenRT = Length.FromMeters (Length.FromFeet (Length.FromMeters (len.Meters) .Feet) .Meters);
    
  5. Sovraccarico dell'operatore (ma privo di controllo del tipo dimensionale)
    // Abbastanza disordinato, difettoso, non sicuro e potrebbe non essere possibile senza usare F # o C ++ MPL.
    // Continua affermando che l' analisi dimensionale non è una funzione opzionale per UoM -
    // se lo usi direttamente o no. È richiesto .
    

0

C'è un buon articolo in una rivista che si trova in maniera non ufficiale in tedesco: http://www.dotnetpro.de/articles/onlinearticle1398.aspx

L'idea di base è quella di avere una classe di unità come Lunghezza con una misurazione di base. La classe contiene il fattore di conversione, i sovraccarichi dell'operatore, i sovraccarichi ToString, il parser delle stringhe e un'implementazione come indicizzatore. Abbiamo persino implementato anche la visione di Architectual ma non è una versione come libreria.

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

...

Quindi vedi l'utilizzo con l'operatore Pressure o semplicemente:

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

Ma come hai detto, non ho trovato neanche l'unicorno :)


-1

Questa è la ragion d'essere del unitscomando Unix , che fa tutto usando un approccio basato su file di dati per specificare le relazioni.


Grazie per averlo menzionato units. Il motivo principale per cui le unità non funzionano per la mia soluzione più ampia sono le stringhe di forma libera. Certo, fornisce in cambio messaggi di errore, ma questo approccio è rivolto agli sviluppatori che integreranno questo codice con la nostra applicazione. Le stringhe in formato libero presentano troppe opportunità di errore.

1
Dovresti dare un'occhiata al unitsfile di dati di. Il modo in cui definisce le relazioni tra quantità è molto pulito e potrebbe essere utile al tuo problema.
Ross Patterson
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.