Struttura dei dati per accedere alle unità di misura

17

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

Un Unit of measure è essenzialmente un value (o quantità) associato 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 tariffe che usiamo frequentemente. Un esempio di unità combinata sarebbe il Newton: kg * m / s^2 e un tasso di esempio sarebbe tons / hr .

Abbiamo un'applicazione che fa molto affidamento sulle unità implicite. Incorporeremo le unità all'interno del nome della variabile o della colonna. Ma questo crea problemi quando abbiamo bisogno di specificare un'unità di misura con unità diverse. Sì, possiamo convertire i valori in input e display ma questo genera un sacco di codice di sovraccarico che vorremmo incapsulare all'interno della sua stessa classe.

Esistono numerose soluzioni su codeplex e altri ambienti collaborativi. La concessione di licenze per i progetti è gradevole, ma il progetto di solito finisce per essere troppo leggero o troppo pesante. Stiamo dando la caccia al nostro unicorno di "giusto".

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

UOM myUom1 = new UOM(10, volts);
UOM myUom2 = new UOM(43.2, Newtons);

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

Abbiamo anche bisogno di mantenere questa struttura di unità sincronizzata con una futura tabella di database in modo da poter fornire lo stesso grado di coerenza anche all'interno dei nostri dati.

Qual è il modo migliore per definire le unità, le unità derivate e le tariffe che dobbiamo utilizzare per creare la nostra classe di unità di misura? Potrei vedere usando una o più enumerazioni, ma potrebbe essere frustrante per altri sviluppatori. Un singolo enum sarebbe enorme con oltre 200 voci, mentre più enumerazioni potrebbero essere confuse basandosi su unità SI contro unità imperiali e ulteriore suddivisione basata sulla categorizzazione dell'unità stessa.

Esempi di enum che mostrano alcuni dei miei dubbi:

myUnits.Volt
myUnits.Newton
myUnits.meter

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

Il nostro set di unità in uso è 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 di progettazione più ampi siano applicabili a più lingue.

Una delle librerie che ho visto consente l'immissione di unità in forma libera tramite stringa. La loro classe UOM ha poi analizzato la stringa e inserito le voci 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 essenzialmente ha creato troppe classi con le quali lo sviluppatore avrebbe dovuto lavorare. Insieme a un UOM equivalente ha fornito un DerivedUnit e RateUnit e così via. Essenzialmente, 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 risolvere il nostro problema (semplificare il nostro codice) non consentendo ogni possibile combinazione.

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

Inoltre, non sono 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 proteggere da questi tipi di errori.

    
posta GlenH7 08.05.2013 - 15:14
fonte

9 risposte

11

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

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, forza (massa * distanza / tempo ^ 2) sarebbe rappresentata come

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

Le unità Imperial contro SI possono 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.

    
risposta data 08.05.2013 - 15:37
fonte
8

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

Ti dà tutte le unità e le conversioni comuni. È leggero, unità testata e supporta PCL.

Verso la tua domanda:

  • Questo è nella parte più leggera delle implementazioni. Il focus è di aiutare nella rappresentazione inequivocabile, nella conversione e nella costruzione di unità di misura.
  • Non esiste un risolutore di equazioni, non ricava automaticamente nuove unità dai calcoli.
  • Un grande enume per la definizione delle unità.
  • Classe UnitConverter per la conversione dinamica tra unità.
  • Strutture dati immutabili per la conversione esplicita tra le unità.
  • Operatori sovraccaricati per aritmetica semplice.
  • Estendere a nuove unità e conversioni è questione di aggiungere una nuova enumerazione per la conversione dinamica e aggiungere un'unità di misura di classe, come Lunghezza, per definire proprietà esplicite di conversione e sovraccarico dell'operatore.

Devo ancora vedere il Santo Graal delle soluzioni in questo settore. Come affermi, può facilmente diventare troppo complesso o troppo prolisso per 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);
    
risposta data 22.07.2013 - 01:00
fonte
3

