Unités de mesure

En F#, les valeurs entières signées et à virgule flottante peuvent avoir des unités de mesure associées, qui sont généralement utilisées pour indiquer la longueur, le volume, la masse, etc. En utilisant des quantités avec des unités, vous permettez au compilateur de vérifier que les relations arithmétiques ont les unités correctes, ce qui permet d’éviter les erreurs de programmation.

Remarque

Ces exemples illustrent l’exactitude dans les calculs arithmétiques impliquant des unités de mesure. Vous pouvez également exploiter la fonctionnalité pour ajouter une annotation de type sécurisé avec des coûts de représentation nuls à d’autres types, avec une approche telle que celle du projet FSharp.UMX.

Syntaxe

[<Measure>] type unit-name [ = measure ]

Notes

La syntaxe précédente définit unit-name en tant qu’unité de mesure. La partie facultative est utilisée pour définir une nouvelle mesure en matière d’unités précédemment définies. Par exemple, la ligne suivante définit la mesure cm (centimètre).

[<Measure>] type cm

La ligne suivante définit la mesure ml (millilitre) comme un centimètre cube (cm^3).

[<Measure>] type ml = cm^3

Dans la syntaxe précédente, measure est une formule qui implique des unités. Dans les formules qui impliquent des unités, les puissances intégrales sont prises en charge (positives et négatives), les espaces entre les unités indiquent un produit des deux unités, * indique également un produit d’unités et / indique un quotient d’unités. Pour une unité réciproque, vous pouvez utiliser une puissance entière négative ou un / qui indique une séparation entre le numérateur et le dénominateur d’une formule d’unité. Si le dénominateur contient plusieurs unités, celles-ci doivent être entourées de parenthèses. Les unités séparées par des espaces après un / sont interprétées comme faisant partie du dénominateur, mais toutes les unités qui suivent un * sont interprétées comme faisant partie du numérateur.

Vous pouvez utiliser le nombre 1 dans les expressions d’unité, soit seul pour indiquer une quantité sans dimension, soit avec d’autres unités, comme dans le numérateur. Par exemple, les unités d’un taux sont écrites en tant que 1/s, où s représente des secondes. Les parenthèses ne sont pas utilisées dans les formules d’unités. Vous ne spécifiez pas de constantes de conversion numériques dans les formules d’unités ; toutefois, vous pouvez définir des constantes de conversion avec des unités séparément et les utiliser dans des calculs vérifiés par unité.

Il est possible d’écrire de manière équivalente des formules d’unités qui signifient la même chose. Par conséquent, le compilateur convertit les formules d’unités en une forme cohérente, qui convertit les puissances négatives en valeurs réciproques, regroupe les unités en un numérateur unique et un dénominateur, et alphabétise les unités dans le numérateur et le dénominateur.

Par exemple, les formules d’unités kg m s^-2 et m /s s * kg sont toutes deux converties en kg m/s^2.

Vous utilisez des unités de mesure dans les expressions à virgule flottante. L’utilisation de nombres à virgule flottante avec des unités de mesure associées ajoute un autre niveau de cohérence des types et permet d’éviter les erreurs d’incompatibilité d’unités qui peuvent se produire dans les formules lorsque vous utilisez des nombres à virgule flottante faiblement typés. Si vous écrivez une expression à virgule flottante qui utilise des unités, les unités de l’expression doivent correspondre.

Vous pouvez annoter des littéraux avec une formule d’unité entre crochets, comme illustré dans les exemples suivants.

1.0<cm>
55.0<miles/hour>

Vous ne placez pas d’espace entre le nombre et le crochet ; toutefois, vous pouvez inclure un suffixe littéral tel que f, comme dans l’exemple suivant.

// The f indicates single-precision floating point.
55.0f<miles/hour>

Une telle annotation remplace le type primitif du littéral (tel que float) en un type dimensionné, tel que float<cm> ou, dans ce cas, float<miles/hour>. Une annotation d’unité de <1> indique une quantité sans dimension, et son type est équivalent au type primitif sans paramètre d’unité.

Le type d’une unité de mesure est un type à virgule flottante ou un type intégral signé avec une annotation d’unité supplémentaire, indiquée entre crochets. Par conséquent, lorsque vous écrivez le type d’une conversion de g (grammes) en kg (kilogrammes), vous décrivez les types comme suit.

