65.9K
CodeProject 正在变化。 阅读更多。
Home

让你的大脑进入另一个维度 - 用于物理单位的 C# 库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (77投票s)

2021年7月6日

CPOL

16分钟阅读

viewsIcon

93860

downloadIcon

1504

用于物理和工程计算的 C# 库

引言

这个项目的最初灵感来自于1999年NASA的火星探测器轨道器失事。该探测器未能进入火星轨道,是因为公制(SI)单位和美国习惯单位之间的混淆。一个子系统向另一个期望牛顿秒(Newton Seconds)的子系统提供了磅力秒(pound-force seconds)的测量值。当探测器减速进入轨道时,它离行星大气层太近,最终烧毁或弹射进入太阳轨道。

因此,我尝试构建一个代码库,通过设计来排除此类错误。它具有以下特点:

  • 它可以用于执行物理和工程学中的许多标准计算。
  • 它基于量纲分析,因此所有物理量都有一个对应的物理量纲,如长度(Length)或质量(Mass)。
  • 它是强类型的,因此不同量纲的物理量只能以科学上有效的方式组合。
  • 在内部,所有值都以SI(公制)单位存储。
  • 值仅在外部接口处转换为特定的单位系统,例如,在转换为string或从string转换时。

它使用C# 9版本编写,并利用.NET 5.0框架。

下面是该库使用的一个示例。

        // Tsiolkovsky rocket equation
        Mass EmptyMass = 120000.Kilograms();
        Mass PropellantMass = 1200000.Kilograms();
        Mass WetMass = EmptyMass + PropellantMass;
        Velocity ExhaustVelocity = 3700.MetresPerSecond();
        Velocity DeltaV = ExhaustVelocity * Math.Log(WetMass / EmptyMass);
        // DeltaV = 8872.21251 m/s

在本文以及示例代码和单元测试中,我使用了我以前的文法学校物理教科书《Nelkon and Parker Advanced Level Physics》中的例子。这是20世纪60年代和70年代英国标准的高中物理教材。

背景

该库基于量纲(dimensions)和单位(units)的概念。

尺寸

物理量的量纲决定了它与一组基本量(如质量、长度和时间)的关系。这些通常缩写为M、L、T等。新的量纲可以通过将这些基本量相乘和相除来派生。所以:

  • 面积 = 长度 × 长度 = L²
  • 体积 = 长度 × 长度 × 长度 = L³
  • 密度 = 质量 / 体积 = M/L³ = ML⁻³
  • 速度 = 长度 / 时间 = L/T = LT⁻¹
  • 加速度 = 速度 / 时间 = LT⁻²
  • 力 = 质量 × 加速度 = MLT⁻²

依此类推。

任何特定物理量的量纲都可以表示为基本量纲的幂的序列(例如,上面的力 = MLT⁻²)。如果量纲不匹配,则尝试加减物理量是无效的。因此,例如,将质量加到体积上是无效的。

国际单位制(S.I.)使用以下基本量纲:

维度 符号 单位 单位符号
质量 M 千克 千克
长度 L m
时间 T second s
电流 安培 A
热力学温度 Θ 开尔文 K
物质的量 N 摩尔 mol
发光强度 J 坎德拉 cd

该库定义了这些基本量纲以及许多派生量纲。

单位

一个单位系统可以定义不同的基本单位来对应各种量纲。例如,SI系统使用千克作为质量单位,而美国和英国系统则使用磅。类似地,我们使用英尺代替米作为长度单位。在体积测量方面,美国和英国系统也存在差异。值得庆幸的是,其他基本量纲的单位在这三个系统中都是相同的。

虽然该库同时定义了SI、美国和英国系统,但也可以创建和使用新的系统。例如,您可以使用日本的shakkanho系统,以shaku(尺)作为长度单位,以kan(贯)作为质量单位。

Using the Code

随附ZIP文件中的代码包含一个Visual Studio解决方案,其中有两个项目:库本身和一个命令行程序,用于测试和演示库的功能。要在您自己的项目中使用该库,请在“\KMB.Library.Units\KMB.Library.Units.csproj”中添加库项目文件,然后将以下using语句添加到您的代码中:

using KMB.Library.Units;
using KMB.Library.Units.Metric;
using KMB.Library.Units.TimeUnits;      // for hours, minutes etc.
using KMB.Library.Units.British;        // For feet and pounds. Or use USA if you prefer

库的内容

Units库定义了各种类和接口。这里讨论主要的一些:

Dimensions

此类用于表示一个物理量纲或它们的组合。它有一个只读字段,用于表示每个量纲的幂。

        public readonly short M; // Mass
        public readonly short L; // Length
        public readonly short T; // Time
        public readonly short I; // Current
        public readonly short Θ; // Temperature
        public readonly short N; // Amount of Substance
        public readonly short J; // Luminous Intensity
        public readonly short A; // Angle. 