Se riesci a passare a F # usando C #, F # ha un sistema di unità di misura (implementato usando i metadati sui valori) che sembra si adatti a ciò che stai cercando di fare:

link

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 *)
    
risposta data 08.05.2013 - 15:39
fonte
3

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

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

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

  • Una classe UOM contenente un valore e un riferimento al unit del valore. La classe UOM fornisce operatori sovraccaricati per l'aggiunta / sottrazione di un altro UOM e per la moltiplicazione / divisione con un valore adimensionale.

    Se l'addizione / sottrazione viene eseguita su due UOM con lo stesso unit , viene eseguita direttamente. Altrimenti entrambi i valori vengono convertiti nelle rispettive unità di base e aggiunti / sottratti. Il risultato è riportato come 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 il design sicuro per il tipo in questo senso. È possibile aggiungere un controllo di runtime verificando che due unità si riferiscano alla stessa unità di base.

    
risposta data 08.05.2013 - 17:50
fonte
2

Pensa a ciò che sta facendo il tuo codice e cosa permetterà. Avere una semplice enumerazione con tutte le unità possibili in esso consente di fare qualcosa come convertire Volt in metri. Ovviamente non è valido per un umano, ma il software sarà felice di provarlo.

Ho fatto qualcosa di vagamente simile a questo una volta, e la mia implementazione aveva classi base astratte (lunghezza, peso, ecc.) che tutte implementavano IUnitOfMeasure . Ogni classe di basi astratte ha definito un tipo predefinito (classe Length ha un'implementazione predefinita di classe Meter ) che userebbe per tutti i lavori di conversione. Quindi IUnitOfMeasure ha implementato due metodi diversi, ToDefault(decimal) e FromDefault(decimal) .

Il numero effettivo che volevo avvolgere era un tipo generico che accetta IUnitOfMeasure come argomento generico. Dire qualcosa come Measurement<Meter>(2.0) ti dà sicurezza di tipo automatico. L'implementazione delle corrette conversioni implicite e dei metodi matematici su queste classi consente di fare cose come 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 Kilogram * Meter / Second / Second.

    
risposta data 08.05.2013 - 15:30
fonte
1

Credo che la risposta sia nella risposta Overflow dello stack di MarioVW a:

Practical Example Where Tuple can be used in .Net 4-0?

With tuples you could easily implement a two-dimensional dictionary (or n-dimensional for that matter). For example, you could use such a dictionary to implement a currency exchange mapping:

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 applicazione. Tuple è anche immutabile, il che vale anche per oggetti come pesi e misure ... Come dice il proverbio "una pinta fa un giro il mondo intorno".

    
risposta data 14.02.2016 - 11:23
fonte
0

Il mio codice prototype: link

I miei punti di progettazione:

  1. Scelta di UM (Unità di misura) come proprietà get / set
    Length len = new Length();
    len.Meters = 2.0;
    Console.WriteLine(len.Feet);
    
  2. Costruttore con nome per la scelta di UoM
    Length 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 con doppia precisione)
    Length lenRT = Length.FromMeters(Length.FromFeet(Length.FromMeters(len.Meters).Feet).Meters);
    
  5. Sovraccarico dell'operatore (ma mancante controllo del tipo dimensionale)
    // Quite messy, buggy, unsafe, and might not be possible without using F# or C++ MPL.
    // It goes on to say that Dimensional Analysis is not an optional feature for UoM -
    // whether you use it directly or not. It is required.
    
risposta data 09.05.2013 - 17:28
fonte
0

C'è un buon articolo su una rivista in senso stretto che è in tedesco: link

L'idea di base è di avere una classe di unità come la lunghezza con una misurazione di base. La classe contiene il fattore di conversione, gli overload dell'operatore, gli overload ToString, il parser delle stringhe e un'implementazione come un indicizzatore. Abbiamo persino implementato anche la vista Architectual ma non è stata rilasciata 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 nemmeno l'unicorno:)

    
risposta data 24.10.2014 - 09:24
fonte
-1

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

    
risposta data 09.05.2013 - 00:26
fonte

Leggi altre domande sui tag