let convertg2kg (x : float<g>) = x / 1000.0<g/kg>

Les unités de mesure sont utilisées pour la vérification des unités au moment de la compilation, mais ne sont pas conservées dans l’environnement d’exécution. Par conséquent, elles n’affectent pas le niveau de performance.

Les unités de mesure peuvent être appliquées à n’importe quel type, pas seulement aux types à virgule flottante ; toutefois, seuls les types à virgule flottante, les types intégraux signés et les types décimaux prennent en charge les quantités dimensionnées. Par conséquent, l’utilisation d’unités de mesure est logique uniquement sur les types primitifs et sur les agrégats qui contiennent ces types primitifs.

L’exemple suivant illustre l’utilisation d’unités de mesure.

// Mass, grams.
[<Measure>] type g
// Mass, kilograms.
[<Measure>] type kg
// Weight, pounds.
[<Measure>] type lb

// Distance, meters.
[<Measure>] type m
// Distance, cm
[<Measure>] type cm

// Distance, inches.
[<Measure>] type inch
// Distance, feet
[<Measure>] type ft

// Time, seconds.
[<Measure>] type s

// Force, Newtons.
[<Measure>] type N = kg m / s^2

// Pressure, bar.
[<Measure>] type bar
// Pressure, Pascals
[<Measure>] type Pa = N / m^2

// Volume, milliliters.
[<Measure>] type ml
// Volume, liters.
[<Measure>] type L

// Define conversion constants.
let gramsPerKilogram : float<g kg^-1> = 1000.0<g/kg>
let cmPerMeter : float<cm/m> = 100.0<cm/m>
let cmPerInch : float<cm/inch> = 2.54<cm/inch>

let mlPerCubicCentimeter : float<ml/cm^3> = 1.0<ml/cm^3>
let mlPerLiter : float<ml/L> = 1000.0<ml/L>

// Define conversion functions.
let convertGramsToKilograms (x : float<g>) = x / gramsPerKilogram
let convertCentimetersToInches (x : float<cm>) = x / cmPerInch

L’exemple de code suivant montre comment convertir un nombre à virgule flottante sans dimension en valeur à virgule flottante dimensionnée. Il vous suffit de multiplier par 1.0, en appliquant les dimensions à la valeur 1.0. Vous pouvez le représenter dans une fonction telle que degreesFahrenheit.

En outre, lorsque vous passez des valeurs dimensionnées à des fonctions qui attendent des nombres à virgule flottante sans dimension, vous devez annuler les unités ou effectuer un cast vers float à l’aide de l’opérateur float. Dans cet exemple, vous divisez par 1.0<degC> pour les arguments vers printf, car printf attend des quantités sans dimension.

[<Measure>] type degC // temperature, Celsius/Centigrade
[<Measure>] type degF // temperature, Fahrenheit

let convertCtoF ( temp : float<degC> ) = 9.0<degF> / 5.0<degC> * temp + 32.0<degF>
let convertFtoC ( temp: float<degF> ) = 5.0<degC> / 9.0<degF> * ( temp - 32.0<degF>)

// Define conversion functions from dimensionless floating point values.
let degreesFahrenheit temp = temp * 1.0<degF>
let degreesCelsius temp = temp * 1.0<degC>

printfn "Enter a temperature in degrees Fahrenheit."
let input = System.Console.ReadLine()
let parsedOk, floatValue = System.Double.TryParse(input)
if parsedOk
   then
      printfn "That temperature in Celsius is %8.2f degrees C." ((convertFtoC (degreesFahrenheit floatValue))/(1.0<degC>))
   else
      printfn "Error parsing input."

L’exemple de session suivant montre les sorties et les entrées de ce code.

Enter a temperature in degrees Fahrenheit.
90
That temperature in degrees Celsius is    32.22.

Types primitifs prenant en charge les unités de mesure

Les types ou alias d’abréviation de type suivants prennent en charge les annotations d’unités de mesure :

Alias F# Type CLR
float32/single System.Single
float/double System.Double
decimal System.Decimal
sbyte/int8 System.SByte
int16 System.Int16
int/int32 System.Int32
int64 System.Int64
byte/uint8 System.Byte
uint16 System.UInt16
uint/uint32 System.UInt32
uint64 System.UIn64
nativeint System.IntPtr
unativeint System.UIntPtr

