Partilhar via


Unidades de medida

O ponto flutuante e os valores inteiros assinados em F# podem ter unidades de medida associadas, que normalmente são usadas para indicar comprimento, volume, massa e assim por diante. Usando quantidades com unidades, você permite que o compilador verifique se as relações aritméticas têm as unidades corretas, o que ajuda a evitar erros de programação.

Nota

Estes exemplos demonstram a correção em cálculos aritméticos envolvendo unidades de medida, o recurso também pode ser aproveitado para adicionar anotação segura de tipo com custos de representação zero para outros tipos, com abordagem como o projeto FSharp.UMX .

Sintaxe

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

Observações

A sintaxe anterior define nome-unidade como uma unidade de medida. A parte facultativa é utilizada para definir uma nova medida em termos de unidades previamente definidas. Por exemplo, a linha a seguir define a medida cm (centímetro).

[<Measure>] type cm

A linha seguinte define a medida ml (mililitro) como um centímetro cúbico (cm^3).

[<Measure>] type ml = cm^3

Na sintaxe anterior, medida é uma fórmula que envolve unidades. Em fórmulas que envolvem unidades, as potências integrais são suportadas (positivas e negativas), os espaços entre unidades indicam um produto das duas unidades, * também indicam um produto de unidades e / indicam um quociente de unidades. Para uma unidade recíproca, você pode usar uma potência inteira negativa ou uma / que indique uma separação entre o numerador e o denominador de uma fórmula de unidade. Várias unidades no denominador devem ser cercadas por parênteses. As unidades separadas por espaços depois de a / são interpretadas como fazendo parte do denominador, mas quaisquer unidades que seguem a * são interpretadas como fazendo parte do numerador.

Você pode usar 1 em expressões unitárias, isoladamente para indicar uma quantidade adimensional, ou em conjunto com outras unidades, como no numerador. Por exemplo, as unidades para uma taxa seriam escritas como 1/s, onde s indica segundos. Parênteses não são usados em fórmulas unitárias. Você não especifica constantes de conversão numéricas nas fórmulas de unidade; no entanto, você pode definir constantes de conversão com unidades separadamente e usá-las em cálculos verificados por unidade.

Fórmulas unitárias que significam a mesma coisa podem ser escritas de várias maneiras equivalentes. Portanto, o compilador converte fórmulas de unidade em uma forma consistente, que converte poderes negativos em recíprocos, agrupa unidades em um único numerador e um denominador, e alfabetiza as unidades no numerador e denominador.

Por exemplo, as fórmulas unitárias kg m s^-2 e m /s s * kg ambas são convertidas em kg m/s^2.

Você usa unidades de medida em expressões de ponto flutuante. O uso de números de ponto flutuante juntamente com unidades de medida associadas adiciona outro nível de segurança de tipo e ajuda a evitar os erros de incompatibilidade de unidade que podem ocorrer em fórmulas quando você usa números de ponto flutuante digitados fracamente. Se você escrever uma expressão de ponto flutuante que usa unidades, as unidades na expressão deverão corresponder.

Você pode anotar literais com uma fórmula de unidade entre colchetes angulares, conforme mostrado nos exemplos a seguir.

1.0<cm>
55.0<miles/hour>

Você não coloca um espaço entre o número e o colchete angular; no entanto, você pode incluir um sufixo literal como , como fno exemplo a seguir.

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

Tal anotação altera o tipo do literal de seu tipo primitivo (como float) para um tipo dimensionado, como float<cm> ou, neste caso, float<miles/hour>. Uma anotação de unidade indica <1> uma quantidade adimensional, e seu tipo é equivalente ao tipo primitivo sem um parâmetro de unidade.

O tipo de unidade de medida é um ponto flutuante ou um tipo integral assinado, juntamente com uma anotação de unidade extra, indicada entre parênteses. Assim, quando você escreve o tipo de uma conversão de g (gramas) para kg (quilogramas), você descreve os tipos da seguinte maneira.

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

