2次发布
0.1.1 | 2019年6月12日 |
---|---|
0.1.0 | 2019年6月10日 |
#257 in 渲染
用于 fts_gamemath
72KB
1K SLoC
fts_units
fts_units是一个Rust库,它通过使用单位使编译时类型安全的数学运算成为可能。
它提供了一系列引人注目的功能。
国际单位制
对SI单位提供强大的支持。可以创建自定义系统,但目前不是开发重点。
基本数学
use fts_units::si_system::quantities::f32::*;
let d = Meters::new(10.0); // units are meters
let t = Seconds::new(2.0); // units are seconds
let v = d / t; // units are m·s⁻¹ (MetersPerSecond)
// compile error! meters plus time doesn't make sense
let _ = d + t; // compile errors! meters + time doesn't make sense
// compile error! can't mix different ratios (Unit and Kilo)
let _ = d + Kilometers::new(2.3);
组合运算
基本数学运算可以任意组合。有效的类型不是预定义的。如果你的计算结果有14次方的米,它仍然可以正常工作。
use fts_units::si_system::quantities::*;
fn calc_ballistic_range(speed: MetersPerSecond<f32>, gravity: MetersPerSecond2<f32>, initial_height: Meters<f32>)
-> Meters<f32>
{
let d2r = 0.01745329252;
let angle : f32 = 45.0 * d2r;
let cos = Dimensionless::<f32>::new(angle.cos());
let sin = Dimensionless::<f32>::new(angle.sin());
let range = (speed*cos/gravity) * (speed*sin + (speed*speed*sin*sin + Dimensionless::<f32>::new(2.0)*gravity*initial_height).sqrt());
range
}
类型控制
fts_units对存储类型提供了完全控制。
let s = Seconds::<f32>::new(22.3);
let ns = Nanoseconds::<i64>::new(237_586_538);
如果你主要使用f32值,则便利模块会包装所有常见类型。
use fts_units::si_system::quantities::f32::*;
转换
可以转换相同维度的量。
let d = Kilometers::new(15.3);
let t = Hours::new(2.7);
let kph = d / t; // KilometersPerHour
let mps : MetersPerSecond = kph.convert_into();
let mps = MetersPerSecond::convert_from(kph);
尝试将不同维度的量转换会产生编译时错误。
let d = Meters::<f32>::new(5.5);
let _ : Seconds<f32> = d.convert_into(); // compile error!
let _ : Meters<f64> = d.convert_into(); // also compile error!
转换
可以按照正常转换规则将数量转换为。这个特性使用了num-traits。
let m = Meters::<f32>::new(7.73);
let i : Meters<i32> = m.cast_into();
assert_eq!(i.amount(), 7);
永远不会隐式执行转换或转换。这确保了在处理不同尺度时完全控制。例如,在纳秒和年之间转换。
显示
SI系统支持人类可读的显示输出。
println!("{}", MetersPerSecond2::<f32>::new(9.8));
// 9.8 m·s⁻²
println!("{}", KilometersPerHour::<f32>::new(65.5));
// 65.5 km·h⁻¹
任意比率
si_system
数量可以具有完全任意的比率。
type R = RatioT<P37,P10>;
let q : QuantityT<f32, SIUnitsT<SIRatiosT<R, Zero, Zero>, SIExponentsT<P1, Z0, Z0>>> = 1.1.into();
没有宏或构建脚本
此crate是纯Rust代码。没有宏或构建脚本来自动生成任何内容。
这是为了使源代码易于阅读、理解和扩展而做出的明确选择。
自定义金额
struct QuantityT<T,U>
适用于任何T
,其中T:Amount
。
在以下内置类型中实现了 Amount
:u8
、u16
、u32
、u64
、u128,
i8,
i16,
i32,
i64,
i128,
f32,和
f64。
Amount
也可以为任何自定义类型实现。例如 Vector3<f32>
。 QuantityT<Vector3<f32>, _>
将正确支持或不支持您想要的运算符。如果 Vector3<f32>
实现 std::ops::Add<Vector3f<f32>>
但不实现 std::ops::Mul<Vector3<f32>>
,则 QuantityT<Vector3<f32>, _>
的行为也将相同。
实现
fts_units 完全是编译时实现,没有运行时成本。单位存储为零大小类型,编译后会消失。
如果您使用的是提供的SI系统,那么这些实际上并不重要。您永远不需要输入这些类型。但是,如果您犯了一个错误并生成了编译错误,了解底层类型将帮助您理解错误的来源。
了解实现的最佳方式是快速浏览几个重要的结构体。对于大多数结构体,都有一个匹配的特质。我选择使用 T 后缀来表示结构体。例如,Quantity(特质)和 QuantityT(结构体)。还有 Ratio(特质)和 RatiotT(结构体)。T 表示结构体必须提供一个类型。
QuantityT 是基本的结构体。米、秒和米/秒都是具有不同 U 类型的 QuantityT 结构体。
pub struct QuantityT<T:Amount, U> {
amount : T,
_u: PhantomData<U>
}
RatioT 是一个将分子和分母以类型形式存储的结构体。千米的比率为 1000 / 1。纳米的比率为 1 / 1_000_000_000。
pub struct RatioT<NUM, DEN>
where
NUM: Integer + NonZero,
DEN: Integer + NonZero,
{
_num: PhantomData<NUM>,
_den: PhantomData<DEN>,
}
这里会变得有些复杂。具有 SI 单位的量有一个比率和指数列表。
pub struct SIUnitsT<RATIOS,EXPONENTS>
where
RATIOS: SIRatios,
EXPONENTS: SIExponents
{
_r: PhantomData<RATIOS>,
_e: PhantomData<EXPONENTS>,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct SIRatiosT<L,M,T>
where
L: Ratio,
M: Ratio,
T: Ratio
{
_l: PhantomData<L>,
_m: PhantomData<M>,
_t: PhantomData<T>
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct SIExponentsT<L,M,T>
where
L: Integer,
M: Integer,
T: Integer,
{
_l: PhantomData<L>,
_m: PhantomData<M>,
_t: PhantomData<T>
}
以下是完全展开的一些示例类型。
// Ratios and exponents are stored in Length/Mass/Time order
type Kilometers = QuantityT<f32,
SIUnitsT<
SIRatiosT<Kilo, Zero, Zero>,
SIExponentsT<P1, Z0, Z0>>>;
type CentimetersPerSecondSquared = QuantityT<f64,
SIUnitsT<
SIRatiosT<Centi, Zero, Unit>,
SIExponentsT<P1, Z0, N2>>>;
只要单位类型支持该操作,就支持 Quantity 的加、乘、除和平方根等操作。
当与si_system一起工作时,这意味着我们正在使用 QuantityT<T,SIUnitsT<R,E>>
。所有操作都需要匹配的 T
类型。加法和减法在 SIUnitsT<R,E>
中实现,乘法和除法在 R
类型不冲突时实现。如果 T 支持开方且所有 E 值都是偶数,则实现开方。
您可以通过使用 CastAmount
特征来更改 T
。您可以通过使用 ConvertUnits
来更改 U
。
注意事项
国际单位制
国际单位制目前仅支持长度、质量和时间维度。这几乎是大多数游戏所需要的。
电流、温度、物质的量和光强度将稍后添加。这是一项微不足道的工作,但需要一定数量的复制/粘贴。这些维度将在基本 API 稳定后添加。
错误信息不佳
fts_units 利用出色的 typenum crate 进行编译时数学。不幸的是,这导致 糟糕的 错误信息。
当 const generics 到来时,这将大幅改进。
此代码
let _ = Meters::new(5.0) + Seconds::new(2.0);
产生此错误
error[E0308]: mismatched types
--> examples\sandbox.rs:82:32
|
82 | let _ = Meters::new(5.0) + Seconds::new(2.0);
| ^^^^^^^^^^^^^^^^^ expected struct `fts_units::ratio::RatioT`, found struct `fts_units::ratio::RatioZero`
|
= note: expected type `fts_units::quantity::QuantityT<_, fts_units::si_system::SIUnitsT<fts_units::si_system::SIRatiosT<fts_units::ratio::RatioT<typenum::int::PInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>>, typenum::int::PInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>>>, _, fts_units::ratio::RatioZero>, fts_units::si_system::SIExponentsT<typenum::int::PInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>>, _, typenum::int::Z0>>>`
found type `fts_units::quantity::QuantityT<_, fts_units::si_system::SIUnitsT<fts_units::si_system::SIRatiosT<fts_units::ratio::RatioZero, _, fts_units::ratio::RatioT<typenum::int::PInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>>, typenum::int::PInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>>>>, fts_units::si_system::SIExponentsT<typenum::int::Z0, _, typenum::int::PInt<typenum::uint::UInt<typenum::uint::UTerm, typenum::bit::B1>>>>>`
这是一个视觉噩梦。但它可以理解!
当排成一行或放入差异工具中时,差异很容易发现。'找到的类型' 在第一个 SIUnitsT 插槽中有一个 RatioZero 类型,而期望的是非零类型。如果您记得插槽是长度/质量/时间,这应该是有意义的。米值有一个非零长度比率。秒值有一个零长度比率。要添加两个 SIUnitsT 量,它们必须具有完全相同的比率和指数。
数量级
支持飞托到拍。不幸的是,Atto/Zepto/Yocto 和 Exa/Zetta/Yotta 不受支持。它们需要 128 位比率,而 fts_units 目前由于 typenum 而受到 64 位的限制。
当 const generics 到来时,这应该会改变。
导出单位
国际单位制的一个优点是导出单位。每个人都知道 Force = Mass * Acceleration
。力是一个如此常见的量,它有一个名字,牛顿。牛顿被存储在 kg⋅m⋅s⁻²
中。这也允许像千牛顿的力或太瓦的功率这样的单位。
遗憾的是,fts_units 不支持导出单位。当 特殊化 到来时,将更容易很好地支持。
常见问题解答
fts是什么意思?
这是我的首字母。
你为什么做这个?
因为我一直想要它存在。
为什么使用fts_units?
为什么有人应该使用 fts_units 而不是 uom 或 dimensioned?
这是一个好问题。您可能会更喜欢其中的一个 crate!我认为 fts_units 有更好的 API。我喜欢有明确的控制权进行转换和转换。我喜欢它不使用宏,因此代码易于阅读和理解。
这正是我想要的。
许可证:Unlicense 或 MIT
依赖关系
~215–305KB