Par exemple, vous pouvez annoter un entier non signé comme suit :

[<Measure>]
type days

let better_age = 3u<days>

L’ajout de types entiers non signés à cette fonctionnalité est documenté dans F# RFC FS-1091.

Unités de mesure prédéfinies

Une bibliothèque d’unités est disponible dans l’espace de noms FSharp.Data.UnitSystems.SI. Elle inclut les unités SI sous la forme de symboles (comme m pour le mètre) dans le sous-espace de noms UnitSymbols, et avec leur nom complet (comme meter pour le mètre) dans le sous-espace de noms UnitNames.

Utilisation d’unités génériques

Vous pouvez écrire des fonctions génériques qui fonctionnent sur des données avec une unité de mesure associée. Pour ce faire, spécifiez un type avec une unité générique comme paramètre de type, comme illustré dans l’exemple de code suivant.

// Distance, meters.
[<Measure>] type m
// Time, seconds.
[<Measure>] type s

let genericSumUnits ( x : float<'u>) (y: float<'u>) = x + y

let v1 = 3.1<m/s>
let v2 = 2.7<m/s>
let x1 = 1.2<m>
let t1 = 1.0<s>

// OK: a function that has unit consistency checking.
let result1 = genericSumUnits v1 v2
// Error reported: mismatched units.
// Uncomment to see error.
// let result2 = genericSumUnits v1 x1

Création de types de collections avec des unités génériques

Le code suivant montre comment créer un type d’agrégat composé de valeurs à virgule flottante individuelles dotées d’unités génériques. Cela permet de créer un type unique qui fonctionne avec diverses unités. En outre, les unités génériques préservent la cohérence des types en garantissant qu’un type générique disposant d’un ensemble d’unités est un type différent du même type générique avec un ensemble différent d’unités. La base de cette technique est que l’attribut Measure peut être appliqué au paramètre de type.

 // Distance, meters.
[<Measure>] type m
// Time, seconds.
[<Measure>] type s

// Define a vector together with a measure type parameter.
// Note the attribute applied to the type parameter.
type vector3D<[<Measure>] 'u> = { x : float<'u>; y : float<'u>; z : float<'u>}

// Create instances that have two different measures.
// Create a position vector.
let xvec : vector3D<m> = { x = 0.0<m>; y = 0.0<m>; z = 0.0<m> }
// Create a velocity vector.
let v1vec : vector3D<m/s> = { x = 1.0<m/s>; y = -1.0<m/s>; z = 0.0<m/s> }

Unités au moment de l’exécution

Les unités de mesure sont utilisées pour le contrôle de type statique. Lorsque des valeurs à virgule flottante sont compilées, les unités de mesure sont supprimées, de sorte que les unités sont perdues au moment de l’exécution. Par conséquent, toute tentative d’implémentation de fonctionnalités qui dépend de la vérification des unités au moment de l’exécution est impossible. Par exemple, l’implémentation d’une fonction ToString pour imprimer les unités n’est pas possible.

Conversions

Pour convertir un type qui a des unités (par exemple, float<'u>) en un type qui n’a pas d’unités, vous pouvez utiliser la fonction de conversion standard. Par exemple, vous pouvez utiliser float pour convertir le type en une valeur float qui n’a pas d’unités, comme indiqué dans le code suivant.

[<Measure>]
type cm
let length = 12.0<cm>
let x = float length

Pour convertir une valeur sans unité en une valeur contenant des unités, vous pouvez multiplier par une valeur 1 ou 1,0 annotée avec les unités appropriées. Toutefois, pour écrire des couches d’interopérabilité, vous pouvez également utiliser certaines fonctions explicites pour convertir des valeurs sans unité en valeurs avec des unités. Ces fonctions se trouvent dans le module FSharp.Core.LanguagePrimitives. Par exemple, pour convertir une valeur float en float<cm> sans unité, utilisez FloatWithMeasure, comme indiqué dans le code suivant.

open Microsoft.FSharp.Core
let height:float<cm> = LanguagePrimitives.FloatWithMeasure x

Voir aussi