请注意角度的值。严格来说,角度是无量纲的,但为了方便起见,可以将其视为具有不同的量纲。这样,例如在转换为string时,我们可以区分角度和无量纲量。

该类有各种构造函数,并定义了乘法和除法的运算符。

       public static Dimensions operator *(Dimensions d1, Dimensions d2)...
       public static Dimensions operator /(Dimensions d1, Dimensions d2)...

使用此类,我们可以定义基本量纲:

        public static readonly Dimensions Dimensionless = new Dimensions(0, 0, 0);
        public static readonly Dimensions Mass = new Dimensions(1, 0, 0);
        public static readonly Dimensions Length = new Dimensions(0, 1, 0);
        public static readonly Dimensions Time = new Dimensions(0, 0, 1);
                :

并定义任何派生量纲:

        public static readonly Dimensions Area = Length * Length;
        public static readonly Dimensions Volume = Area * Length;
        public static readonly Dimensions Density = Mass / Volume;
        public static readonly Dimensions Velocity = Length / Time;
        public static readonly Dimensions AngularVelocity = Angle / Time;
                :

Dimensions类的重载ToString()方法输出每个量纲的幂。

        Dimensions.Pressure.ToString()  // returns "M L⁻¹ T⁻²"
        Dimensions.Resistivity.ToString()  // returns "M L³ T⁻³ I⁻²"

接口 IPhysicalQuantity

此接口是系统中所有物理量的基础。它有两个属性:

        double Value { get; }
        Dimensions Dimensions { get; }

对于每个定义的Dimensions值,都将有一个相应的结构实现IPhysicalQuantity接口。例如,LengthAreaMass等。

物理量示例 - 长度

Length结构实现了IPhysicalQuantity接口。

        public readonly partial struct Length: IPhysicalQuantity

它有一个只读的Value属性。

        public readonly double Value { get; init; }

还有一个Dimensions属性。

        public readonly Dimensions Dimensions { get { return Dimensions.Length; } }

请注意,Dimensions属性如何返回相应的静态定义的Dimensions值。

因此,有了这个结构,我们现在可以定义一个变量来表示一个特定的长度:

        Length l0 = new Length(3.4);        // 3.4 metres

struct定义了许多运算符。例如,您可以将一个length加到另一个上:

        public static Length operator+(Length v1, Length v2)
        {
            return new Length(v1.Value + v2.Value);
        }

或比较两个length

       public static bool operator >(Length v1, Length v2)
        {
            return Compare(v1, v2) > 0;
        }

或者,您可以将两个length相乘来创建一个Area

        public static Area operator*(Length v1, Length v2)
        {
            return new Area(v1.Value * v2.Value);
        }

或者,将一个length除以一个time来创建一个Velocity

        public static Velocity operator/(Length v1, Time v2)
        {
            return new Velocity(v1.Value / v2.Value);
        }

这里是这个除法运算符的使用示例:

        Length l = 100.Metres();
        Time t = 9.58.Seconds();
        Velocity v = l / t;         // v = 10.43 m/s

还有各种ToString()Parse()方法。

        public override string ToString();
        public string ToString(UnitsSystem.FormatOption option);
        public string ToString(UnitsSystem system, UnitsSystem.FormatOption option);
        public string ToString(params Unit[] units);
        public static Length Parse(string s);
        public static Length Parse(string s, UnitsSystem system);

string的格式化和解析实际上委托给当前的单位系统。请参见下文。

这里有一些示例,用于演示ToString()Parse()的各种选项:

Length l = 1234.567.Metres();
string s = l.ToString();    // s = "1.234567 km" (same as BestFit)
// Formatting options:
s = l.ToString(UnitsSystem.FormatOption.Standard); // s = "1234.567 m" 
                                                   // (standard unit for length is metres)
s = l.ToString(UnitsSystem.FormatOption.BestFit);  // s = "1.234567 km" 
                                                   // (kilometres is the best fit unit 
                                                   // for the value)
s = l.ToString(UnitsSystem.FormatOption.Multiple); // s = "1 km 234 m 56 cm 7 mm" 
                                                   // (use multiple units in decreasing value)
// Specify the units:
s = l.ToString(MetricUnits.Metres, MetricUnits.Centimetres); // s = "1234 m 56.7 cm" 
// British units:
s = l.ToString(BritishUnits.System, UnitsSystem.FormatOption.Standard); // s = "4050.41667 ft"
s = l.ToString(BritishUnits.System, UnitsSystem.FormatOption.BestFit);  // s = "1350.13889 yd"
s = l.ToString(BritishUnits.System, UnitsSystem.FormatOption.Multiple); // s = "1350 yd 5 in"
// Specified British units:
s = l.ToString(BritishUnits.Miles, 
               BritishUnits.Feet, BritishUnits.Inches); // s = "4050 ft 5 in"

