Physical 是一个 Swift 编程语言的度量单位系统,构建在 ( 的) Foundation 框架之上。
该项目旨在利用量纲分析和单位/测量研究的进展,使编写代码更适合琐碎和复杂的现实世界计算。即使您认为您没有在代码中进行此类计算,您可能也在进行。它可以帮助您维护可读、可调试的代码。
使用此软件包的真实世界代码越多,我们就越能优化整个系统。请提交请求,最好是包含真实(或期望的)代码,提交拉取请求,并提出可以改进的领域。
有关该项目的最新概述,请参阅在 360iDev 2022 上关于该框架的演讲。
Physical 在 MIT 许可下发布。
如果使用 Xcode,请打开一个工作区或项目,然后:File → Add Packages... → 放入此项目的 URL。
如果手动编辑您的 Package.swift
,请添加以下依赖信息
let package = Package(
...
targets: [
.target(name: "YourAwesomeProject", dependencies: ["Physical"]),
],
dependencies: [
.package(url: "https://github.com/hyperjeff/Physical.git", .branch("main")),
],
...
)
设置好以上任何一个后,只需在您想要的任何文件顶部 import Physical
即可。
Physical 扩展了 Measurement 框架的维度集支持的单位
Acceleration, Angle, Area, ConcentrationMass,
Dispersion, Duration, ElectricCharge, ElectricCurrent,
ElectricPotentialDifference, ElectricResistance, Energy,
Frequency, FuelEfficiency, InformationStorage, Length,
Mass, Power, Pressure, Speed, Temperature, Volume
并添加了这些维度
Activity, Amount, AngularSpeed, ElectricCapacitance,
ElectricConductance, ElectricInductance, Force,
IonizingRadiation, LuminousIntensity, LuminousFlux,
MagneticFlux, MagneticFluxDensity, SolidAngle
此外,整个系统都是可定制和可扩展的。
单位的简单创建和组合。然后可以将结果转换为更合适的单位。
import Physical
var distance1 = 10.5.centimeters
let distance2 = 3.3.feet
distance1 + distance2
distance1 += 14.furlongs
distance1 < 3e-5.miles
let speed = 42.0.kilometersPerHour
distance2 / speed
(distance2 / speed).to(.hours)
distance2 / speed → .milliseconds
注意: 这是一个真正的 Unicode 箭头字符 → (U+2192),而不是 ->。这完全没有必要。如果您不想使用它,或者您使用的键盘布局不包含它,那么您可以使用 .to()
函数代替。它就在那里,如果你需要它。
要创建具有任何维度的 Physical 对象,只需链接单位,可能带有指数。 此外,可以通过下面的 /
强制单位位于分母中。 每个单位也有一个“per”变体。
let aForce = 1.kilograms.meters.seconds(-2)
aForce → .newtons
6.meters.seconds(-1)
6.meters/.seconds
6.meters.perSecond
12.metersPerSecondSquared
12.meters/.seconds/.seconds
12.m/.s(2)
12.meters.perSecond.perSecond
有几种(希望数量合适)编写复杂单位集的方法,可以选择适合情况和可读性要求的方法。
var density = 27.grams.centimeters(-3)
density = 27.grams.cubicCentimeters(-1)
density = 27.g.cm(-3)
density = 27.g/.cm(3)
density = 27.grams.perCubicCentimeter
density → .milligramsPerDeciliter
density → .gramsPerLiter
可以通过标准代数组成新型的 Physical 对象。
如果项目的组合是不可能的,则结果是 Physical.notAThing
,类似于浮点数发生不可能的数学结果时的 NaN
。 在更多方程中进一步使用此对象会反过来影响这些结果。 您可以使用 .isNotAThing
检查其状态,就像浮点数具有 .isNaN
一样。 要找出变量为什么变得不是一个东西,您可以检查它的 .errorStack
,其中包含自它第一次变成不是一个东西以来的不良历史记录。
~
运算符可用于测试可通约性。 x ~ y
表示:x
与 y
是否具有相同的维度。
let force = 4.5.newtons // 4.5 N
let mass = 17.poundsMass // 17 lb
force + mass // Not a Thing
(force + mass) / 7.feet // Not a Thing
(force + mass).isNotAThing // true
(force + mass).errorStack // ["4.5 N + 17 lb"]
force.dimensionalDescription // L¹ M¹ T⁻²
mass.dimensionalDescription // M¹
let acceleration = force / mass // 0.58358 m / s²
acceleration → .gravity // 0.059488 g
acceleration ~ 1.gravity // true
1.gravity.withBasicUnits // 9.81 m / s²
force.withBasicUnits // 4.5 kg m / s²
mass * acceleration // 9.9208 lb m / s²
mass * acceleration → .newtons // 4.5 N
mass * acceleration → .joules // Not a Thing
mass * acceleration * 37.feet → .joules // 50.749 J
与您可能认为的相反,三角函数使用单位。 Physical 提供三角函数、指数函数和反三角函数(普通和双曲),它们既可以消除一整类错误(“我是否应该乘以 π 并除以 180?”),也可以极大地改进您的代码和推理。 这些函数是对标准三角函数的补充,不会与其冲突。
三角函数将角度映射到一个实数。 反函数将实数映射回角度。
这些函数不会干扰现有的旧式三角函数,也不会与任何现有算法相矛盾。 这纯粹是可选的。
75° // 75 °
sin(75°) // 0.96593
asin(sin(75°)) // 1.309 rad
75° → .radians // 1.309 rad
asin(sin(75°)) → .degrees // 75 °
let θ₁ = (2.π/5).radians // 1.2566 rad
let θ₂ = θ₁ → .degrees // 72 °
let θ₃ = θ₁ → .revolutions // 0.2 rev
sin(θ₁) // 0.95106
sin(θ₂) // 0.95106
sin(θ₃) // 0.95106
注意: 度符号是 °
(在美式标准键盘上是 ⇧⌥8
),而不是 º
(⌥0
)。
由于有几个尺度(摄氏度和华氏度)没有共同的零温度参考,因此必须特别考虑温度代数。 值得庆幸的是,它们至少都是线性相关的。 但也不止一种基于零的比例,有两种:开尔文和兰金,都支持,尽管开尔文是 SI 标准参考。
可以很容易地在它们之间进行转换,但各种代数运算会阻止方程具有唯一的含义,并且这些总和将变为 notAThing
。
2.kelvin → .celsius // -271.2.celsius
25.celsius → .fahrenheit // 77 °F
2 * 100.kelvin // 200 K
2 * 100.celsius // Not a Thing
500.kelvin ^ 4 // 6.25e10 K⁴
500.fahrenheit ^ 4 // Not a Thing
30.fahrenheit < 30.celsius // true
温差被视为不同种类的量,这扩大了它们的有意义的使用。 可以将温度显式指定为差值,也可以通过减法隐式指定。
50.celsius - 20.celsius // 30.celsius
50.celsius - 20.celsius == 30.kelvin // false
50.celsius - 20.celsius == 30.kelvin.difference // true
50.celsius - 20.celsius == 54.fahrenheit.difference // true
50.fahrenheit + 30.celsius // Not a Thing
50.fahrenheit + 30.celsius.difference // 104 °F
let a = [68.12, 72.22, 120.5].celsius
let b = [30.31, 71.81, 90.33].celsius
a - b → .fahrenheit // [68.06, 0.738, 54.31] ∆°F
支持分贝,并且始终相对于参考值设置。 该系统支持许多标准 dB 测量,并允许您使用任意参考值。 (没有“纯”dB 值。)分贝不被认为是无单位的,因为它表示相对于参考值的物理测量。 可以随意来回转换,但不能在解包值没有意义的上下文中使用它们。
功率与非功率比率的 2 倍因子会自动跟踪。 (如果您使用自定义的基于非功率的比率,这些比率将使用标准因子 10 log₁₀(ratio) 进行计算。)
一些常用的标准措施
1.pascals.dBSPL // 93.98 dB SPL
30.watts.dBm // 44.77 dBm
30.watts.dBW // 14.77 dBW
可以按如下方式使用自定义参考值。(自定义 dB 测量的参考值显示在括号中。)
let pressure = 43e-3.pascals
let dBPressure = pressure.dB(reference: 4e-3.newtons.perSquareMeter) // 20.63 dB (0.004 N/m²)
let energy = 10.watts
let dBEnergy = energy.dB(reference: 37.horsepower) // -34.41 dB (37 hp)
可以查询与 dB 测量以及参考值对应的 Physical 值。
12.dBm.dBReference // 1 mW
12.dBm.dereferencedValue // 3.981 mW
12.dBK.dBReference // 1 K
12.dBK.dereferencedValue // 3.981 K
8.dBm.ratio // 6.3096
8.dBSPL.ratio // 2.5119
8.dBm ~ Power.self // true
14.dBSPL ~ Pressure.self // true
分贝代数像普通代数一样工作,并且可以与物理测量混合,从而在现实世界场景中快速发挥作用。
0.7.dBW - 21.dBW + 13.dBW // -7.3 dBW
10.watts + 0.7.dBW - 21.dBW + 13.dBW // 1.862 W
1.W + 10.mW.dB(reference: 1.mW) // 10 W
1.W + 10.mW.dB(reference: 2.mW) // 5 W
10.dB(reference: 1.mW) + 10.dB(reference: 2.mW) // 23.01 dBm
√(24.dBm * 600.ohms) → .volts // 12.28 V
12.dBSWL + 4.dBK // Not a Thing
关于这个话题还有很多要说。
当前问题: 负数 -
运算存在问题,需要将分贝的数字放在括号中。 现在请注意包含它们,否则您会得到错误但可用的数字。 示例:(-161).dBK
。
Physical 可以一次描述整个数组,还可以免费加速对它们进行的计算。 此外,还包括一个 ramp
函数,类似于 Numpy 的 linspace
函数。 可以将数组视为 n 维向量,并支持旋转(目前)2-d 数组。
let fileSizes = [1, 3, 14, -2].gigabytes // [1, 3, 14, -2] GB
let dataRate = 1.megabits.perSecond // 1 Mb / s
(fileSizes / dataRate → .hours) // [2.2222, 6.6667, 31.111, -4.4444] hr
(0.s...50.s).by(20.μs) // [0, 0.00002, 0.00004, ...] seconds
ramp(in: 0...1.pi, count: 27) // [0, 0.1208304866765305, ...]
ramp(in: 0...100, by: 3) // [0, 3, 6, ..., 99]
let angles = ramp(in: 0...1.pi, count: 27).radians.sigfigs(3)
4.meters.repeated(50) // [4, 4, ... ] meters
angles[7] // 0.84581 rad
sin(angles)[7] // 0.74851
var position = [0, 1].meters
var velocity = [0, 1].rotated(32°) * 110.milesPerHour
指数是一种特殊的 TieredNumber
类型,它交替地是整数、有理数或浮点值,并将根据需要优雅地降级。 这允许方程恢复整数或有理数指数,从而提供更好的单位匹配和结果准确性。
let x = 4.76.meters
x^5 // 2,443.6 m⁵
(x^5) ^ 0.2 // 4.76 m
(x^5) ^ 0.2 → .yards // 5.2056 yd
let y = x ^ (3.0/7) // 1.9517 m^(3/7)
y^7 // 107.85 m³
y^7 → .cubicInches // 6.5814e6 in³
x^π // 134.51 m^3.141592653589793
(x^π) ^ (1/π) // 4.76 m^1.0
(x^π) ^ (1/π) → .yards // Not a Thing
(请注意,在最后一个示例中,Double 指数值意味着 m^1.0 不完全精确,因此与长度不相称。)
可以选择(双关语!)使用强类型。 如果用强类型包装 Physical 对象,结果是可选的。 这类似于 Swift 标准库中 Int(...)
和 Double(...)
等如何成为可选的。
可以使用两个参数的 init 来创建文字强类型,Length(value, unit: .meters)
(等等),这保证了单位的正确性,并且不会产生可选类型。 如果错误地选择了类型,则会导致编译时错误。
要将强类型对象与动态类型的 Physical 对象一起使用,只需提取 physical
内容即可。 (这里还有改进的空间。)
Length(45.feet) // optionals, akin to Int("test")
Length(45.hectares) // nil
Length(45, unit: .hectares) // Compile-time error!
let sailHeight = Length(45, unit: .feet) // guaranteed type-correct
sailHeight.physical // retrieve dynamic Physical type
func orbitalRadiusOfChargeInMagneticField(
mass: Mass,
velocity: Speed,
charge: ElectricCharge,
magneticFluxDensity: MagneticFluxDensity) -> Length? {
Length(mass * velocity / (charge * magneticFluxDensity))
}
Physical 具有丰富的常量集,包括其单位和当前已知的精度。
let G = Physical.Constants.gravitation
let sunMass = Physical.Constants.Astronomic.Sun.mass
let earthMass = Physical.Constants.Earth.mass
let earthRadius = Physical.Constants.Earth.meanRadius
func orbitHeight(period: Duration) -> Length {
return Length( ∛(G * earthMass * (period^2) / 4.π²) - earthRadius )!
}
let heightISS = orbitHeight(period: Duration(92, unit: .minutes)).physical
heightISS → .miles
func orbitalPeriod(height: Length) -> Duration {
Duration( √(4.π² * ((heightISS + earthRadius)^3) / (G * earthMass)) )!
}
let periodISS = orbitalPeriod(height: Length(254, unit: .miles)).physical
periodISS → .minutes
包含了一些对浮点数的增强功能。
6.pi // sugar
2.π // greek letter π
3.e // 3 * natural number
14.0 ^ 2 // instead of pow(14.0, 2)
√14
√(x + y) // longer expressions need parentheses
∛14 // silly but available
∜14
8√14 // 8th root of 14. can be any integer.
n√14
47% // 0.47
distance *= 2.1%
注意: 大多数键盘布局上都可以键入 π 和 √。 (在美国 Qwerty 布局上,它们是 ⌥p
和 ⌥v
。)
注意: 对于那些受限于具有 20 世纪限制和 Asciitarians 的项目的人,非 ASCII 字符都有 ASCII 等效字符。