As unidades de medida são usadas para verificação de unidade em tempo de compilação, mas não são persistentes no ambiente de tempo de execução. Portanto, eles não afetam o desempenho.

As unidades de medida podem ser aplicadas a qualquer tipo, não apenas a tipos de ponto flutuante; no entanto, apenas tipos de ponto flutuante, tipos integrais assinados e tipos decimais suportam quantidades dimensionadas. Portanto, só faz sentido usar unidades de medida nos tipos primitivos e nos agregados que contêm esses tipos primitivos.

O exemplo a seguir ilustra o uso de unidades de medida.

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

O exemplo de código a seguir ilustra como converter de um número de ponto flutuante adimensional para um valor de ponto flutuante dimensionado. Basta multiplicar por 1.0, aplicando as dimensões ao 1.0. Você pode abstrair isso em uma função como degreesFahrenheit.

Além disso, quando você passa valores dimensionados para funções que esperam números de ponto flutuante adimensionais, você deve cancelar as unidades ou transmitir para float usando o float operador. Neste exemplo, você divide por 1.0<degC> para os argumentos para printf porque printf espera quantidades adimensionais.

[<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."

A sessão de exemplo a seguir mostra as saídas e entradas desse código.

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

Tipos primitivos que suportam unidades de medida

Os seguintes tipos ou aliases de abreviatura de tipo suportam anotações de unidade de medida:

Alias F# Tipo 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

Por exemplo, você pode anotar um inteiro não assinado da seguinte maneira:

[<Measure>]
type days

let better_age = 3u<days>

A adição de tipos inteiros não assinados a esse recurso está documentada em F# RFC FS-1091.

Unidades de medida pré-definidas

Uma biblioteca de unidades está disponível no FSharp.Data.UnitSystems.SI namespace. Ele inclui unidades SI em sua forma de símbolo (como m para meter) no UnitSymbols subnamespace e em seu nome completo (como meter para meter) no UnitNames subnamespace.

Usando unidades genéricas

Você pode escrever funções genéricas que operam em dados que têm uma unidade de medida associada. Para fazer isso, especifique um tipo junto com uma unidade genérica como um parâmetro type, conforme mostrado no exemplo de código a seguir.

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

Criando tipos de coleção com unidades genéricas

O código a seguir mostra como criar um tipo agregado que consiste em valores de ponto flutuante individuais que têm unidades que são genéricas. Isso permite que um único tipo seja criado que funcione com uma variedade de unidades. Além disso, as unidades genéricas preservam a segurança do tipo, garantindo que um tipo genérico que tenha um conjunto de unidades seja um tipo diferente do mesmo tipo genérico com um conjunto diferente de unidades. A base desta técnica é que o Measure atributo pode ser aplicado ao parâmetro 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> }

Unidades em tempo de execução

As unidades de medida são utilizadas para a verificação do tipo estático. Quando os valores de ponto flutuante são compilados, as unidades de medida são eliminadas, de modo que as unidades são perdidas em tempo de execução. Portanto, qualquer tentativa de implementar a funcionalidade que depende da verificação das unidades em tempo de execução não é possível. Por exemplo, implementar uma ToString função para imprimir as unidades não é possível.

Conversões

Para converter um tipo que tem unidades (por exemplo, float<'u>) em um tipo que não tem unidades, você pode usar a função de conversão padrão. Por exemplo, você pode usar float para converter em um float valor que não tem unidades, conforme mostrado no código a seguir.

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

Para converter um valor sem unidade em um valor que tenha unidades, você pode multiplicar por um valor 1 ou 1,0 anotado com as unidades apropriadas. No entanto, para escrever camadas de interoperabilidade, também há algumas funções explícitas que você pode usar para converter valores sem unidade em valores com unidades. Eles estão no módulo FSharp.Core.LanguagePrimitives . Por exemplo, para converter de um unitless float para um float<cm>, use FloatWithMeasure, conforme mostrado no código a seguir.

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

Consulte também