// Parsing
l = Length.Parse("42 m");    // l = 42 m
l = Length.Parse("42 m 76 cm"); // l = 42.76 m
l = Length.Parse("5 ft 4 in", BritishUnits.System); // l = 1.6256 m
// This will throw an exception
l = Length.Parse("42 m 76 kg");

由于数量类需要大量的类、运算符和方法,因此这些类是使用T4模板处理器生成的。请参阅代码生成部分。

温度

该库包含两个类来处理温度 - AbsoluteTemperature(绝对温度)和TemperatureChange(温度变化)。第一个类用于绝对温度,就像您从温度计上读到的那样:

        AbsoluteTemperature t3 = 600.65.Kelvin();       // melting point of lead
        AbsoluteTemperature c2 = 60.Celsius();          // c2 = 333.15 K

第二个类用于许多公式中,其中温度变化很重要:

        TemperatureChange deltaT = 100.Celsius() - 20.Celsius();
        ThermalCapacity tcKettle = 100.CaloriesPerDegreeKelvin();
        SpecificHeat shWater = 4184.JoulesPerKilogramPerDegreeKelvin();
        Mass mWater = 1.Kilograms();
        ThermalCapacity tcWater = mWater * shWater;
        ThermalCapacity tcTotal = tcKettle + tcWater;
        Energy e = tcTotal * deltaT;    // e = 368208 J

结构 PhysicalQuantity

