@quenty/metricutils 2.4.3 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -54
- package/README.md +80 -2
- package/deploy.nevermore.json +10 -0
- package/package.json +22 -4
- package/src/Shared/Definitions/MetricUnitDefinitions.lua +1082 -0
- package/src/Shared/Definitions/MetricUnitDefinitions.spec.lua +55 -0
- package/src/Shared/Definitions/MetricUnitDimensionUtils.lua +164 -0
- package/src/Shared/Definitions/MetricUnitDimensionUtils.spec.lua +49 -0
- package/src/Shared/Definitions/MetricUnitKinds.lua +237 -0
- package/src/Shared/Definitions/MetricUnitKinds.spec.lua +51 -0
- package/src/Shared/Formatting/MetricUnitDisplayUnitUtils.lua +129 -0
- package/src/Shared/Formatting/MetricUnitFormatUtils.lua +924 -0
- package/src/Shared/Formatting/MetricUnitFormatUtils.spec.lua +280 -0
- package/src/Shared/Formatting/RxMetricUnitFormatUtils.lua +62 -0
- package/src/Shared/Localization/MetricUnitFallbackStrings.lua +289 -0
- package/src/Shared/Localization/MetricUnitFallbackStrings.spec.lua +82 -0
- package/src/Shared/Localization/MetricUnitTranslator/ar.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/de.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/en.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/es.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/fr.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/id.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/init.lua +92 -0
- package/src/Shared/Localization/MetricUnitTranslator/it.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/ja.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/ko.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/pl.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/pt.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/ru.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/th.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/tr.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/vi.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/zh-cn.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator/zh-tw.json.txt +182 -0
- package/src/Shared/Localization/MetricUnitTranslator.spec.lua +309 -0
- package/src/Shared/Localization/PluralCategoryUtils.lua +103 -0
- package/src/Shared/Localization/PluralCategoryUtils.spec.lua +77 -0
- package/src/Shared/Logarithmic/DecibelUtils.lua +72 -0
- package/src/Shared/Logarithmic/DecibelUtils.spec.lua +35 -0
- package/src/Shared/MetricUnit.Angles.spec.lua +80 -0
- package/src/Shared/MetricUnit.Customary.spec.lua +104 -0
- package/src/Shared/MetricUnit.Derived.spec.lua +173 -0
- package/src/Shared/MetricUnit.Display.spec.lua +153 -0
- package/src/Shared/MetricUnit.Electrical.spec.lua +114 -0
- package/src/Shared/MetricUnit.Math.spec.lua +112 -0
- package/src/Shared/MetricUnit.Roblox.spec.lua +34 -0
- package/src/Shared/MetricUnit.SI.spec.lua +127 -0
- package/src/Shared/MetricUnit.Spring.spec.lua +94 -0
- package/src/Shared/MetricUnit.Temperature.spec.lua +212 -0
- package/src/Shared/MetricUnit.Time.spec.lua +202 -0
- package/src/Shared/MetricUnit.Units.spec.lua +210 -0
- package/src/Shared/MetricUnit.lua +1730 -0
- package/src/Shared/MetricUnit.spec.lua +362 -0
- package/src/Shared/MetricUtils.lua +47 -16
- package/src/Shared/MetricUtils.spec.lua +76 -0
- package/src/Shared/Parsing/MetricUnitParseUtils.lua +799 -0
- package/src/Shared/Parsing/MetricUnitParseUtils.spec.lua +189 -0
- package/src/Shared/Systems/MetricUnitSystem.lua +322 -0
- package/src/Shared/Systems/MetricUnitSystem.spec.lua +181 -0
- package/src/Shared/Systems/MetricUnitSystems.lua +225 -0
- package/src/jest.config.lua +3 -0
- package/src/node_modules.project.json +7 -0
- package/test/default.project.json +25 -0
- package/test/scripts/Server/ServerMain.server.lua +14 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUnitDefinitions.spec.lua
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local Jest = require("Jest")
|
|
9
|
+
local MetricUnit = require("MetricUnit")
|
|
10
|
+
local MetricUnitDefinitions = require("MetricUnitDefinitions")
|
|
11
|
+
local MetricUnitDimensionUtils = require("MetricUnitDimensionUtils")
|
|
12
|
+
|
|
13
|
+
local describe = Jest.Globals.describe
|
|
14
|
+
local expect = Jest.Globals.expect
|
|
15
|
+
local it = Jest.Globals.it
|
|
16
|
+
|
|
17
|
+
-- A unit is written by name unless @quenty/time's duration words or a per phrase write it
|
|
18
|
+
local function isWrittenByName(unit: MetricUnitDefinitions.UnitDefinition): boolean
|
|
19
|
+
return unit.per == nil and (not unit.duration or unit.id == "microsecond")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe("MetricUnitDefinitions", function()
|
|
23
|
+
it("declares each unit once, and every unit makes and reads back through MetricUnit.new", function()
|
|
24
|
+
local seen: { [string]: boolean } = {}
|
|
25
|
+
for _, unit in MetricUnitDefinitions.getUnits() do
|
|
26
|
+
expect(seen[unit.id]).toBeNil()
|
|
27
|
+
seen[unit.id] = true
|
|
28
|
+
expect(MetricUnitDefinitions.getUnit(unit.id)).toBe(unit)
|
|
29
|
+
expect(MetricUnit.new(1, unit.id):In(unit.id)).toBeCloseTo(1, 9)
|
|
30
|
+
expect(MetricUnitDimensionUtils.isDimensions(unit.dimensions)).toBe(true)
|
|
31
|
+
expect(unit.factor > 0).toBe(true)
|
|
32
|
+
end
|
|
33
|
+
end)
|
|
34
|
+
|
|
35
|
+
it("gives English names to every unit written by name, and to no other", function()
|
|
36
|
+
for _, unit in MetricUnitDefinitions.getUnits() do
|
|
37
|
+
if isWrittenByName(unit) then
|
|
38
|
+
local names = unit.names
|
|
39
|
+
assert(names, string.format("%s has no English names", unit.id))
|
|
40
|
+
expect(#names.one > 0 and #names.other > 0).toBe(true)
|
|
41
|
+
else
|
|
42
|
+
expect(unit.names).toBeNil()
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end)
|
|
46
|
+
|
|
47
|
+
it("resolves @quenty/time's names to time units", function()
|
|
48
|
+
for alias, id in MetricUnitDefinitions.getTimeAliases() do
|
|
49
|
+
local unit = MetricUnitDefinitions.getUnit(alias)
|
|
50
|
+
assert(unit, alias)
|
|
51
|
+
expect(unit.id).toBe(id)
|
|
52
|
+
expect(unit.duration).toBe(true)
|
|
53
|
+
end
|
|
54
|
+
end)
|
|
55
|
+
end)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[=[
|
|
3
|
+
Functions over dimensions: frozen tables of integer exponents by name, zero exponents left out, so a speed is
|
|
4
|
+
`{ length = 1, time = -1 }` and a plain number is `{}`. Any name is a dimension; `time`, `length`, `memory`,
|
|
5
|
+
`mass`, `temperature`, `angle`, `current`, `amount` and `luminousIntensity` are the base dimensions [MetricUnit] has units for, and derived ones such as force
|
|
6
|
+
and energy are products of those. The named combinations, such as `"speed"`, are in [MetricUnitKinds].
|
|
7
|
+
|
|
8
|
+
```lua
|
|
9
|
+
MetricUnitDimensionUtils.combine({ length = 1 }, { time = 1 }, -1) -- { length = 1, time = -1 }
|
|
10
|
+
MetricUnitDimensionUtils.describe({ length = 1, time = -1 }) -- "length/time"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
@class MetricUnitDimensionUtils
|
|
14
|
+
]=]
|
|
15
|
+
|
|
16
|
+
local MetricUnitDimensionUtils = {}
|
|
17
|
+
|
|
18
|
+
--[=[
|
|
19
|
+
Integer exponents keyed by dimension name, with zero exponents left out.
|
|
20
|
+
|
|
21
|
+
@type Dimensions { [string]: number }
|
|
22
|
+
@within MetricUnitDimensionUtils
|
|
23
|
+
]=]
|
|
24
|
+
export type Dimensions = { [string]: number }
|
|
25
|
+
|
|
26
|
+
--[=[
|
|
27
|
+
No dimensions: a plain number's, and what `combine` returns when every exponent cancels.
|
|
28
|
+
|
|
29
|
+
@prop NONE Dimensions
|
|
30
|
+
@within MetricUnitDimensionUtils
|
|
31
|
+
]=]
|
|
32
|
+
MetricUnitDimensionUtils.NONE = table.freeze({}) :: Dimensions
|
|
33
|
+
|
|
34
|
+
--[=[
|
|
35
|
+
The exponents of `a` plus `sign` times those of `b`: a product when `sign` is 1, a quotient when it is -1.
|
|
36
|
+
|
|
37
|
+
@param a Dimensions
|
|
38
|
+
@param b Dimensions
|
|
39
|
+
@param sign number -- 1 or -1
|
|
40
|
+
@return Dimensions
|
|
41
|
+
]=]
|
|
42
|
+
function MetricUnitDimensionUtils.combine(a: Dimensions, b: Dimensions, sign: number): Dimensions
|
|
43
|
+
local result: Dimensions = table.clone(a)
|
|
44
|
+
for name, exponent in b do
|
|
45
|
+
local total = (result[name] or 0) + sign * exponent
|
|
46
|
+
if total == 0 then
|
|
47
|
+
result[name] = nil
|
|
48
|
+
else
|
|
49
|
+
result[name] = total
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
return table.freeze(result)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
--[=[
|
|
56
|
+
The exponents of `a` times `factor`, erroring if any is not a whole number.
|
|
57
|
+
|
|
58
|
+
@param a Dimensions
|
|
59
|
+
@param factor number
|
|
60
|
+
@return Dimensions
|
|
61
|
+
]=]
|
|
62
|
+
function MetricUnitDimensionUtils.scale(a: Dimensions, factor: number): Dimensions
|
|
63
|
+
local result: Dimensions = {}
|
|
64
|
+
for name, exponent in a do
|
|
65
|
+
local scaled = exponent * factor
|
|
66
|
+
if scaled ~= math.floor(scaled) then
|
|
67
|
+
error(
|
|
68
|
+
string.format(
|
|
69
|
+
"MetricUnit: %s to the power %s has a fractional exponent",
|
|
70
|
+
MetricUnitDimensionUtils.describe(a),
|
|
71
|
+
tostring(factor)
|
|
72
|
+
),
|
|
73
|
+
3
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
if scaled ~= 0 then
|
|
77
|
+
result[name] = scaled
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
return table.freeze(result)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
--[=[
|
|
84
|
+
Whether `a` and `b` have the same exponents.
|
|
85
|
+
|
|
86
|
+
@param a Dimensions
|
|
87
|
+
@param b Dimensions
|
|
88
|
+
@return boolean
|
|
89
|
+
]=]
|
|
90
|
+
function MetricUnitDimensionUtils.equal(a: Dimensions, b: Dimensions): boolean
|
|
91
|
+
if a == b then
|
|
92
|
+
return true
|
|
93
|
+
end
|
|
94
|
+
for name, exponent in a do
|
|
95
|
+
if b[name] ~= exponent then
|
|
96
|
+
return false
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
for name in b do
|
|
100
|
+
if a[name] == nil then
|
|
101
|
+
return false
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
return true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
--[=[
|
|
108
|
+
Whether `value` is a table of whole-number exponents keyed by name, none of them zero.
|
|
109
|
+
|
|
110
|
+
@param value any
|
|
111
|
+
@return boolean
|
|
112
|
+
]=]
|
|
113
|
+
function MetricUnitDimensionUtils.isDimensions(value: any): boolean
|
|
114
|
+
if type(value) ~= "table" then
|
|
115
|
+
return false
|
|
116
|
+
end
|
|
117
|
+
for name, exponent in value do
|
|
118
|
+
if
|
|
119
|
+
type(name) ~= "string"
|
|
120
|
+
or type(exponent) ~= "number"
|
|
121
|
+
or exponent == 0
|
|
122
|
+
or exponent ~= math.floor(exponent)
|
|
123
|
+
then
|
|
124
|
+
return false
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
return true
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
--[=[
|
|
131
|
+
A readable formula for `dimensions`: "time", "length/time", "1/time", "length^2" or "dimensionless".
|
|
132
|
+
|
|
133
|
+
@param dimensions Dimensions
|
|
134
|
+
@return string
|
|
135
|
+
]=]
|
|
136
|
+
function MetricUnitDimensionUtils.describe(dimensions: Dimensions): string
|
|
137
|
+
local over: { string } = {}
|
|
138
|
+
local under: { string } = {}
|
|
139
|
+
for _, name in MetricUnitDimensionUtils._sortedNames(dimensions) do
|
|
140
|
+
local exponent = dimensions[name]
|
|
141
|
+
local power = math.abs(exponent)
|
|
142
|
+
local part = if power == 1 then name else name .. "^" .. tostring(power)
|
|
143
|
+
table.insert(if exponent > 0 then over else under, part)
|
|
144
|
+
end
|
|
145
|
+
if #over == 0 and #under == 0 then
|
|
146
|
+
return "dimensionless"
|
|
147
|
+
end
|
|
148
|
+
local text = if #over == 0 then "1" else table.concat(over, "·")
|
|
149
|
+
for _, part in under do
|
|
150
|
+
text ..= "/" .. part
|
|
151
|
+
end
|
|
152
|
+
return text
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
function MetricUnitDimensionUtils._sortedNames(dimensions: Dimensions): { string }
|
|
156
|
+
local names: { string } = {}
|
|
157
|
+
for name in dimensions do
|
|
158
|
+
table.insert(names, name)
|
|
159
|
+
end
|
|
160
|
+
table.sort(names)
|
|
161
|
+
return names
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
return table.freeze(MetricUnitDimensionUtils)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUnitDimensionUtils.spec.lua
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local Jest = require("Jest")
|
|
9
|
+
local MetricUnitDimensionUtils = require("MetricUnitDimensionUtils")
|
|
10
|
+
|
|
11
|
+
local describe = Jest.Globals.describe
|
|
12
|
+
local expect = Jest.Globals.expect
|
|
13
|
+
local it = Jest.Globals.it
|
|
14
|
+
|
|
15
|
+
describe("MetricUnitDimensionUtils", function()
|
|
16
|
+
it("combines exponents, dropping zeros, frozen", function()
|
|
17
|
+
local speed = MetricUnitDimensionUtils.combine({ length = 1 }, { time = 1 }, -1)
|
|
18
|
+
expect(speed).toEqual({ length = 1, time = -1 })
|
|
19
|
+
expect(table.isfrozen(speed)).toBe(true)
|
|
20
|
+
expect(MetricUnitDimensionUtils.combine(speed, { time = 1 }, 1)).toEqual({ length = 1 })
|
|
21
|
+
expect(MetricUnitDimensionUtils.combine(speed, speed, -1)).toEqual(MetricUnitDimensionUtils.NONE)
|
|
22
|
+
end)
|
|
23
|
+
|
|
24
|
+
it("takes any name as a dimension", function()
|
|
25
|
+
local people = MetricUnitDimensionUtils.combine({ people = 1 }, { length = 1 }, -2)
|
|
26
|
+
expect(MetricUnitDimensionUtils.describe(people)).toBe("people/length^2")
|
|
27
|
+
end)
|
|
28
|
+
|
|
29
|
+
it("scales exponents, erroring on a fractional one", function()
|
|
30
|
+
expect(MetricUnitDimensionUtils.scale({ length = 2 }, 0.5)).toEqual({ length = 1 })
|
|
31
|
+
expect(function()
|
|
32
|
+
MetricUnitDimensionUtils.scale({ length = 1 }, 0.5)
|
|
33
|
+
end).toThrow("length to the power 0.5 has a fractional exponent")
|
|
34
|
+
end)
|
|
35
|
+
|
|
36
|
+
it("describes dimensions as a formula", function()
|
|
37
|
+
expect(MetricUnitDimensionUtils.describe(MetricUnitDimensionUtils.NONE)).toBe("dimensionless")
|
|
38
|
+
expect(MetricUnitDimensionUtils.describe({ time = -1 })).toBe("1/time")
|
|
39
|
+
expect(MetricUnitDimensionUtils.describe({ length = 2, mass = 1 })).toBe("length^2·mass")
|
|
40
|
+
end)
|
|
41
|
+
|
|
42
|
+
it("tells equal dimensions apart, and valid ones from others", function()
|
|
43
|
+
expect(MetricUnitDimensionUtils.equal({ length = 1, time = -1 }, { time = -1, length = 1 })).toBe(true)
|
|
44
|
+
expect(MetricUnitDimensionUtils.equal({ length = 1 }, { length = 1, time = -1 })).toBe(false)
|
|
45
|
+
expect(MetricUnitDimensionUtils.isDimensions({ length = 2 })).toBe(true)
|
|
46
|
+
expect(MetricUnitDimensionUtils.isDimensions({ length = 0.5 })).toBe(false)
|
|
47
|
+
expect(MetricUnitDimensionUtils.isDimensions({ length = 0 })).toBe(false)
|
|
48
|
+
end)
|
|
49
|
+
end)
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[=[
|
|
3
|
+
The named kinds of measurement, such as `"length"` and `"speed"`: each a set of dimensions, absolute or a
|
|
4
|
+
difference. [MetricUnit.GetKind], [MetricUnit.isKind] and [MetricUnit.expect] take these names. The dimension
|
|
5
|
+
functions they are built from are in [MetricUnitDimensionUtils].
|
|
6
|
+
|
|
7
|
+
```lua
|
|
8
|
+
MetricUnitKinds.SPEED -- { length = 1, time = -1 }
|
|
9
|
+
MetricUnitKinds.fromName("speed") -- { length = 1, time = -1 }, false
|
|
10
|
+
MetricUnitKinds.getKind({ temperature = 1 }, true) -- "temperature"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
@class MetricUnitKinds
|
|
14
|
+
]=]
|
|
15
|
+
|
|
16
|
+
local require = require(script.Parent.loader).load(script)
|
|
17
|
+
|
|
18
|
+
local MetricUnitDimensionUtils = require("MetricUnitDimensionUtils")
|
|
19
|
+
|
|
20
|
+
local MetricUnitKinds = {}
|
|
21
|
+
|
|
22
|
+
type Dimensions = MetricUnitDimensionUtils.Dimensions
|
|
23
|
+
|
|
24
|
+
--[=[
|
|
25
|
+
A kind's name. `"temperature"` is an absolute temperature and `"temperatureDifference"` a difference between two.
|
|
26
|
+
`"time"` is a span of time and `"instant"` a point in time, measured from an epoch the caller chooses. `"none"` is
|
|
27
|
+
a plain, dimensionless number.
|
|
28
|
+
|
|
29
|
+
@type KindName "none" | "time" | "length" | "area" | "volume" | "memory" | "mass" | "rate" | "speed" | "acceleration" | "force" | "energy" | "power" | "pressure" | "density" | "dataRate" | "temperature" | "temperatureDifference" | "instant" | "angle" | "angularVelocity" | "angularAcceleration" | "current" | "charge" | "voltage" | "resistance" | "conductance" | "capacitance" | "inductance" | "magneticFlux" | "magneticFluxDensity" | "amount" | "luminousIntensity" | "solidAngle" | "luminousFlux" | "illuminance" | "absorbedDose" | "catalyticActivity" | "molarMass" | "molarConcentration" | "fuelEconomy"
|
|
30
|
+
@within MetricUnitKinds
|
|
31
|
+
]=]
|
|
32
|
+
export type KindName =
|
|
33
|
+
"none"
|
|
34
|
+
| "time"
|
|
35
|
+
| "length"
|
|
36
|
+
| "area"
|
|
37
|
+
| "volume"
|
|
38
|
+
| "memory"
|
|
39
|
+
| "mass"
|
|
40
|
+
| "rate"
|
|
41
|
+
| "speed"
|
|
42
|
+
| "acceleration"
|
|
43
|
+
| "force"
|
|
44
|
+
| "energy"
|
|
45
|
+
| "power"
|
|
46
|
+
| "pressure"
|
|
47
|
+
| "density"
|
|
48
|
+
| "dataRate"
|
|
49
|
+
| "temperature"
|
|
50
|
+
| "temperatureDifference"
|
|
51
|
+
| "instant"
|
|
52
|
+
| "angle"
|
|
53
|
+
| "angularVelocity"
|
|
54
|
+
| "angularAcceleration"
|
|
55
|
+
| "current"
|
|
56
|
+
| "charge"
|
|
57
|
+
| "voltage"
|
|
58
|
+
| "resistance"
|
|
59
|
+
| "conductance"
|
|
60
|
+
| "capacitance"
|
|
61
|
+
| "inductance"
|
|
62
|
+
| "magneticFlux"
|
|
63
|
+
| "magneticFluxDensity"
|
|
64
|
+
| "amount"
|
|
65
|
+
| "luminousIntensity"
|
|
66
|
+
| "solidAngle"
|
|
67
|
+
| "luminousFlux"
|
|
68
|
+
| "illuminance"
|
|
69
|
+
| "absorbedDose"
|
|
70
|
+
| "catalyticActivity"
|
|
71
|
+
| "molarMass"
|
|
72
|
+
| "molarConcentration"
|
|
73
|
+
| "fuelEconomy"
|
|
74
|
+
|
|
75
|
+
MetricUnitKinds.TIME = table.freeze({ time = 1 }) :: Dimensions
|
|
76
|
+
MetricUnitKinds.LENGTH = table.freeze({ length = 1 }) :: Dimensions
|
|
77
|
+
MetricUnitKinds.MEMORY = table.freeze({ memory = 1 }) :: Dimensions
|
|
78
|
+
MetricUnitKinds.MASS = table.freeze({ mass = 1 }) :: Dimensions
|
|
79
|
+
MetricUnitKinds.TEMPERATURE = table.freeze({ temperature = 1 }) :: Dimensions
|
|
80
|
+
MetricUnitKinds.ANGLE = table.freeze({ angle = 1 }) :: Dimensions
|
|
81
|
+
MetricUnitKinds.CURRENT = table.freeze({ current = 1 }) :: Dimensions
|
|
82
|
+
MetricUnitKinds.AMOUNT = table.freeze({ amount = 1 }) :: Dimensions
|
|
83
|
+
MetricUnitKinds.LUMINOUS_INTENSITY = table.freeze({ luminousIntensity = 1 }) :: Dimensions
|
|
84
|
+
|
|
85
|
+
local function times(a: Dimensions, b: Dimensions): Dimensions
|
|
86
|
+
return MetricUnitDimensionUtils.combine(a, b, 1)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
local function per(a: Dimensions, b: Dimensions): Dimensions
|
|
90
|
+
return MetricUnitDimensionUtils.combine(a, b, -1)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
MetricUnitKinds.AREA = times(MetricUnitKinds.LENGTH, MetricUnitKinds.LENGTH)
|
|
94
|
+
MetricUnitKinds.VOLUME = times(MetricUnitKinds.AREA, MetricUnitKinds.LENGTH)
|
|
95
|
+
MetricUnitKinds.RATE = per(MetricUnitDimensionUtils.NONE, MetricUnitKinds.TIME)
|
|
96
|
+
MetricUnitKinds.SPEED = per(MetricUnitKinds.LENGTH, MetricUnitKinds.TIME)
|
|
97
|
+
MetricUnitKinds.ACCELERATION = per(MetricUnitKinds.SPEED, MetricUnitKinds.TIME)
|
|
98
|
+
MetricUnitKinds.FORCE = times(MetricUnitKinds.MASS, MetricUnitKinds.ACCELERATION)
|
|
99
|
+
MetricUnitKinds.ENERGY = times(MetricUnitKinds.FORCE, MetricUnitKinds.LENGTH)
|
|
100
|
+
MetricUnitKinds.POWER = per(MetricUnitKinds.ENERGY, MetricUnitKinds.TIME)
|
|
101
|
+
MetricUnitKinds.PRESSURE = per(MetricUnitKinds.FORCE, MetricUnitKinds.AREA)
|
|
102
|
+
MetricUnitKinds.DENSITY = per(MetricUnitKinds.MASS, MetricUnitKinds.VOLUME)
|
|
103
|
+
MetricUnitKinds.DATA_RATE = per(MetricUnitKinds.MEMORY, MetricUnitKinds.TIME)
|
|
104
|
+
MetricUnitKinds.ANGULAR_VELOCITY = per(MetricUnitKinds.ANGLE, MetricUnitKinds.TIME)
|
|
105
|
+
MetricUnitKinds.ANGULAR_ACCELERATION = per(MetricUnitKinds.ANGULAR_VELOCITY, MetricUnitKinds.TIME)
|
|
106
|
+
MetricUnitKinds.CHARGE = times(MetricUnitKinds.CURRENT, MetricUnitKinds.TIME)
|
|
107
|
+
MetricUnitKinds.VOLTAGE = per(MetricUnitKinds.POWER, MetricUnitKinds.CURRENT)
|
|
108
|
+
MetricUnitKinds.RESISTANCE = per(MetricUnitKinds.VOLTAGE, MetricUnitKinds.CURRENT)
|
|
109
|
+
MetricUnitKinds.CONDUCTANCE = per(MetricUnitKinds.CURRENT, MetricUnitKinds.VOLTAGE)
|
|
110
|
+
MetricUnitKinds.CAPACITANCE = per(MetricUnitKinds.CHARGE, MetricUnitKinds.VOLTAGE)
|
|
111
|
+
MetricUnitKinds.MAGNETIC_FLUX = times(MetricUnitKinds.VOLTAGE, MetricUnitKinds.TIME)
|
|
112
|
+
MetricUnitKinds.INDUCTANCE = per(MetricUnitKinds.MAGNETIC_FLUX, MetricUnitKinds.CURRENT)
|
|
113
|
+
MetricUnitKinds.MAGNETIC_FLUX_DENSITY = per(MetricUnitKinds.MAGNETIC_FLUX, MetricUnitKinds.AREA)
|
|
114
|
+
MetricUnitKinds.SOLID_ANGLE = times(MetricUnitKinds.ANGLE, MetricUnitKinds.ANGLE)
|
|
115
|
+
MetricUnitKinds.LUMINOUS_FLUX = times(MetricUnitKinds.LUMINOUS_INTENSITY, MetricUnitKinds.SOLID_ANGLE)
|
|
116
|
+
MetricUnitKinds.ILLUMINANCE = per(MetricUnitKinds.LUMINOUS_FLUX, MetricUnitKinds.AREA)
|
|
117
|
+
MetricUnitKinds.ABSORBED_DOSE = per(MetricUnitKinds.ENERGY, MetricUnitKinds.MASS)
|
|
118
|
+
MetricUnitKinds.CATALYTIC_ACTIVITY = per(MetricUnitKinds.AMOUNT, MetricUnitKinds.TIME)
|
|
119
|
+
MetricUnitKinds.MOLAR_MASS = per(MetricUnitKinds.MASS, MetricUnitKinds.AMOUNT)
|
|
120
|
+
MetricUnitKinds.MOLAR_CONCENTRATION = per(MetricUnitKinds.AMOUNT, MetricUnitKinds.VOLUME)
|
|
121
|
+
MetricUnitKinds.FUEL_ECONOMY = per(MetricUnitKinds.LENGTH, MetricUnitKinds.VOLUME)
|
|
122
|
+
|
|
123
|
+
type NamedKind = { name: KindName, dimensions: Dimensions, absolute: boolean }
|
|
124
|
+
|
|
125
|
+
local NAMED_KINDS_IN_LOOKUP_ORDER: { NamedKind } = {
|
|
126
|
+
{ name = "none", dimensions = MetricUnitDimensionUtils.NONE, absolute = false },
|
|
127
|
+
{ name = "time", dimensions = MetricUnitKinds.TIME, absolute = false },
|
|
128
|
+
{ name = "length", dimensions = MetricUnitKinds.LENGTH, absolute = false },
|
|
129
|
+
{ name = "area", dimensions = MetricUnitKinds.AREA, absolute = false },
|
|
130
|
+
{ name = "volume", dimensions = MetricUnitKinds.VOLUME, absolute = false },
|
|
131
|
+
{ name = "memory", dimensions = MetricUnitKinds.MEMORY, absolute = false },
|
|
132
|
+
{ name = "mass", dimensions = MetricUnitKinds.MASS, absolute = false },
|
|
133
|
+
{ name = "rate", dimensions = MetricUnitKinds.RATE, absolute = false },
|
|
134
|
+
{ name = "speed", dimensions = MetricUnitKinds.SPEED, absolute = false },
|
|
135
|
+
{ name = "acceleration", dimensions = MetricUnitKinds.ACCELERATION, absolute = false },
|
|
136
|
+
{ name = "force", dimensions = MetricUnitKinds.FORCE, absolute = false },
|
|
137
|
+
{ name = "energy", dimensions = MetricUnitKinds.ENERGY, absolute = false },
|
|
138
|
+
{ name = "power", dimensions = MetricUnitKinds.POWER, absolute = false },
|
|
139
|
+
{ name = "pressure", dimensions = MetricUnitKinds.PRESSURE, absolute = false },
|
|
140
|
+
{ name = "density", dimensions = MetricUnitKinds.DENSITY, absolute = false },
|
|
141
|
+
{ name = "dataRate", dimensions = MetricUnitKinds.DATA_RATE, absolute = false },
|
|
142
|
+
{ name = "temperature", dimensions = MetricUnitKinds.TEMPERATURE, absolute = true },
|
|
143
|
+
{ name = "temperatureDifference", dimensions = MetricUnitKinds.TEMPERATURE, absolute = false },
|
|
144
|
+
{ name = "instant", dimensions = MetricUnitKinds.TIME, absolute = true },
|
|
145
|
+
{ name = "angle", dimensions = MetricUnitKinds.ANGLE, absolute = false },
|
|
146
|
+
{ name = "angularVelocity", dimensions = MetricUnitKinds.ANGULAR_VELOCITY, absolute = false },
|
|
147
|
+
{ name = "angularAcceleration", dimensions = MetricUnitKinds.ANGULAR_ACCELERATION, absolute = false },
|
|
148
|
+
{ name = "current", dimensions = MetricUnitKinds.CURRENT, absolute = false },
|
|
149
|
+
{ name = "charge", dimensions = MetricUnitKinds.CHARGE, absolute = false },
|
|
150
|
+
{ name = "voltage", dimensions = MetricUnitKinds.VOLTAGE, absolute = false },
|
|
151
|
+
{ name = "resistance", dimensions = MetricUnitKinds.RESISTANCE, absolute = false },
|
|
152
|
+
{ name = "conductance", dimensions = MetricUnitKinds.CONDUCTANCE, absolute = false },
|
|
153
|
+
{ name = "capacitance", dimensions = MetricUnitKinds.CAPACITANCE, absolute = false },
|
|
154
|
+
{ name = "inductance", dimensions = MetricUnitKinds.INDUCTANCE, absolute = false },
|
|
155
|
+
{ name = "magneticFlux", dimensions = MetricUnitKinds.MAGNETIC_FLUX, absolute = false },
|
|
156
|
+
{ name = "magneticFluxDensity", dimensions = MetricUnitKinds.MAGNETIC_FLUX_DENSITY, absolute = false },
|
|
157
|
+
{ name = "amount", dimensions = MetricUnitKinds.AMOUNT, absolute = false },
|
|
158
|
+
{ name = "luminousIntensity", dimensions = MetricUnitKinds.LUMINOUS_INTENSITY, absolute = false },
|
|
159
|
+
{ name = "solidAngle", dimensions = MetricUnitKinds.SOLID_ANGLE, absolute = false },
|
|
160
|
+
{ name = "luminousFlux", dimensions = MetricUnitKinds.LUMINOUS_FLUX, absolute = false },
|
|
161
|
+
{ name = "illuminance", dimensions = MetricUnitKinds.ILLUMINANCE, absolute = false },
|
|
162
|
+
{ name = "absorbedDose", dimensions = MetricUnitKinds.ABSORBED_DOSE, absolute = false },
|
|
163
|
+
{ name = "catalyticActivity", dimensions = MetricUnitKinds.CATALYTIC_ACTIVITY, absolute = false },
|
|
164
|
+
{ name = "molarMass", dimensions = MetricUnitKinds.MOLAR_MASS, absolute = false },
|
|
165
|
+
{ name = "molarConcentration", dimensions = MetricUnitKinds.MOLAR_CONCENTRATION, absolute = false },
|
|
166
|
+
{ name = "fuelEconomy", dimensions = MetricUnitKinds.FUEL_ECONOMY, absolute = false },
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
local KIND_BY_NAME: { [string]: NamedKind } = {}
|
|
170
|
+
for _, kind in NAMED_KINDS_IN_LOOKUP_ORDER do
|
|
171
|
+
KIND_BY_NAME[kind.name] = table.freeze(kind)
|
|
172
|
+
end
|
|
173
|
+
table.freeze(NAMED_KINDS_IN_LOOKUP_ORDER)
|
|
174
|
+
table.freeze(KIND_BY_NAME)
|
|
175
|
+
|
|
176
|
+
--[=[
|
|
177
|
+
The dimensions a [KindName] stands for, or the table given, and whether it names an absolute value. Errors on an
|
|
178
|
+
unknown name.
|
|
179
|
+
|
|
180
|
+
@param kind KindName | Dimensions
|
|
181
|
+
@return Dimensions
|
|
182
|
+
@return boolean -- whether the name is of an absolute value
|
|
183
|
+
]=]
|
|
184
|
+
function MetricUnitKinds.fromName(kind: KindName | Dimensions): (Dimensions, boolean)
|
|
185
|
+
if type(kind) == "table" then
|
|
186
|
+
return kind, false
|
|
187
|
+
end
|
|
188
|
+
local named = KIND_BY_NAME[kind]
|
|
189
|
+
if not named then
|
|
190
|
+
error(string.format("MetricUnit: no kind named %q", tostring(kind)), 3)
|
|
191
|
+
end
|
|
192
|
+
return named.dimensions, named.absolute
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
--[=[
|
|
196
|
+
The [KindName] of `dimensions`, absolute or a difference, or nil for a compound without a name.
|
|
197
|
+
|
|
198
|
+
```lua
|
|
199
|
+
MetricUnitKinds.getKind({ length = 1, time = -1 }, false) -- "speed"
|
|
200
|
+
MetricUnitKinds.getKind({ temperature = 1 }, true) -- "temperature"
|
|
201
|
+
MetricUnitKinds.getKind({ length = 4 }, false) -- nil
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
@param dimensions Dimensions
|
|
205
|
+
@param absolute boolean
|
|
206
|
+
@return KindName?
|
|
207
|
+
]=]
|
|
208
|
+
function MetricUnitKinds.getKind(dimensions: Dimensions, absolute: boolean): KindName?
|
|
209
|
+
for _, kind in NAMED_KINDS_IN_LOOKUP_ORDER do
|
|
210
|
+
if kind.absolute == absolute and MetricUnitDimensionUtils.equal(kind.dimensions, dimensions) then
|
|
211
|
+
return kind.name
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
return nil
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
--[=[
|
|
218
|
+
What a value of `dimensions` is, absolute or a difference: "absolute temperature", "temperature difference",
|
|
219
|
+
"instant", "length/time".
|
|
220
|
+
|
|
221
|
+
@param dimensions Dimensions
|
|
222
|
+
@param absolute boolean
|
|
223
|
+
@return string
|
|
224
|
+
]=]
|
|
225
|
+
function MetricUnitKinds.describeKind(dimensions: Dimensions, absolute: boolean): string
|
|
226
|
+
local formula = MetricUnitDimensionUtils.describe(dimensions)
|
|
227
|
+
if absolute and MetricUnitDimensionUtils.equal(dimensions, MetricUnitKinds.TIME) then
|
|
228
|
+
return "instant"
|
|
229
|
+
elseif absolute then
|
|
230
|
+
return "absolute " .. formula
|
|
231
|
+
elseif MetricUnitDimensionUtils.equal(dimensions, MetricUnitKinds.TEMPERATURE) then
|
|
232
|
+
return formula .. " difference"
|
|
233
|
+
end
|
|
234
|
+
return formula
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
return table.freeze(MetricUnitKinds)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[[
|
|
3
|
+
@class MetricUnitKinds.spec.lua
|
|
4
|
+
]]
|
|
5
|
+
|
|
6
|
+
local require = require(script.Parent.loader).load(script)
|
|
7
|
+
|
|
8
|
+
local Jest = require("Jest")
|
|
9
|
+
local MetricUnitDimensionUtils = require("MetricUnitDimensionUtils")
|
|
10
|
+
local MetricUnitKinds = require("MetricUnitKinds")
|
|
11
|
+
|
|
12
|
+
local describe = Jest.Globals.describe
|
|
13
|
+
local expect = Jest.Globals.expect
|
|
14
|
+
local it = Jest.Globals.it
|
|
15
|
+
|
|
16
|
+
describe("MetricUnitKinds", function()
|
|
17
|
+
it("derives every kind from the base dimensions", function()
|
|
18
|
+
expect(MetricUnitKinds.DENSITY).toEqual({ mass = 1, length = -3 })
|
|
19
|
+
expect(MetricUnitKinds.POWER).toEqual({ mass = 1, length = 2, time = -3 })
|
|
20
|
+
expect(MetricUnitKinds.VOLTAGE).toEqual({ mass = 1, length = 2, time = -3, current = -1 })
|
|
21
|
+
expect(MetricUnitKinds.RESISTANCE).toEqual({ mass = 1, length = 2, time = -3, current = -2 })
|
|
22
|
+
expect(MetricUnitKinds.CAPACITANCE).toEqual({ mass = -1, length = -2, time = 4, current = 2 })
|
|
23
|
+
expect(MetricUnitKinds.MAGNETIC_FLUX_DENSITY).toEqual({ mass = 1, time = -2, current = -1 })
|
|
24
|
+
end)
|
|
25
|
+
|
|
26
|
+
it("reads a kind's dimensions and absoluteness from its name", function()
|
|
27
|
+
expect((MetricUnitKinds.fromName("dataRate"))).toEqual({ memory = 1, time = -1 })
|
|
28
|
+
expect((MetricUnitKinds.fromName("none"))).toBe(MetricUnitDimensionUtils.NONE)
|
|
29
|
+
expect(select(2, MetricUnitKinds.fromName("temperature"))).toBe(true)
|
|
30
|
+
expect(select(2, MetricUnitKinds.fromName("temperatureDifference"))).toBe(false)
|
|
31
|
+
expect((MetricUnitKinds.fromName({ length = 4 }))).toEqual({ length = 4 })
|
|
32
|
+
expect(function()
|
|
33
|
+
MetricUnitKinds.fromName("velocity" :: any)
|
|
34
|
+
end).toThrow('no kind named "velocity"')
|
|
35
|
+
end)
|
|
36
|
+
|
|
37
|
+
it("names the kind of a set of dimensions", function()
|
|
38
|
+
expect(MetricUnitKinds.getKind(MetricUnitKinds.SPEED, false)).toBe("speed")
|
|
39
|
+
expect(MetricUnitKinds.getKind(MetricUnitKinds.TEMPERATURE, true)).toBe("temperature")
|
|
40
|
+
expect(MetricUnitKinds.getKind(MetricUnitKinds.TIME, true)).toBe("instant")
|
|
41
|
+
expect(MetricUnitKinds.getKind(MetricUnitDimensionUtils.NONE, false)).toBe("none")
|
|
42
|
+
expect(MetricUnitKinds.getKind({ length = 4 }, false)).toBeNil()
|
|
43
|
+
end)
|
|
44
|
+
|
|
45
|
+
it("describes a kind, absolute or a difference", function()
|
|
46
|
+
expect(MetricUnitKinds.describeKind(MetricUnitKinds.TEMPERATURE, true)).toBe("absolute temperature")
|
|
47
|
+
expect(MetricUnitKinds.describeKind(MetricUnitKinds.TEMPERATURE, false)).toBe("temperature difference")
|
|
48
|
+
expect(MetricUnitKinds.describeKind(MetricUnitKinds.TIME, true)).toBe("instant")
|
|
49
|
+
expect(MetricUnitKinds.describeKind(MetricUnitKinds.SPEED, false)).toBe("length/time")
|
|
50
|
+
end)
|
|
51
|
+
end)
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[=[
|
|
3
|
+
Which unit to display a value in when the caller does not name one: for each dimension, the units to choose from
|
|
4
|
+
by size (`2500 m` displays as `2.5 km`), the time unit a rate is displayed per, and the SI symbols for an unnamed
|
|
5
|
+
compound. Used by [MetricUnitFormatUtils].
|
|
6
|
+
|
|
7
|
+
@class MetricUnitDisplayUnitUtils
|
|
8
|
+
]=]
|
|
9
|
+
|
|
10
|
+
local require = require(script.Parent.loader).load(script)
|
|
11
|
+
|
|
12
|
+
local MetricUnitDefinitions = require("MetricUnitDefinitions")
|
|
13
|
+
local MetricUnitDimensionUtils = require("MetricUnitDimensionUtils")
|
|
14
|
+
local MetricUnitSystem = require("MetricUnitSystem")
|
|
15
|
+
local MetricUnitSystems = require("MetricUnitSystems")
|
|
16
|
+
|
|
17
|
+
local MetricUnitDisplayUnitUtils = {}
|
|
18
|
+
|
|
19
|
+
type UnitDefinition = MetricUnitDefinitions.UnitDefinition
|
|
20
|
+
|
|
21
|
+
local PER_UNIT_IDS_SHORTEST_FIRST: { string } = table.freeze({ "second", "minute", "hour", "day", "year" })
|
|
22
|
+
local perUnitsShortestFirst: { UnitDefinition }? = nil
|
|
23
|
+
|
|
24
|
+
local SI_BASE_UNIT_SYMBOLS: { [string]: string } = table.freeze({
|
|
25
|
+
time = "s",
|
|
26
|
+
length = "m",
|
|
27
|
+
memory = "B",
|
|
28
|
+
mass = "kg",
|
|
29
|
+
temperature = "K",
|
|
30
|
+
angle = "rad",
|
|
31
|
+
current = "A",
|
|
32
|
+
amount = "mol",
|
|
33
|
+
luminousIntensity = "cd",
|
|
34
|
+
}) :: any
|
|
35
|
+
|
|
36
|
+
--[=[
|
|
37
|
+
The units a value of `dimensions` is shown in, largest first, from `system` and its fallbacks ([MetricUnitSystems.METRIC]
|
|
38
|
+
by default), or nil when none lists the dimension. An absolute value is shown on a scale people read, °C rather
|
|
39
|
+
than kelvin, where the system has one.
|
|
40
|
+
|
|
41
|
+
@param dimensions Dimensions
|
|
42
|
+
@param system MetricUnitSystem?
|
|
43
|
+
@param absolute boolean?
|
|
44
|
+
@return { UnitDefinition }?
|
|
45
|
+
]=]
|
|
46
|
+
function MetricUnitDisplayUnitUtils.getDisplayUnits(
|
|
47
|
+
dimensions: MetricUnitDimensionUtils.Dimensions,
|
|
48
|
+
system: MetricUnitSystem.MetricUnitSystem?,
|
|
49
|
+
absolute: boolean?
|
|
50
|
+
): { UnitDefinition }?
|
|
51
|
+
return MetricUnitSystem.GetDisplayUnits(system or MetricUnitSystems.METRIC, dimensions, absolute)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
--[=[
|
|
55
|
+
The largest unit on `units` that `amount` base units fills at least once, the smallest when it fills none, or
|
|
56
|
+
[MetricUnitDisplayUnitUtils.getBaseUnit] when `amount` is zero.
|
|
57
|
+
|
|
58
|
+
@param amount number
|
|
59
|
+
@param units { UnitDefinition }
|
|
60
|
+
@return UnitDefinition
|
|
61
|
+
]=]
|
|
62
|
+
function MetricUnitDisplayUnitUtils.pickDisplayUnit(amount: number, units: { UnitDefinition }): UnitDefinition
|
|
63
|
+
if amount == 0 then
|
|
64
|
+
return MetricUnitDisplayUnitUtils.getBaseUnit(units)
|
|
65
|
+
end
|
|
66
|
+
for _, unit in units do
|
|
67
|
+
if math.abs(amount / unit.factor) >= 1 then
|
|
68
|
+
return unit
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
return units[#units]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
--[=[
|
|
75
|
+
The unit on `units` that is one SI base unit, or the smallest when none is.
|
|
76
|
+
|
|
77
|
+
@param units { UnitDefinition }
|
|
78
|
+
@return UnitDefinition
|
|
79
|
+
]=]
|
|
80
|
+
function MetricUnitDisplayUnitUtils.getBaseUnit(units: { UnitDefinition }): UnitDefinition
|
|
81
|
+
for _, unit in units do
|
|
82
|
+
if unit.factor == 1 then
|
|
83
|
+
return unit
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
return units[#units]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
--[=[
|
|
90
|
+
The shortest time unit that a rate of `amount` per second reaches at least once over, or a year when none does.
|
|
91
|
+
|
|
92
|
+
@param amount number -- per second
|
|
93
|
+
@return UnitDefinition
|
|
94
|
+
]=]
|
|
95
|
+
function MetricUnitDisplayUnitUtils.pickDisplayPerUnit(amount: number): UnitDefinition
|
|
96
|
+
local units = MetricUnitDisplayUnitUtils._perUnitsShortestFirst()
|
|
97
|
+
for _, unit in units do
|
|
98
|
+
if math.abs(amount * unit.factor) >= 1 then
|
|
99
|
+
return unit
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
return units[#units]
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
function MetricUnitDisplayUnitUtils._perUnitsShortestFirst(): { UnitDefinition }
|
|
106
|
+
local found = perUnitsShortestFirst
|
|
107
|
+
if found then
|
|
108
|
+
return found
|
|
109
|
+
end
|
|
110
|
+
local units: { UnitDefinition } = {}
|
|
111
|
+
for _, id in PER_UNIT_IDS_SHORTEST_FIRST do
|
|
112
|
+
table.insert(units, (assert(MetricUnitDefinitions.getUnit(id), id)))
|
|
113
|
+
end
|
|
114
|
+
local frozen = table.freeze(units)
|
|
115
|
+
perUnitsShortestFirst = frozen
|
|
116
|
+
return frozen
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
--[=[
|
|
120
|
+
The SI base unit symbol for a dimension name, such as `"m"` for `"length"`, or the name itself when it has none.
|
|
121
|
+
|
|
122
|
+
@param name string
|
|
123
|
+
@return string
|
|
124
|
+
]=]
|
|
125
|
+
function MetricUnitDisplayUnitUtils.getBaseSymbol(name: string): string
|
|
126
|
+
return SI_BASE_UNIT_SYMBOLS[name] or name
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
return table.freeze(MetricUnitDisplayUnitUtils)
|