度量单位 (F#)

F# 中的浮点值和带符号整数值可以具有关联的度量单位,这些度量单位通常用于指示长度、体积、面积等等。 通过使用带有单位的数量,可以让编译器验证算术关系是否具有正确的单位,从而有助于防止出现编程错误。

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

备注

前面的语法将 unit-name 定义为一个度量单位。 其中的可选部分可用来按照以前定义的单位来定义新的度量值。 例如,下面的行定义了度量值 cm(厘米)。

[<Measure>] type cm

下面的行将度量值 ml(毫升)定义为立方厘米 (cm^3)。

[<Measure>] type ml = cm^3

在前面的语法中,measure 是一个涉及单位的公式。 在涉及单位的公式中,支持整数幂(正和负),单位之间的空格指示两个单位的积,* 也指示单位的积,而 / 指示单位的商。 对于倒数单位,可以使用负整数幂或 /(指示单位公式的分子和分母之间的分隔)。 分母中的多个单位应用括号括起来。 / 后面的用空格分隔的单位将被解释为属于分母的一部分,而 * 后面的任何单位将被解释为属于分子的一部分。

在单位表达式中,既可以单独使用 1 来指示一个无量纲量,也可以将 1 与其他单位一起使用,如在分子中。 例如,比率单位的编写形式为 1/s,其中 s 指示秒。 在单位公式中不使用括号。 不要在单位公式中指定数值转换常量;不过,可以单独定义带有单位的转换常量,并在需要进行单位检查的计算中使用这些常量。

表示相同意义的单位公式可以使用不同的等效方法进行编写。 因此,编译器会按如下方式将单位公式转换为一致的形式:将负数幂转换成倒数,将各个单位组合为单一的分子和分母,并将分子和分母中的单位按字母顺序排列。

例如,kg m s^-2 和 m /s s * kg 均会转换为 kg m/s^2。

可以在浮点表达式中使用度量单位。 通过将浮点数与关联的度量单位一起使用,可增加一层类型安全保护,并有助于避免在您使用弱类型浮点数时,公式中出现单位不匹配的错误。 如果您编写使用单位的浮点表达式,则表达式中的单位必须匹配。

可以通过放置在尖括号中的单位公式来批注文本,如下面的示例所示。

1.0<cm>
55.0<miles/hour>

不要在数字和尖括号之间放置空格;但是,可以包含文本后缀(如 f),如下面的示例所示。

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

此类批注会将文本的类型从其基元类型(如 float)更改为有量纲类型(如 float<cm> 或本例中所示的 float<miles/hour>)。 <1> 单位批注指示一个无量纲量,并且其类型等效于不带单位参数的基元类型。

度量单位的类型是带有额外的单位批注(在括号中指示)的浮点型或带符号整型。 因此,当您编写从 g(克)到 kg(千克)的转换的类型时,可以按如下方式描述类型。

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

度量单位可用于编译时单位检查,但在运行时环境中不会保留。 因此,它们不会影响性能。

度量单位可应用于任何类型,而不仅仅是浮点类型;不过,只有浮点类型、带符号整型和 Decimal 类型支持有量纲量。 因此,只有对基元类型以及包含这些基元类型的聚合使用度量单位才有意义。

下面的示例阐释了度量单位的用法。

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

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

下面的代码示例演示如何将无量纲浮点数转换为有量纲浮点值。 只需将无量纲浮点数乘以应用了量纲的 1.0。 可以将此操作抽象到一个类似 degreesFahrenheit 的函数中。

此外,在将有量纲值传递到应使用无量纲浮点数的函数时,必须抵消单位或者使用 float 运算符将其转换为 float。 在此示例中,由于 printf 应采用无量纲量作为参数,因此,需将传递给 printf 的参数除以 1.0<degC>。

[<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 mutable floatValue = 0.
if System.Double.TryParse(input, &floatValue)
   then 
      printfn "That temperature in Celsius is %8.2f degrees C." ((convertFtoC (degreesFahrenheit floatValue))/(1.0<degC>))
   else
      printfn "Error parsing input."

下面的示例会话演示此代码的输出和输入。

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

使用泛型单位

可以编写泛型函数来运算具有关联的度量单位的数据。 可以通过将一个类型连同一个泛型单位一起指定为类型参数来完成此操作,如下面的代码示例所示。

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

创建带有泛型单位的聚合类型

以下代码演示如何创建一个由带有泛型单位的各个浮点值构成的聚合类型。 通过这种方式,可以创建一个用来处理各种单位的类型。 此外,通过确保具有一组单位的泛型类型与具有另一组单位的相同泛型类型是不同的类型,泛型单位可以保留类型安全。 此技术的基础是可将 Measure 特性应用于类型参数。

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

运行时的单位

度量单位可用于静态类型检查。 当编译浮点值时,将会消除度量单位,这样,单位在运行时就消失了。 因此,任何希望实现依靠在运行时检查单位的功能的尝试都是不可能的。 例如,实现 ToString 函数以打印出单位是不可能的。

转换

若要将带单位的类型(例如 float<'u> )转换为不带单位的类型,您可以使用标准的转换函数。 例如,如下面的代码所示可以将 float 转换为 float 没有单位的值。

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

要将无单位的值转换为有单位的值,您可以乘以用适当的单位进行标注的 1 或 1.0 值。 但在您编写互操作性层时,另外还有一些显式函数可供您用于将无单位值转换为有单位值。 这些函数位于 Microsoft.FSharp.Core.LanguagePrimitives 模块中。 例如,若要从无单位的 float 转换为 float<cm>,请使用 FloatWithMeasure,如下面的代码所示。

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

F# Power Pack 中的度量单位

F# PowerPack 中提供了一个单位库。 该单位库包括 SI 单位和物理常量。

请参见

其他资源

F# 语言参考