当强类型量无法满足需求时,这就是一个“万能牌”。它是弱类型的,因此它有自己的属性来表示量纲:

    public readonly partial struct PhysicalQuantity: IPhysicalQuantity
    {
        public double Value { get; init; }
        public Dimensions Dimensions { get; init; }

与强类型量类似,它具有加法等运算符,但这些是在运行时检查的,而不是阻止编译。因此,可以执行此操作:

        PhysicalQuantity l1 = new PhysicalQuantity(2.632, Dimensions.Length);
        PhysicalQuantity l2 = new PhysicalQuantity(2.632, Dimensions.Length);
        PhysicalQuantity sum = l1 + l2;

但这会引发一个异常:

        PhysicalQuantity m = new PhysicalQuantity(65, Dimensions.Mass);
        sum = l1 + m;

但乘法和除法将正确计算出正确的量纲:

        PhysicalQuantity product = l1 * m;
        string s = product.ToString(); // s = "171.08 kg⋅m"

Shapes

该库定义了一些实用类,用于进行与二维和三维形状相关的计算。例如,计算半径为3厘米的圆的面积:

            Circle circle = Circle.OfRadius(3.Centimetres());
            Area area = circle.Area;	// = 28.27 cm²

这是一个使用三维实体形状的例子:

          SolidCylinder cylinder = new SolidCylinder()
                                        {
                                            Mass = 20.Pounds(),
                                            Radius = 2.Inches(),
                                            Height = 6.Inches()
                                        };
            area = cylinder.SurfaceArea;			// = 101.2 in²
            Volume volume = cylinder.Volume;			// = 75.23 in³
            Density density = cylinder.Density			// = 0.2653 lb/in³
            Length radiusOfGyration = cylinder.RadiusOfGyration;// = 1.414 in
            MomentOfInertia I = cylinder.MomentOfInertia;	// = 0.2778 lb⋅ft²

VectorOf

该库定义了一个VectorOf类,可用于表示定向量,如位移、速度或力。我称之为VectorOf,以避免与System.Numerics.Vector类发生名称冲突。

    public class VectorOf<T> where T: IPhysicalQuantity, new()

它有接受3个标量值的构造函数:

        public VectorOf(T x, T y, T z)

或接受幅度和方向(用于二维值):

        public VectorOf(T magnitude, Angle direction)

或接受幅度和两个角度(俯仰角和方位角)的构造函数(用于三维向量):

        public VectorOf(T magnitude, Angle inclination, Angle azimuth)

例如

        // Suppose a ship is travelling due east at 30 mph and a boy runs across the deck
        // in a north west direction at 6 mph.  What is the speed and direction of the boy
        // relative to the sea?
        var v2 = new VectorOf<velocity>(30.MilesPerHour(), 90.Degrees());
        var v3 = new VectorOf<velocity>(6.MilesPerHour(), 315.Degrees());
        var v4 = v2 + v3;
        Velocity m2 = v4.Magnitude; // 26 mph
        Angle a3 = v4.Direction;    // 81 degrees

目前,VectorOf类在内部使用PhysicalQuantity类型。这是因为.NET 5不支持“泛型数学”。当迁移到.NET 6或7版本时,我将在IPhysicalQuantity接口中定义支持数学运算符的静态方法,然后可以重新实现向量数学。

UnitsSystem

该库定义了一个用于单位系统的抽象基类:

        public abstract class UnitsSystem

UnitsSystem的子类负责将量纲转换为string以及从string转换。因此,有各种用于string转换的虚拟方法。还有一个指向当前单位系统的静态引用,默认是Metric

        public static UnitsSystem Current = Metric;

默认情况下,ToString()Parse()方法将使用当前的单位系统。

        internal static string ToString(IPhysicalQuantity qty)
        {
            return Current.DoToString(qty);
        }
        internal static PhysicalQuantity Parse(string s)
        {
            return Current.DoParse(s);
        }

或者,您可以指定要使用的系统:

        internal static string ToString(IPhysicalQuantity qty, UnitsSystem system)
        {
            return system.DoToString(qty);
        }
        public static PhysicalQuantity Parse(string s, UnitsSystem system)
        {
            return system.DoParse(s);
        }

默认情况下,单位系统将使用单位定义查找表来执行string转换。单位定义使用此类:

    public class Unit
    {
        public readonly UnitsSystem System;
        public readonly string Name;
        public readonly string ShortName;
        public readonly string CommonCode;
        public readonly Dimensions Dimensions;
        public readonly double ConversionFactor; //to convert from ISO units
                        :

例如,这里是一些公制系统的定义:

	public static Unit Metres = 
      new Unit(System, "metres", "m", "MTR", Dimensions.Length, 1.0, Unit.DisplayOption.Standard);
	public static Unit SquareMetres = 
      new Unit(System, "squaremetres", "m²", "MTK", Dimensions.Area, 1.0, Unit.DisplayOption.Standard);
	public static Unit CubicMetres = 
      new Unit(System, "cubicmetres", "m³", "MTQ", Dimensions.Volume, 1.0, Unit.DisplayOption.Standard);
	public static Unit Kilograms = 
      new Unit(System, "kilograms", "kg", "KGM", Dimensions.Mass, 1.0, Unit.DisplayOption.Standard);
	public static Unit Seconds = 
      new Unit(System, "seconds", "s", "SEC", Dimensions.Time, 1.0, Unit.DisplayOption.Standard);

或者英制单位的类似定义:

	public static Unit Feet = new Unit
      (System, "feet", "ft", "FOT", Dimensions.Length, feetToMetres, Unit.DisplayOption.Standard);
	public static Unit Inches = new Unit
      (System, "inches", "in", "INH", Dimensions.Length, (feetToMetres/12.0), Unit.DisplayOption.Multi);
	public static Unit Fortnight = new Unit
      (System, "fortnight", "fn", "fn", Dimensions.Time, 3600.0*24.0*14.0, Unit.DisplayOption.Explicit);
	public static Unit Pounds = new Unit
      (System, "pounds", "lb", "LBR", Dimensions.Mass, poundsToKilogrammes, Unit.DisplayOption.Standard);

UN/CEFACT通用代码

每个单位都有一个唯一的UN/CEFACT代码。例如,英制加仑和美制加仑的代码分别为GLI和GLL。在电子交换数量信息时,例如在物料清单或采购订单中,可以使用这些代码。在少数没有通用代码的情况下,则使用短名称。

例如

  string code = BritishUnits.CubicFeet.CommonCode;  // code == "FTQ"

扩展方法

单位系统还定义了一组这样的扩展方法:

        public static Length Metres(this double v)
        {
                return new Length(v);
        }

这使得可以轻松地从浮点数或整数值创建数量:

        Length l1 = 4.2.Metres();
        Mass m1 = 12.Kilograms();

代码生成

如前所述,由于库中有大量重复代码,我们使用了Visual Studio中提供的T4宏处理器。该工具允许我们通过创建包含C#代码和所需输出文本混合的模板文件来自动化源代码的创建。通常,我们从一个XML定义文件开始,读取它,然后使用模板生成所需的C#类和数据。

例如,这是定义公制单位系统的XML文件中的一行:

    <unit name="Volts" shortname="volt" dimension="ElectricPotential" display="Standard" CommonCode="VLT" />

此模板片段将创建静态单位定义:

<#+ foreach(var ui in unitInfoList)
    {
#>
		public static Unit <# =ui.longName #>= new Unit(System, "<#= ui.longName.ToLower() #>", 
                           "<#= ui.shortName #>", "<#= ui.CommonCode #>",
                        Dimensions.<#= ui.dimension #>, <#= ui.factor #>, 
                        Unit.DisplayOption.<#= ui.displayOption #>);
<#+ }	// end foreach ui
#>

从而在最终代码中生成类似这样的行:

	public static Unit Volts = new Unit
      (System, "Volts", "volt", "VLT", Dimensions.ElectricPotential, 1.0, Unit.DisplayOption.Standard);

这种技术使我们能够为每个数量类生成所需的大量运算符定义。例如,给定Dimensions.xml文件中的以下定义:

    <dimension name="Density" equals="Mass / Volume" />

我们可以生成Density类以及以下所有运算符:

        public static Density operator/(Mass v1, Volume v2)
        public static Volume operator/(Mass v1, Density v2)
        public static Mass operator*(Volume v1, Density v2)

提供了以下XML定义文件:

文件 描述
Dimensions.xml 定义了量纲及其之间的关系。
MetricUnits.xml 公制单位定义。
BritishUnits.xml 英制单位,如英尺和磅。
USAUnits.xml 美制单位。这些与英制单位有些重叠。
TimeUnits.xml 时间单位(除秒外),如小时和天。

汇总表

此表总结了该库支持的类、量纲、公式和单位。

名称 公式 尺寸 单位
AbsoluteTemperature   Θ K (开尔文)
°C (摄氏度)
°F (华氏度)
加速 速度/时间
速度² / 长度
长度 / 时间²
长度 * 角速度²
L T⁻² m/s² (米/秒²)
g0 (重力加速度)
AmountOfSubstance   N mol (摩尔)
nmol (纳摩尔)
AmountOfSubstanceByArea 物质的量 / 面积 L⁻² N m⁻²⋅mol
AmountOfSubstanceByTime 物质的量 / 时间 T⁻¹ N mol⋅s⁻¹
Angle   A rad (弧度)
° (度)
AngularMomentum 转动惯量 * 角速度 M L² T⁻¹ A kg⋅m²/s (千克·米²/秒)
AngularVelocity 角度 / 时间
切向速度 / 长度
T⁻¹ A rad⋅s⁻¹
AngularVelocitySquared 角速度 * 角速度 T⁻² A² rad²⋅s⁻²
Area 长度 * 长度 m² (平方米)
cm² (平方厘米)
ha (公顷)
ByArea 无量纲 / 面积
长度 / 体积
L⁻² m⁻²
ByLength 无量纲 / 长度
面积 / 体积
L⁻¹ m⁻¹
CoefficientOfThermalExpansion 无量纲 / 温度变化 Θ⁻¹ K⁻¹ (每开尔文)
CoefficientOfViscosity 力 / 运动粘度
压强 / 速度梯度
动量 / 面积
面积质量 * 速度
面积质量/时间² / 密度速度
M L⁻¹ T⁻¹ Pa⋅s (帕斯卡秒)
P (泊)
Current   amp (安培)
Density 质量 / 体积 M L⁻³ kg/m³ (千克/立方米)
gm/cc (克/立方厘米)
gm/Ltr (克/升)
mg/cc (毫克/立方厘米)
DiffusionFlux 面积物质的量 / 时间
运动粘度 * 摩尔浓度梯度
时间物质的量 / 面积
L⁻² T⁻¹ N m⁻²⋅mol⋅s⁻¹
Dimensionless     1 (单位)
% (百分比)
doz (打)
hundred (百)
thousand (千)
million (百万)
billion (十亿)
trillion (万亿)
ElectricCharge 电流 * 时间 T I amp⋅s
ElectricPotential 能量 / 电荷 M L² T⁻³ I⁻¹ volt (伏特)
ElectricPotentialSquared 电势 * 电势 M² L⁴ T⁻⁶ I⁻² kg²⋅m⁴⋅amp⁻²⋅s⁻⁶
能量 力 * 长度
质量 * 速度²
角动量 * 角速度²
表面张力 * 面积
M L² T⁻² J (焦耳)
cal (卡路里)
eV (电子伏特)
kWh (千瓦时)
toe (吨油当量)
erg (尔格)
EnergyFlux 功率 / 面积 M T⁻³ kg⋅s⁻³
Force 质量 * 加速度
动量 / 时间
质量流率 * 速度
M L T⁻² N (牛顿)
dyn (达因)
gm⋅wt (克重)
kg⋅wt (千克重)
FourDimensionalVolume 体积 * 长度
面积 * 面积
L⁴ m⁴
频率 无量纲 / 时间
角速度 / 角度
T⁻¹ Hz (赫兹)
Illuminance 光通量 / 面积 L⁻² J A² lux (勒克斯)
KinematicViscosity 面积 / 时间
面积 * 速度梯度
L² T⁻¹ m²/s (平方米/秒)
cm²/s (平方厘米/秒)
长度   L m (米)
km (千米)
cm (厘米)
mm (毫米)
μ (微米)
nm (纳米)
Å (埃)
au (天文单位)
LuminousFlux 发光强度 * 立体角 J A² lm (流明)
LuminousIntensity   J cd (坎德拉)
质量   M kg (千克)
g (克)
mg (毫克)
μg (微克)
ng (纳克)
t (吨)
Da (道尔顿)
MassByArea 质量 / 面积
长度 * 密度
M L⁻² kg⋅m⁻²
MassByAreaByTimeSquared 面积质量 / 时间²
加速度 * 面积
M L⁻² T⁻² kg⋅m⁻²⋅s⁻²
MassByLength 质量 / 长度 M L⁻¹ kg⋅m⁻¹
MassFlowRate 质量 / 时间
粘度系数 * 长度
M T⁻¹ kg/s (千克/秒)
MolarConcentration 物质的量 / 体积
密度 / 摩尔质量
L⁻³ N mol/m3 (摩尔/立方米)
mol/L (摩尔/升)
MolarConcentrationGradient 摩尔浓度 / 长度 L⁻⁴ N m⁻⁴⋅mol
MolarConcentrationTimesAbsoluteTemperature 摩尔浓度 * 绝对温度 L⁻³ Θ N m⁻³⋅K⋅mol
MolarMass 质量 / 物质的量 M N⁻¹ kg/mol (千克/摩尔)
gm/mol (克/摩尔)
MolarSpecificHeat 热容 / 物质的量
压强 / (摩尔浓度 * 绝对温度)
M L² T⁻² Θ⁻¹ N⁻¹ J⋅K⁻¹⋅mol⁻¹ (焦耳/开尔文/摩尔)
MomentOfInertia 质量 * 面积 M L² kg⋅m² (千克·米²)
Momentum 质量 * 速度 M L T⁻¹ kg⋅m/s (千克·米/秒)
能量 / 时间
电势 * 电流
电势² / 电阻
M L² T⁻³ W (瓦特)
kW (千瓦)
PowerGradient 功率 / 长度 M L T⁻³ kg⋅m⋅s⁻³
压力 力 / 面积
面积质量 * 加速度
M L⁻¹ T⁻² Pa (帕斯卡)
mmHg (毫米汞柱)
dyn/cm² (达因/平方厘米)
Resistance 电势 / 电流 M L² T⁻³ I⁻² Ω (欧姆)
ResistanceTimesArea 电阻 * 面积 M L⁴ T⁻³ I⁻² kg⋅m⁴⋅amp⁻²⋅s⁻³
ResistanceToFlow 质量流率 / 四维体积 M L⁻⁴ T⁻¹ kg⋅m⁻⁴⋅s⁻¹
Resistivity 电阻 * 长度
电阻面积 / 长度
M L³ T⁻³ I⁻² Ω⋅m (欧姆·米)
SolidAngle 角度 * 角度 sr (球面度)
°² (平方度)
SpecificHeat 热容 / 质量 L² T⁻² Θ⁻¹ J⋅kg⁻¹⋅K⁻¹ (焦耳/千克/开尔文)
SurfaceTension 力 / 长度
长度 * 压强
M T⁻² N/m (牛顿/米)
dyne/cm (达因/厘米)
TangentialVelocity 速度 L T⁻¹ m/s (米/秒)
cm/s (厘米/秒)
kph (千米/小时)
TemperatureChange   Θ K (开尔文)
°C (摄氏度)
°F (华氏度)
TemperatureGradient 温度变化 / 长度 L⁻¹ Θ m⁻¹⋅K
ThermalCapacity 能量 / 温度变化 M L² T⁻² Θ⁻¹ J/K (焦耳/开尔文)
cal/K (卡路里/开尔文)
ThermalCapacityByVolume 热容 / 体积
摩尔浓度 * 摩尔比热容
压强 / 绝对温度
M L⁻¹ T⁻² Θ⁻¹ kg⋅m⁻¹⋅K⁻¹⋅s⁻²
ThermalConductivity 能量通量 / 温度梯度
功率梯度 / 温度变化
M L T⁻³ Θ⁻¹ W⋅m⁻¹⋅K⁻¹ (瓦特/米/开尔文)
时间   T s (秒)
ms (毫秒)
min (分钟)
hr (小时)
day (天)
week (周)
month (月)
yr (儒略年)
aₛ (恒星年)
TimeSquared 时间 * 时间
速度 长度 / 时间 L T⁻¹ m/s (米/秒)
cm/s (厘米/秒)
kph (千米/小时)
VelocityByDensity 速度 / 密度 M⁻¹ L⁴ T⁻¹ kg⁻¹⋅m⁴⋅s⁻¹
VelocityGradient 速度 / 长度 T⁻¹ Hz (赫兹)
VelocitySquared 速度 * 速度 L² T⁻² m²⋅s⁻²
面积 * 长度 m³ (立方米)
cc (立方厘米)
L (升)
VolumeFlowRate 体积 / 时间
压强 / 阻力
L³ T⁻¹ m³/s (立方米/秒)
cc/s (立方厘米/秒)

更多示例

这里有更多使用该库的例子,基于《Nelkon and Parker》中的问题。

鲁莽的跳跃者

        // A person of mass 50 kg who is jumping from a height of 5 metres 
        // will land on the ground
        // with a velocity = √2gh = √ 2 x 9.8 x 5 = 9.9 m/s , assuming g = 9.8 m/s.
    	Mass m = 50.Kilograms();
        Length h = 5.Metres();
        Acceleration g = 9.80665.MetresPerSecondSquared();
        Velocity v = Functions.Sqrt(2 * g * h); // v = 9.90285312 m/s
        // If he does not flex his knees on landing, 
        // he will be brought to rest very quickly, say in
        // 1/10th second.  The force F acting is then given 
        // by momentum change/time = 50 * 9.9 / 0.1 = 4951 N
        Momentum mm = m * v;
        Time t = 0.1.Seconds();
        Force f = mm / t; // f = 4951.42656 N

以及飞行的板球

        // Suppose a cricket ball was thrown straight up with an initial velocity, 
        // u, of 30 m/s.
        // The time taken to reach the top of its motion can be obtained from the equation
        // v = u + at.
        // The velocity, v, at the top is zero; and since u = 30 m and 
        // a = —9.8 or 10 m/s²(approx),
        // we have 30 - 10t = 0.
        // Therefore t = 30 / 10 = 3s
        // The highest distance reached is thus given by
        // d = ut + 1 / 2 at ^ 2 = 30x3 - 5x3 ^ 2 = 45 m.
        var u = 30.MetresPerSecond();
        var g = 9.80665.MetresPerSecondSquared();
        var t = u / g;  // t = 3.05914864 s
        var d = u * t + -g * t * t / 2.0;   // d = 45.8872296 m

表面张力

        // Calculate the work done against surface tension in blowing a bubble of 1 cm in diamter
        // if surface tension of a soap solution = 25 dynes/cm.
        Length r = 1.Centimetres() / 2;
        SurfaceTension surfaceTensionOfSoapSolution = 25.DynesPerCentimetre();
        // The initial surface area is zero
        // The final surface area = 2 x 4π x 0.5² = 2π sq cm.
        Area a = new Sphere(r).Area * 2;
        // Therefor work done = T x increase in surface area = 25 x 2π = 157 ergs.
        Energy e = surfaceTensionOfSoapSolution * a; // 157.1 erg

杨氏模量

        // If a 2kg weight is attached to the end of a wire of length 200cm and diameter 0.64mm 
        // and the extension is 0.6mm then what is the Young's Modulus E of the wire?
        Force f = 2.KilogramWeight();
        Area a = Circle.OfDiameter(0.64.Millimetres()).Area;
        var stress = f / a;
        Length l = 200.Centimetres();
        Length e = 0.6.Millimetres();
        var strain = e / l;
        // E = (2000 x 980 / π x 0.032²) / (0.06/200) = 2e12 dynes/cm²
        var E = stress / strain;	// 2.032E+12 dyn/cm²

菲克第一定律

	// In a region of an unsaturated solution of sucrose the molar concentration gradient is -0.1 mol/L/cm.
	// What quantity of sucrose molecules pass through an area of 1cm² in 10 minutes?
	MolarConcentration c = 0.1.Mole() / 1.Litres();
	MolarConcentrationGradient cg = -c / 1.Centimetres();
	Area a = 1.SquareCentimetres();
	Time t = 10.Minutes();
	AreaFlowRate d = 0.522e-9.SquareMetresPerSecond();    // diffusion coefficient
	DiffusionFlux j = d * cg;
	AmountOfSubstance n = j * a * t;	// -313.2 nmol

关注点

单元测试

示例程序还测试了该库,但没有使用单元测试框架。相反,它使用了一个简单的静态类Check,允许我们编写如下代码:

       Check.Equal(42.0, d5, "wrong value for d5");

如果前两个参数不相等,这将引发一个异常。

性能

我曾希望通过创建不可变数据类型并大量使用激进的内联和优化提示,使数量类的性能与“原始”双精度浮点数的性能相当。但事实并非如此。为了测试这一点,我实现了两次相同的火箭模拟,一次使用纯双精度浮点数,另一次使用数量类。在发布版本中,使用double的版本速度快约6倍。原因可以通过检查一些典型算术的代码生成来观察。例如,这段代码:

        double d1 = 4.2;
        double d2 = 5.3;
        double d3 = 6.4;
        double d4 = d1 + d2 + d3;

生成如下的加法代码:

00007FFCCC4B6A46  vmovsd      xmm3,qword ptr [rbp-8]  
00007FFCCC4B6A4B  vaddsd      xmm3,xmm3,mmword ptr 
                              [UnitTests.Program.TestDouble()+0B0h (07FFCCC4B6AC0h)]  
00007FFCCC4B6A53  vaddsd      xmm3,xmm3,mmword ptr [rbp-10h]  
00007FFCCC4B6A58  vmovsd      qword ptr [rbp-18h],xmm3  

而使用该库的相同公式:

        Dimensionless d1 = 4.2;
        Dimensionless d2 = 5.3;
        Dimensionless d3 = 6.4;
        Dimensionless d4 = d1 + d2 + d3;

生成了更长的代码:

00007FFCD5726B59  mov         rcx,qword ptr [rsp+70h]  
00007FFCD5726B5E  mov         qword ptr [rsp+58h],rcx  
00007FFCD5726B63  mov         rcx,qword ptr [rsp+68h]  
00007FFCD5726B68  mov         qword ptr [rsp+50h],rcx  
00007FFCD5726B6D  vmovsd      xmm0,qword ptr [rsp+58h]  
00007FFCD5726B73  vaddsd      xmm0,xmm0,mmword ptr [rsp+50h]  
00007FFCD5726B79  vmovsd      qword ptr [rsp+48h],xmm0  
00007FFCD5726B7F  mov         rcx,qword ptr [rsp+48h]  
00007FFCD5726B84  mov         qword ptr [rsp+40h],rcx  
00007FFCD5726B89  mov         rcx,qword ptr [rsp+60h]  
00007FFCD5726B8E  mov         qword ptr [rsp+38h],rcx  
00007FFCD5726B93  vmovsd      xmm0,qword ptr [rsp+40h]  
00007FFCD5726B99  vaddsd      xmm0,xmm0,mmword ptr [rsp+38h]  
00007FFCD5726B9F  vmovsd      qword ptr [rsp+30h],xmm0  
00007FFCD5726BA5  mov         rcx,qword ptr [rsp+30h]  
00007FFCD5726BAA  mov         qword ptr [rsp+78h],rcx  

有大量的多余的移动指令。也许对JIT编译器有更深入了解的人可以对此进行阐述。

与F#的比较

F#语言内置了对度量单位的支持,其目标也是防止编程错误。因此,可以编写如下语句:

    let l1 = 12.0<m>	    // define a length in metres
    let l2 = 7.0<m>	        // define another length
    let l3 = l1 + l2        // add lengths together
    let a = l1 * l2         // define an area (a has type float<m^2>)
    let v = l1 * l2 * l3    // define a volume (v has type float<m^3>)
    let m1 = 5.0<kg>	    // define a mass in kilogrammes
    let d = m1 / v;         // define a density (d has type float<kg/m^3>)

并且在上述情况下,此语句将不会编译:

    let x = m1 + l1; // !! The unit of measure 'm' does not match the unit of measure 'kg'

标准单位库定义了基本的SI单位(如米),但没有定义派生单位(如厘米)。您可以这样定义自己的单位:

[<Measure>] type cm     // centimetres

并以同样的方式使用它:

    let l4 = 42.0<cm>

但是,无法指示厘米和米是相同的量纲。因此,上面l1的类型是float<m>,而l4的类型是float<cm>,尝试将它们相加不会编译:

    let l5 = l1 + l4;    // !! The unit of measure 'cm' does not match the unit of measure 'm'

您只能通过定义一个转换函数来解决这个问题:

let convertcm2m (x : float<cm>) = x / 1000.0<cm/m>

然后在表达式中使用它:

    let l5 = l1 + convertcm2m(l4);

在使用度量单位时,您还必须小心使用相同的数值类型。这是因为在此定义中:

    let l6 = 5<m>

l6的类型是int<m>,而这不能添加到类型为float<m>的值中。因此,这一行也不会编译:

    let l7 = l1 + l6;           // !! The type float<m> does not match the type int<m>

最后,尽管度量单位在编译时被检查,但类型不会传递到编译后的代码中。值仅定义为浮点数。因此,您无法在运行时发现值的度量单位是什么。所以,您只能像这样打印这些类型的值:

    printfn "l5 = %e" l5        // outputs "l5 = 1.204200e+001" 

即使您使用格式说明符%O

    printfn "l5 = %O" l5        // outputs "l5 = 12.042" 

因此,尽管F#系统具有相同的目标(防止无效的数学运算),但由于其基于单位而不是量纲,因此它更加受限。

历史

  • 2021年7月6日:初始版本
  • 2021年8月6日:向库添加了AbsoluteTemperature。在文章中添加了一个表格,总结了库的内容。
  • 2021年8月8日:更正了汇总表的格式。
  • 2021年12月7日:添加了来自热学、热膨胀、导热和理想气体的方程示例。
  • 2022年5月9日:添加了形状、向量、静力学、流体静力学、表面张力、弹性学和摩擦力。
  • 2022年9月1日:添加了粘度、渗透和扩散。还更新了汇总表。
  • 2023年3月12日:添加了通用代码、一些光学和几何光学方程,并纠正了错误。

我在业余时间为此工作了两年多。目前,该库已具备基础,可用于动力学、静力学、热学、光学和部分电学方程。我将继续添加更多派生量纲和数量类,以支持我在Nelkon and Parker书中遇到的更多方程。

© . All rights reserved.