本文用于记录探索带中文单位算式计算的预研,学习mathjs的设计实现。
功能介绍
mathjs中单位模块提供了带(英文)单位的运算、对带单位值进行(英文)单位转换等功能。
// 基本使用
const math = require('mathjs');
// 单位转换
const unitValue1 = math.unit('24 hours');
console.log(unitValue1.toNumber('minutes')); // ===> 1440
// 带单位计算
const unitValue2 = math.unit(2, 'km/s');
console.log(math.multiply(unitValue1, unitValue2).toNumber('m')); // ===> 172800000
// 自定义单位
math.createUnit('ly', math.multiply(math.evaluate('speedOfLight'), math.unit('1 year'))); // 光年
console.log(math.unit('1 ly').toNumber('km')); // ===> 9460730472580.8
// 判断字符串是否为单位
console.log(math.Unit.isValuelessUnit('ly')); // ===> true
console.log(math.Unit.isValuelessUnit('hello')); // ===> false
// 判断是否同类型单位
const unitValue3 = math.unit('1 mile/year');
console.log(unitValue2.equalBase(unitValue3)); // ===> true
const unitValue4 = math.multiply(math.unit('1 kW'), math.unit('1 hour')); // 千瓦时
const unitValue5 = math.multiply(math.unit('1 N'), math.unit('1 m')); // 焦耳
console.log(unitValue4.equalBase(unitValue5)); // ===> true
console.log(unitValue2.equalBase(unitValue5)); // ===> false
单位的格式
mathjs中的Unit类主要分为数值与单位两部分 mathjs对Unit类的输入数据有如下要求:
// A unit should follow this pattern:
// [number] ...[ [/] unit[^number] ]
// unit[^number] ... [ [/] unit[^number] ]
// Rules:
// number is any floating point number.
// unit is any alphanumeric string beginning with an alpha. Units with names like e3 should be avoided because they look like the exponent of a floating point number!
// The string may optionally begin with a number.
// Each unit may optionally be followed by ^number.
// Whitespace or a forward slash is recommended between consecutive units, although the following technically is parseable:
// 2m^2kg/s^2
// it is not good form. If a unit starts with e, then it could be confused as a floating point number:
// 4erg
数值为浮点数值(在实际应用中可以使用mathjs提供的BigNumber、Fraction等类型)
单位为一个英文字母开头的,由英文字母与数字组成的字符串,此处的数字允许带有部分数学运算符号,如"^"、"/"、“(”、“)”等
单位的解析
在解析的时候,mathjs先解析数值部分,根据配置转换为数值类型(BigNumber、Fraction等)。而对于后面的单位,mathjs会将其分割成一个一个的基本单位进行解析
比如上面的"2m^2kg/s^2",单位部分为m^2kg/s^2,会被分成m^2、kg、s^2三个部分,我们可以通过如下代码查看其数据结构。
const unitValue = math.unit('1', 'm^2kg/s^2');
console.log(JSON.stringify(unitValue.units, null, 2));
得到如下输出
[
{
"unit": {
"name": "m",
"base": {
"dimensions": [ 0, 1, 0, 0, 0, 0, 0, 0, 0 ],
"key": "LENGTH"
},
"prefixes": { ... },
"value": 1,
"offset": 0,
"dimensions": [ 0, 1, 0, 0, 0, 0, 0, 0, 0 ]
},
"prefix": {
"name": "",
"value": 1,
"scientific": true
},
"power": 2
},
{
"unit": {
"name": "g",
"base": {
"dimensions": [ 1, 0, 0, 0, 0, 0, 0, 0, 0 ],
"key": "MASS"
},
"prefixes": { ... },
"value": 0.001,
"offset": 0,
"dimensions": [ 1, 0, 0, 0, 0, 0, 0, 0, 0 ]
},
"prefix": {
"name": "k",
"value": 1000,
"scientific": true
},
"power": 1
},
{
"unit": {
"name": "s",
"base": {
"dimensions": [ 0, 0, 1, 0, 0, 0, 0, 0, 0 ],
"key": "TIME"
},
"prefixes": { ... },
"value": 1,
"offset": 0,
"dimensions": [ 0, 0, 1, 0, 0, 0, 0, 0, 0 ]
},
"prefix": {
"name": "",
"value": 1,
"scientific": true
},
"power": -2
}
]
属性解析
每个单位被分为prefix、unit、power三个部分:
prefix
单位的前缀,比如“kg”中的k就是前缀部分,这代表它的值是单位g的1000倍,所以它的prefix中的value为1000
power
是单位的幂次数,比如平方米m2(或者用m^2表示),它的次数就是2,但是我们看到上面的输出中,s^2的power值为-2,这是因为它前面有一个反斜杠“/”,所以它的次数被乘了-1
unit
部分代表了一个基本单位,有如下的属性
unit
name
单位的名称,由英文字母组成
base
单位的基础,其中的key表示了单位的衡量类型,mathjs提供了如下类型的单位
MASS:质量
LENGTH:长度
TIME:时间
CURRENT:电流(安培等)
TEMPERATURE:温度
LUMINOUS_INTENSITY:光度
AMOUNT_OF_SUBSTANCE:物质的量(摩尔)
ANGLE:角度
BIT:数据量
以上的为基本类型,每个作为一个基础维度Dimension,后面的单位都是由它们通过乘除运算得出
FORCE:力
SURFACE:面积
VOLUME:体积
ENERGY:能量/功
POWER:功率
PRESSURE:压强
ELECTRIC_CHARGE
ELECTRIC_CAPACITANCE
ELECTRIC_POTENTIAL
ELECTRIC_RESISTANCE
ELECTRIC_INDUCTANCE
ELECTRIC_CONDUCTANCE
MAGNETIC_FLUX
MAGNETIC_FLUX_DENSITY
FREQUENCY:频率
value
对于该类型的基本单位的值
比如mathjs中质量的标准单位为kg(千克),而克与千克的转换公式为 g = 0.001 kg
,所以克的单位g的value值为0.001
offset
相对于该类型基本单位的偏移值
比如mathjs中温度的标准单位为K(开尔文),而摄氏度与开尔文的转换公式为 C = K + 273.15
, 所以摄氏度的单位degC的offset值为273.15
prefixes
单位支持的前缀列表,针对不同类型的单位,提供了不同的前缀集
比如单位缩写中常见的前缀有k(千)、m(毫)、M(兆), 而它们在完整的单位中,则实用kilo、milli、mega表示
dimensions
单位的维度向量,与base中的dimensions是一样的,是一个数组,数组中的每一个元素代表一种基本单位类型在该单位中的次数
从左到右分别代表:
- 质量
- 长度
- 时间
- 电流
- 温度
- 光度
- 物质的量
- 角度
- 数据量
在基本单位类型中,只有自身对应的维的值为1,其它维的值都是0
当两个单位相乘时,它们的维度向量相加,当一个单位除以另一个单位时,它们的维度向量相减
如 面积单位 = 长度单位 * 长度单位
,那么面积单位的维度向量就是 [0, 2, 0, 0, 0, 0, 0, 0, 0],其中的2就是长度维度的次数
又比如速度单位 = 长度单位 / 时间单位,name速度单位的维度向量就是 [0, 1, -1, 0, 0, 0, 0, 0, 0],其中的1是长度维度次数,-1是时间维度次数
通过维度值,可以计算两个单位是否同一类型,如果是同一类型的单位,那么它们可以互相转化,如:
J(焦耳) = N(牛顿) * m(米)
kWh(千瓦时) = kW(千瓦) * h(小时)
N(牛顿)的单位类型是力,维度向量为 [1, 1, -2, 0, 0, 0, 0, 0, 0]
m(米)的单位类型是长度,维度向量为 [0, 1, 0, 0, 0, 0, 0, 0, 0]
它们相乘,得到J(焦耳),J(焦耳)的维度向量为它们两者的维度向量相加,即 [1, 2, -2, 0, 0, 0, 0, 0, 0]
kW(千瓦)是带前缀的W(瓦特),单位类型是功率,维度向量为 [1, 2, -3, 0, 0, 0, 0, 0, 0]
h(小时)的单位类型是时间,维度向量为 [0, 0, 1, 0, 0, 0, 0, 0, 0]
它们相乘,得到kWh(千瓦时),kWh(千瓦时)的维度向量为它们两者的维度向量相加,即 [1, 2, -2, 0, 0, 0, 0, 0, 0]
可以看到两者虽然计算得到的过程不一样,但是最终它们的维度向量是一样的,它们都是能量的单位,可以互相转换,实现1kWh = 3600000 J