@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,1730 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--[=[
|
|
3
|
+
A measurement that knows what it measures: an amount in SI base units with integer exponents for each dimension.
|
|
4
|
+
Time is held in seconds, length in metres, mass in kilograms, memory in bytes, temperature in kelvin, angle in
|
|
5
|
+
radians, current in amperes, amount of substance in moles and luminous intensity in candelas: the SI base units,
|
|
6
|
+
with bytes and radians added. Derived
|
|
7
|
+
quantities such as speed, force, energy and power are products of those, so `MetricUnit.new(1, "newton")` and
|
|
8
|
+
`MetricUnit.kilograms(1) * MetricUnit.meters(1) / MetricUnit.pow(MetricUnit.seconds(1), 2)` are the same value.
|
|
9
|
+
|
|
10
|
+
Every unit has one id, such as `"kilometer"`, used to read and write it, and one constructor, such as
|
|
11
|
+
[MetricUnit.kilometers]. Read a value in any unit of its dimension with [MetricUnit.In].
|
|
12
|
+
|
|
13
|
+
Values are frozen, and every operation returns a new one. `*` and `/` combine dimensions, a plain number scales,
|
|
14
|
+
and `+`, `-`, `<`, `<=` and `==` need the same dimension on both sides. A plain `0` is zero of whatever dimension it
|
|
15
|
+
meets in `+`, `-` and the helpers; any other plain number is dimensionless.
|
|
16
|
+
|
|
17
|
+
A value is either a difference, which scales and adds freely, or absolute, a point on a scale such as a
|
|
18
|
+
temperature in °C or an instant in time. [MetricUnit.new] makes points on the temperature scales and differences
|
|
19
|
+
in every other unit; [MetricUnit.point] and [MetricUnit.difference] make either in any unit. Two absolutes subtract to a difference, an absolute and a difference add to an
|
|
20
|
+
absolute, and an absolute cannot be added to another, negated, multiplied or divided, kelvin included.
|
|
21
|
+
|
|
22
|
+
```lua
|
|
23
|
+
local speed = MetricUnit.kilometers(36) / MetricUnit.hours(1)
|
|
24
|
+
speed:In("meterPerSecond") -- 10
|
|
25
|
+
speed:In("milePerHour") -- 22.37
|
|
26
|
+
speed:GetKind() -- "speed"
|
|
27
|
+
MetricUnit.format(speed) -- "10 m/s"
|
|
28
|
+
|
|
29
|
+
local power = MetricUnit.new(3, "kilowattHour") / MetricUnit.hours(1)
|
|
30
|
+
power:In("kilowatt") -- 3
|
|
31
|
+
|
|
32
|
+
local reach = MetricUnit.expect(MetricUnit.studs(100), "length", "reach") -- 28, in metres
|
|
33
|
+
|
|
34
|
+
local swing = MetricUnit.difference(12, "celsius")
|
|
35
|
+
(MetricUnit.celsius(15) + swing):In("celsius") -- 27
|
|
36
|
+
(MetricUnit.celsius(15) - MetricUnit.celsius(10)):In("kelvin") -- 5
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Time is elapsed time in exact seconds. [MetricUnit.years] and [MetricUnit.months] are averages for long-range math
|
|
40
|
+
(a Julian year of 365.25 days, and a twelfth of it), not calendar spans; move a date by calendar months with
|
|
41
|
+
`Time.add` from `@quenty/time`. [MetricUnit.InSeconds] hands exact seconds to `task.wait`, `os.time` and
|
|
42
|
+
`TimeDurationUtils`. [MetricUnit.instant] makes a point in time on any epoch, and [MetricUnit.fromDateTime] and
|
|
43
|
+
[MetricUnit.ToDateTime] cross to and from `DateTime`.
|
|
44
|
+
|
|
45
|
+
[MetricUnit.format] writes a value for players, localized, and [MetricUnit.parse] reads one back. A value is
|
|
46
|
+
written in the system of the unit it was made from (miles stay miles, studs stay studs), and metric when made
|
|
47
|
+
from a metric unit. Convert it ([MetricUnit.ToMetric], [MetricUnit.ToImperial]) or pin one unit
|
|
48
|
+
([MetricUnit.As]) to write it otherwise; the locale never chooses. `tostring` is a fixed English debug string.
|
|
49
|
+
|
|
50
|
+
Luau cannot compare a table with a number, so `x < 0` and `x == 0` do not work; use [MetricUnit.sign] and
|
|
51
|
+
[MetricUnit.isZero].
|
|
52
|
+
|
|
53
|
+
@class MetricUnit
|
|
54
|
+
]=]
|
|
55
|
+
|
|
56
|
+
local require = require(script.Parent.loader).load(script)
|
|
57
|
+
|
|
58
|
+
local MetricUnitDefinitions = require("MetricUnitDefinitions")
|
|
59
|
+
local MetricUnitDimensionUtils = require("MetricUnitDimensionUtils")
|
|
60
|
+
local MetricUnitDisplayUnitUtils = require("MetricUnitDisplayUnitUtils")
|
|
61
|
+
local MetricUnitFormatUtils = require("MetricUnitFormatUtils")
|
|
62
|
+
local MetricUnitKinds = require("MetricUnitKinds")
|
|
63
|
+
local MetricUnitParseUtils = require("MetricUnitParseUtils")
|
|
64
|
+
local MetricUnitSystem = require("MetricUnitSystem")
|
|
65
|
+
local MetricUnitSystems = require("MetricUnitSystems")
|
|
66
|
+
|
|
67
|
+
local TOSTRING_FORMAT_OPTIONS: MetricUnitFormatUtils.MetricUnitFormatOptions =
|
|
68
|
+
table.freeze({ maximumSignificantDigits = 4 })
|
|
69
|
+
|
|
70
|
+
local MetricUnit = {}
|
|
71
|
+
MetricUnit.ClassName = "MetricUnit"
|
|
72
|
+
MetricUnit.__index = MetricUnit
|
|
73
|
+
|
|
74
|
+
export type Dimensions = MetricUnitDimensionUtils.Dimensions
|
|
75
|
+
export type KindName = MetricUnitKinds.KindName
|
|
76
|
+
|
|
77
|
+
--[=[
|
|
78
|
+
See [MetricUnitDefinitions.UnitId].
|
|
79
|
+
|
|
80
|
+
@type UnitId MetricUnitDefinitions.UnitId
|
|
81
|
+
@within MetricUnit
|
|
82
|
+
]=]
|
|
83
|
+
export type UnitId = MetricUnitDefinitions.UnitId
|
|
84
|
+
|
|
85
|
+
--[=[
|
|
86
|
+
See [MetricUnitDefinitions.UnitLike].
|
|
87
|
+
|
|
88
|
+
@type UnitLike MetricUnitDefinitions.UnitLike
|
|
89
|
+
@within MetricUnit
|
|
90
|
+
]=]
|
|
91
|
+
export type UnitLike = MetricUnitDefinitions.UnitLike
|
|
92
|
+
|
|
93
|
+
--[=[
|
|
94
|
+
See [MetricUnitFormatUtils.MetricUnitFormatOptions].
|
|
95
|
+
|
|
96
|
+
@type FormatOptions MetricUnitFormatUtils.MetricUnitFormatOptions
|
|
97
|
+
@within MetricUnit
|
|
98
|
+
]=]
|
|
99
|
+
export type FormatOptions = MetricUnitFormatUtils.MetricUnitFormatOptions
|
|
100
|
+
|
|
101
|
+
--[=[
|
|
102
|
+
See [MetricUnitFormatUtils.MetricUnitData].
|
|
103
|
+
|
|
104
|
+
@type MetricUnitData MetricUnitFormatUtils.MetricUnitData
|
|
105
|
+
@within MetricUnit
|
|
106
|
+
]=]
|
|
107
|
+
export type MetricUnitData = MetricUnitFormatUtils.MetricUnitData
|
|
108
|
+
type System = MetricUnitSystem.MetricUnitSystem
|
|
109
|
+
type DisplayPreference = { system: System?, unit: MetricUnitDefinitions.UnitDefinition? }
|
|
110
|
+
|
|
111
|
+
-- Metatable typed as the whole class, not { __index = MetricUnit }, so the checker sees the metamethods
|
|
112
|
+
export type MetricUnit = typeof(setmetatable(
|
|
113
|
+
{} :: {
|
|
114
|
+
_amount: number,
|
|
115
|
+
_dimensions: Dimensions,
|
|
116
|
+
_absolute: boolean,
|
|
117
|
+
_display: DisplayPreference?,
|
|
118
|
+
},
|
|
119
|
+
{} :: typeof(MetricUnit)
|
|
120
|
+
))
|
|
121
|
+
|
|
122
|
+
--[=[
|
|
123
|
+
`amount` of the unit `unit`: the constructor for every unit. The common ones also have their own, such as
|
|
124
|
+
[MetricUnit.meters]; the rest, and units chosen at runtime, use this. A temperature scale gives an absolute
|
|
125
|
+
temperature, as [MetricUnit.celsius] does.
|
|
126
|
+
|
|
127
|
+
```lua
|
|
128
|
+
MetricUnit.new(5, "kilometer") == MetricUnit.kilometers(5) -- true
|
|
129
|
+
MetricUnit.new(90, "minutes") == MetricUnit.hours(1.5) -- true
|
|
130
|
+
MetricUnit.new(60, "milePerHour")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
@param amount number
|
|
134
|
+
@param unit UnitLike
|
|
135
|
+
@return MetricUnit
|
|
136
|
+
]=]
|
|
137
|
+
function MetricUnit.new(amount: number, unit: UnitLike): MetricUnit
|
|
138
|
+
return MetricUnit._fromUnit(amount, unit, "new")
|
|
139
|
+
end
|
|
140
|
+
--[=[
|
|
141
|
+
A point `amount` of `unit` from that unit's zero: a reading on a scale rather than a change along it. A unit's zero
|
|
142
|
+
is where its scale starts, such as -273.15 °C for kelvin, and is 0 for every unit but the temperature scales; time
|
|
143
|
+
units count from an epoch the caller chooses, as [MetricUnit.instant] does.
|
|
144
|
+
|
|
145
|
+
```lua
|
|
146
|
+
MetricUnit.point(20, "celsius") == MetricUnit.celsius(20) -- true
|
|
147
|
+
MetricUnit.point(90, "second") == MetricUnit.instant(90) -- true
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
@param amount number
|
|
151
|
+
@param unit UnitLike
|
|
152
|
+
@return MetricUnit
|
|
153
|
+
]=]
|
|
154
|
+
function MetricUnit.point(amount: number, unit: UnitLike): MetricUnit
|
|
155
|
+
local definition = MetricUnit._definitionFor(amount, unit, "point", 3)
|
|
156
|
+
return MetricUnit._new(
|
|
157
|
+
amount * definition.factor + (definition.zero or 0),
|
|
158
|
+
definition.dimensions,
|
|
159
|
+
true,
|
|
160
|
+
MetricUnit._displayOfUnit(definition)
|
|
161
|
+
)
|
|
162
|
+
end
|
|
163
|
+
--[=[
|
|
164
|
+
A difference of `amount` of `unit`: a change along a scale rather than a reading on it. Most units already make
|
|
165
|
+
differences with [MetricUnit.new]; this is for the temperature scales, whose readings are points.
|
|
166
|
+
|
|
167
|
+
```lua
|
|
168
|
+
MetricUnit.difference(12, "celsius"):In("kelvin") -- 12
|
|
169
|
+
MetricUnit.difference(9, "fahrenheit"):In("kelvin") -- 5
|
|
170
|
+
(MetricUnit.celsius(15) + MetricUnit.difference(12, "celsius")):In("celsius") -- 27
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
@param amount number
|
|
174
|
+
@param unit UnitLike
|
|
175
|
+
@return MetricUnit
|
|
176
|
+
]=]
|
|
177
|
+
function MetricUnit.difference(amount: number, unit: UnitLike): MetricUnit
|
|
178
|
+
local definition = MetricUnit._definitionFor(amount, unit, "difference", 3)
|
|
179
|
+
return MetricUnit._new(
|
|
180
|
+
amount * definition.factor,
|
|
181
|
+
definition.dimensions,
|
|
182
|
+
false,
|
|
183
|
+
MetricUnit._displayOfUnit(definition)
|
|
184
|
+
)
|
|
185
|
+
end
|
|
186
|
+
--[=[
|
|
187
|
+
A dimensionless value.
|
|
188
|
+
|
|
189
|
+
@param n number
|
|
190
|
+
@return MetricUnit
|
|
191
|
+
]=]
|
|
192
|
+
function MetricUnit.none(n: number): MetricUnit
|
|
193
|
+
MetricUnit._assertNumber(n, "none")
|
|
194
|
+
return MetricUnit._new(n, MetricUnitDimensionUtils.NONE, false)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
--[=[
|
|
198
|
+
`n` milliseconds.
|
|
199
|
+
|
|
200
|
+
@param n number
|
|
201
|
+
@return MetricUnit
|
|
202
|
+
]=]
|
|
203
|
+
function MetricUnit.milliseconds(n: number): MetricUnit
|
|
204
|
+
return MetricUnit._fromUnit(n, "millisecond", "milliseconds")
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
--[=[
|
|
208
|
+
`n` seconds.
|
|
209
|
+
|
|
210
|
+
@param n number
|
|
211
|
+
@return MetricUnit
|
|
212
|
+
]=]
|
|
213
|
+
function MetricUnit.seconds(n: number): MetricUnit
|
|
214
|
+
return MetricUnit._fromUnit(n, "second", "seconds")
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
--[=[
|
|
218
|
+
`n` minutes.
|
|
219
|
+
|
|
220
|
+
@param n number
|
|
221
|
+
@return MetricUnit
|
|
222
|
+
]=]
|
|
223
|
+
function MetricUnit.minutes(n: number): MetricUnit
|
|
224
|
+
return MetricUnit._fromUnit(n, "minute", "minutes")
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
--[=[
|
|
228
|
+
`n` hours.
|
|
229
|
+
|
|
230
|
+
@param n number
|
|
231
|
+
@return MetricUnit
|
|
232
|
+
]=]
|
|
233
|
+
function MetricUnit.hours(n: number): MetricUnit
|
|
234
|
+
return MetricUnit._fromUnit(n, "hour", "hours")
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
--[=[
|
|
238
|
+
`n` days of 24 hours.
|
|
239
|
+
|
|
240
|
+
@param n number
|
|
241
|
+
@return MetricUnit
|
|
242
|
+
]=]
|
|
243
|
+
function MetricUnit.days(n: number): MetricUnit
|
|
244
|
+
return MetricUnit._fromUnit(n, "day", "days")
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
--[=[
|
|
248
|
+
`n` weeks of seven days.
|
|
249
|
+
|
|
250
|
+
@param n number
|
|
251
|
+
@return MetricUnit
|
|
252
|
+
]=]
|
|
253
|
+
function MetricUnit.weeks(n: number): MetricUnit
|
|
254
|
+
return MetricUnit._fromUnit(n, "week", "weeks")
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
--[=[
|
|
258
|
+
`n` averaged months, each exactly a twelfth of an averaged year. For calendar months, use `Time.add`.
|
|
259
|
+
|
|
260
|
+
@param n number
|
|
261
|
+
@return MetricUnit
|
|
262
|
+
]=]
|
|
263
|
+
function MetricUnit.months(n: number): MetricUnit
|
|
264
|
+
return MetricUnit._fromUnit(n, "month", "months")
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
--[=[
|
|
268
|
+
`n` averaged years of 365.25 days (a Julian year), for long-range elapsed time. For calendar years, use `Time.add`.
|
|
269
|
+
|
|
270
|
+
@param n number
|
|
271
|
+
@return MetricUnit
|
|
272
|
+
]=]
|
|
273
|
+
function MetricUnit.years(n: number): MetricUnit
|
|
274
|
+
return MetricUnit._fromUnit(n, "year", "years")
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
--[=[
|
|
278
|
+
`n` times a second.
|
|
279
|
+
|
|
280
|
+
@param n number
|
|
281
|
+
@return MetricUnit
|
|
282
|
+
]=]
|
|
283
|
+
function MetricUnit.perSecond(n: number): MetricUnit
|
|
284
|
+
return MetricUnit._fromUnit(n, "perSecond", "perSecond")
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
--[=[
|
|
288
|
+
`n` times an hour.
|
|
289
|
+
|
|
290
|
+
@param n number
|
|
291
|
+
@return MetricUnit
|
|
292
|
+
]=]
|
|
293
|
+
function MetricUnit.perHour(n: number): MetricUnit
|
|
294
|
+
return MetricUnit._fromUnit(n, "perHour", "perHour")
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
--[=[
|
|
298
|
+
`n` times a day.
|
|
299
|
+
|
|
300
|
+
@param n number
|
|
301
|
+
@return MetricUnit
|
|
302
|
+
]=]
|
|
303
|
+
function MetricUnit.perDay(n: number): MetricUnit
|
|
304
|
+
return MetricUnit._fromUnit(n, "perDay", "perDay")
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
--[=[
|
|
308
|
+
`n` times a year.
|
|
309
|
+
|
|
310
|
+
@param n number
|
|
311
|
+
@return MetricUnit
|
|
312
|
+
]=]
|
|
313
|
+
function MetricUnit.perYear(n: number): MetricUnit
|
|
314
|
+
return MetricUnit._fromUnit(n, "perYear", "perYear")
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
--[=[
|
|
318
|
+
`n` millimetres.
|
|
319
|
+
|
|
320
|
+
@param n number
|
|
321
|
+
@return MetricUnit
|
|
322
|
+
]=]
|
|
323
|
+
function MetricUnit.millimeters(n: number): MetricUnit
|
|
324
|
+
return MetricUnit._fromUnit(n, "millimeter", "millimeters")
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
--[=[
|
|
328
|
+
`n` metres.
|
|
329
|
+
|
|
330
|
+
@param n number
|
|
331
|
+
@return MetricUnit
|
|
332
|
+
]=]
|
|
333
|
+
function MetricUnit.meters(n: number): MetricUnit
|
|
334
|
+
return MetricUnit._fromUnit(n, "meter", "meters")
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
--[=[
|
|
338
|
+
`n` kilometres.
|
|
339
|
+
|
|
340
|
+
@param n number
|
|
341
|
+
@return MetricUnit
|
|
342
|
+
]=]
|
|
343
|
+
function MetricUnit.kilometers(n: number): MetricUnit
|
|
344
|
+
return MetricUnit._fromUnit(n, "kilometer", "kilometers")
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
--[=[
|
|
348
|
+
`n` Roblox studs, 28 cm each, as Roblox measures them against the default avatar. For studs at a game's own
|
|
349
|
+
scale, use its system: `MINI:New(n, "stud")`.
|
|
350
|
+
|
|
351
|
+
```lua
|
|
352
|
+
MetricUnit.studs(10):In("meter") -- 2.8
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
@param n number
|
|
356
|
+
@return MetricUnit
|
|
357
|
+
]=]
|
|
358
|
+
function MetricUnit.studs(n: number): MetricUnit
|
|
359
|
+
return MetricUnit._fromUnit(n, "stud", "studs")
|
|
360
|
+
end
|
|
361
|
+
--[=[
|
|
362
|
+
`n` square kilometres.
|
|
363
|
+
|
|
364
|
+
@param n number
|
|
365
|
+
@return MetricUnit
|
|
366
|
+
]=]
|
|
367
|
+
function MetricUnit.squareKilometers(n: number): MetricUnit
|
|
368
|
+
return MetricUnit._fromUnit(n, "squareKilometer", "squareKilometers")
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
--[=[
|
|
372
|
+
`n` grams.
|
|
373
|
+
|
|
374
|
+
@param n number
|
|
375
|
+
@return MetricUnit
|
|
376
|
+
]=]
|
|
377
|
+
function MetricUnit.grams(n: number): MetricUnit
|
|
378
|
+
return MetricUnit._fromUnit(n, "gram", "grams")
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
--[=[
|
|
382
|
+
`n` kilograms.
|
|
383
|
+
|
|
384
|
+
@param n number
|
|
385
|
+
@return MetricUnit
|
|
386
|
+
]=]
|
|
387
|
+
function MetricUnit.kilograms(n: number): MetricUnit
|
|
388
|
+
return MetricUnit._fromUnit(n, "kilogram", "kilograms")
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
--[=[
|
|
392
|
+
`n` tonnes of 1,000 kg.
|
|
393
|
+
|
|
394
|
+
@param n number
|
|
395
|
+
@return MetricUnit
|
|
396
|
+
]=]
|
|
397
|
+
function MetricUnit.tonnes(n: number): MetricUnit
|
|
398
|
+
return MetricUnit._fromUnit(n, "tonne", "tonnes")
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
--[=[
|
|
402
|
+
`n` bytes.
|
|
403
|
+
|
|
404
|
+
@param n number
|
|
405
|
+
@return MetricUnit
|
|
406
|
+
]=]
|
|
407
|
+
function MetricUnit.bytes(n: number): MetricUnit
|
|
408
|
+
return MetricUnit._fromUnit(n, "byte", "bytes")
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
--[=[
|
|
412
|
+
`n` kilobytes of 1,000 bytes.
|
|
413
|
+
|
|
414
|
+
@param n number
|
|
415
|
+
@return MetricUnit
|
|
416
|
+
]=]
|
|
417
|
+
function MetricUnit.kilobytes(n: number): MetricUnit
|
|
418
|
+
return MetricUnit._fromUnit(n, "kilobyte", "kilobytes")
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
--[=[
|
|
422
|
+
`n` megabytes of 1,000,000 bytes.
|
|
423
|
+
|
|
424
|
+
@param n number
|
|
425
|
+
@return MetricUnit
|
|
426
|
+
]=]
|
|
427
|
+
function MetricUnit.megabytes(n: number): MetricUnit
|
|
428
|
+
return MetricUnit._fromUnit(n, "megabyte", "megabytes")
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
--[=[
|
|
432
|
+
`n` degrees, a 360th of a revolution.
|
|
433
|
+
|
|
434
|
+
@param n number
|
|
435
|
+
@return MetricUnit
|
|
436
|
+
]=]
|
|
437
|
+
function MetricUnit.degrees(n: number): MetricUnit
|
|
438
|
+
return MetricUnit._fromUnit(n, "degree", "degrees")
|
|
439
|
+
end
|
|
440
|
+
|
|
441
|
+
--[=[
|
|
442
|
+
`n` radians, the unit `CFrame.Angles` and `math.sin` take.
|
|
443
|
+
|
|
444
|
+
@param n number
|
|
445
|
+
@return MetricUnit
|
|
446
|
+
]=]
|
|
447
|
+
function MetricUnit.radians(n: number): MetricUnit
|
|
448
|
+
return MetricUnit._fromUnit(n, "radian", "radians")
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
--[=[
|
|
452
|
+
`n` percent, a hundredth each: `percent(45)` is 0.45.
|
|
453
|
+
|
|
454
|
+
@param n number
|
|
455
|
+
@return MetricUnit
|
|
456
|
+
]=]
|
|
457
|
+
function MetricUnit.percent(n: number): MetricUnit
|
|
458
|
+
return MetricUnit._fromUnit(n, "percent", "percent")
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
--[=[
|
|
462
|
+
The absolute temperature `n` kelvin.
|
|
463
|
+
|
|
464
|
+
@param n number
|
|
465
|
+
@return MetricUnit
|
|
466
|
+
]=]
|
|
467
|
+
function MetricUnit.kelvin(n: number): MetricUnit
|
|
468
|
+
return MetricUnit._fromUnit(n, "kelvin", "kelvin")
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
--[=[
|
|
472
|
+
The absolute temperature `n` °C.
|
|
473
|
+
|
|
474
|
+
@param n number
|
|
475
|
+
@return MetricUnit
|
|
476
|
+
]=]
|
|
477
|
+
function MetricUnit.celsius(n: number): MetricUnit
|
|
478
|
+
return MetricUnit._fromUnit(n, "celsius", "celsius")
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
--[=[
|
|
482
|
+
The absolute temperature `n` °F.
|
|
483
|
+
|
|
484
|
+
@param n number
|
|
485
|
+
@return MetricUnit
|
|
486
|
+
]=]
|
|
487
|
+
function MetricUnit.fahrenheit(n: number): MetricUnit
|
|
488
|
+
return MetricUnit._fromUnit(n, "fahrenheit", "fahrenheit")
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
--[=[
|
|
492
|
+
The instant `seconds` after an epoch: a point in time rather than a span of it. The epoch is whatever the caller
|
|
493
|
+
measures from, such as a simulation's start; [MetricUnit.fromDateTime] uses the Unix epoch. Instants follow the
|
|
494
|
+
same rules as absolute temperatures: two subtract to a span, an instant and a span add to an instant, and an
|
|
495
|
+
instant cannot be added to another, scaled or negated. Instants from different epochs must not be mixed; nothing
|
|
496
|
+
can check that for you.
|
|
497
|
+
|
|
498
|
+
```lua
|
|
499
|
+
local worldStart = MetricUnit.instant(0)
|
|
500
|
+
local now = worldStart + MetricUnit.years(250)
|
|
501
|
+
(now - worldStart):In("year") -- 250
|
|
502
|
+
now:In("year") -- 250, time since its epoch
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
@param seconds number -- since the epoch
|
|
506
|
+
@return MetricUnit
|
|
507
|
+
]=]
|
|
508
|
+
function MetricUnit.instant(seconds: number): MetricUnit
|
|
509
|
+
MetricUnit._assertNumber(seconds, "instant")
|
|
510
|
+
return MetricUnit._new(seconds, MetricUnitKinds.TIME, true)
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
--[=[
|
|
514
|
+
The instant `dateTime` marks, measured from the Unix epoch, to the millisecond. Subtract two for the exact time
|
|
515
|
+
between them, which counts calendar months and leap days at their real length.
|
|
516
|
+
|
|
517
|
+
```lua
|
|
518
|
+
local played = MetricUnit.fromDateTime(DateTime.now()) - MetricUnit.fromDateTime(joinedAt)
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
@param dateTime DateTime
|
|
522
|
+
@return MetricUnit
|
|
523
|
+
]=]
|
|
524
|
+
function MetricUnit.fromDateTime(dateTime: DateTime): MetricUnit
|
|
525
|
+
if typeof(dateTime) ~= "DateTime" then
|
|
526
|
+
error(string.format("MetricUnit.fromDateTime takes a DateTime, not %s", typeof(dateTime)), 2)
|
|
527
|
+
end
|
|
528
|
+
return MetricUnit._new(dateTime.UnixTimestampMillis / 1000, MetricUnitKinds.TIME, true)
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
--[=[
|
|
532
|
+
The speed of a Roblox velocity, such as `part.AssemblyLinearVelocity`, in studs per second by its magnitude. The
|
|
533
|
+
value converts to metric through the stud like any other: read it with `:In("kilometerPerHour")`.
|
|
534
|
+
|
|
535
|
+
@param velocity Vector3 -- in studs per second
|
|
536
|
+
@return MetricUnit
|
|
537
|
+
]=]
|
|
538
|
+
function MetricUnit.fromStudVelocity(velocity: Vector3): MetricUnit
|
|
539
|
+
if typeof(velocity) ~= "Vector3" then
|
|
540
|
+
error(string.format("MetricUnit.fromStudVelocity takes a Vector3, not %s", typeof(velocity)), 2)
|
|
541
|
+
end
|
|
542
|
+
return MetricUnit.new(velocity.Magnitude, "studPerSecond")
|
|
543
|
+
end
|
|
544
|
+
--[=[
|
|
545
|
+
Reads `text` such as `"5 km"`, `"2.5 kilometers"`, `"1h30m"`, `"1:30:00"`, `"20 °C"`, `"60 mph"` or `"5 m/s"`,
|
|
546
|
+
for commands, admin tools, configs and text boxes. Returns nil and a message to show instead of erroring. See
|
|
547
|
+
[MetricUnitParseUtils.parse] for the grammar and how `kind` resolves ambiguous text such as `"m"`.
|
|
548
|
+
|
|
549
|
+
```lua
|
|
550
|
+
local reach, err = MetricUnit.parse(textBox.Text, "length")
|
|
551
|
+
if not reach then
|
|
552
|
+
warn(err)
|
|
553
|
+
end
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
@param text string
|
|
557
|
+
@param kind (KindName | Dimensions)?
|
|
558
|
+
@param options ParseOptions?
|
|
559
|
+
@return MetricUnit?
|
|
560
|
+
@return string? -- why it could not be read
|
|
561
|
+
]=]
|
|
562
|
+
function MetricUnit.parse(
|
|
563
|
+
text: string,
|
|
564
|
+
kind: (KindName | Dimensions)?,
|
|
565
|
+
options: MetricUnitParseUtils.ParseOptions?
|
|
566
|
+
): (MetricUnit?, string?)
|
|
567
|
+
return MetricUnit._parseInSystem(text, kind, options, nil)
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
function MetricUnit._parseInSystem(
|
|
571
|
+
text: string,
|
|
572
|
+
kind: (KindName | Dimensions)?,
|
|
573
|
+
options: MetricUnitParseUtils.ParseOptions?,
|
|
574
|
+
system: System?
|
|
575
|
+
): (MetricUnit?, string?)
|
|
576
|
+
local data, err = MetricUnitParseUtils._parseInSystem(text, kind, options, system)
|
|
577
|
+
if not data then
|
|
578
|
+
return nil, err
|
|
579
|
+
end
|
|
580
|
+
local value = MetricUnit.fromData(data)
|
|
581
|
+
return if system then value:ToSystem(system) else value, nil
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
--[=[
|
|
585
|
+
Whether `value` is a [MetricUnit].
|
|
586
|
+
|
|
587
|
+
@param value any
|
|
588
|
+
@return boolean
|
|
589
|
+
]=]
|
|
590
|
+
function MetricUnit.isUnit(value: any): boolean
|
|
591
|
+
return type(value) == "table" and getmetatable(value) == MetricUnit
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
--[=[
|
|
595
|
+
Whether `value` is a [MetricUnit] of `kind`, a [KindName] or a table of exponents. Safe to call on anything.
|
|
596
|
+
Errors on an unknown name, so a typo does not quietly answer false.
|
|
597
|
+
|
|
598
|
+
```lua
|
|
599
|
+
MetricUnit.isKind(MetricUnit.kilometers(1) / MetricUnit.hours(1), "speed") -- true
|
|
600
|
+
MetricUnit.isKind(MetricUnit.celsius(5), "temperature") -- true, absolute
|
|
601
|
+
MetricUnit.isKind(MetricUnit.difference(5, "celsius"), "temperatureDifference") -- true
|
|
602
|
+
MetricUnit.isKind(5, "length") -- false
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
@param value any
|
|
606
|
+
@param kind KindName | Dimensions
|
|
607
|
+
@return boolean
|
|
608
|
+
]=]
|
|
609
|
+
function MetricUnit.isKind(value: any, kind: KindName | Dimensions): boolean
|
|
610
|
+
local dimensions, absolute = MetricUnitKinds.fromName(kind)
|
|
611
|
+
return MetricUnit.isUnit(value)
|
|
612
|
+
and value._absolute == absolute
|
|
613
|
+
and MetricUnitDimensionUtils.equal(value._dimensions, dimensions)
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
--[=[
|
|
617
|
+
Whether `a` and `b` have the same dimensions; a plain 0 has every dimension and any other number none.
|
|
618
|
+
|
|
619
|
+
@param a MetricUnit | number
|
|
620
|
+
@param b MetricUnit | number
|
|
621
|
+
@return boolean
|
|
622
|
+
]=]
|
|
623
|
+
function MetricUnit.sameDimension(a: MetricUnit | number, b: MetricUnit | number): boolean
|
|
624
|
+
if a == 0 or b == 0 then
|
|
625
|
+
return true
|
|
626
|
+
end
|
|
627
|
+
return MetricUnitDimensionUtils.equal(MetricUnit._dimensionsOf(a), MetricUnit._dimensionsOf(b))
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
--[=[
|
|
631
|
+
The amount of `value` in SI base units, for checking arguments at an API boundary. Errors with `slot`, the
|
|
632
|
+
dimension expected and the one given if `value` is not of `kind`. A plain 0 is zero of any kind but an absolute
|
|
633
|
+
one.
|
|
634
|
+
|
|
635
|
+
```lua
|
|
636
|
+
local function setReach(reach: MetricUnit.MetricUnit | number)
|
|
637
|
+
local meters = MetricUnit.expect(reach, "length", "reach")
|
|
638
|
+
end
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
@param value MetricUnit | number
|
|
642
|
+
@param kind KindName | Dimensions
|
|
643
|
+
@param slot string -- what the value is for, used in the error
|
|
644
|
+
@return number -- in SI base units
|
|
645
|
+
]=]
|
|
646
|
+
function MetricUnit.expect(value: MetricUnit | number, kind: KindName | Dimensions, slot: string): number
|
|
647
|
+
local dimensions, absolute = MetricUnitKinds.fromName(kind)
|
|
648
|
+
if type(value) == "number" then
|
|
649
|
+
if value == 0 and not absolute then
|
|
650
|
+
return 0
|
|
651
|
+
elseif not absolute and next(dimensions) == nil then
|
|
652
|
+
return value
|
|
653
|
+
end
|
|
654
|
+
error(
|
|
655
|
+
string.format(
|
|
656
|
+
"MetricUnit.expect: %s expects %s, got the plain number %s",
|
|
657
|
+
slot,
|
|
658
|
+
MetricUnitKinds.describeKind(dimensions, absolute),
|
|
659
|
+
tostring(value)
|
|
660
|
+
),
|
|
661
|
+
2
|
|
662
|
+
)
|
|
663
|
+
end
|
|
664
|
+
if not MetricUnit.isUnit(value) then
|
|
665
|
+
error(
|
|
666
|
+
string.format(
|
|
667
|
+
"MetricUnit.expect: %s expects %s, got %s",
|
|
668
|
+
slot,
|
|
669
|
+
MetricUnitKinds.describeKind(dimensions, absolute),
|
|
670
|
+
typeof(value)
|
|
671
|
+
),
|
|
672
|
+
2
|
|
673
|
+
)
|
|
674
|
+
end
|
|
675
|
+
local unit = value :: MetricUnit
|
|
676
|
+
if unit._absolute ~= absolute or not MetricUnitDimensionUtils.equal(unit._dimensions, dimensions) then
|
|
677
|
+
error(
|
|
678
|
+
string.format(
|
|
679
|
+
"MetricUnit.expect: %s expects %s, got %s",
|
|
680
|
+
slot,
|
|
681
|
+
MetricUnitKinds.describeKind(dimensions, absolute),
|
|
682
|
+
MetricUnitKinds.describeKind(unit._dimensions, unit._absolute)
|
|
683
|
+
),
|
|
684
|
+
2
|
|
685
|
+
)
|
|
686
|
+
end
|
|
687
|
+
return unit._amount
|
|
688
|
+
end
|
|
689
|
+
|
|
690
|
+
--[=[
|
|
691
|
+
Whether `value` is zero.
|
|
692
|
+
|
|
693
|
+
@param value MetricUnit | number
|
|
694
|
+
@return boolean
|
|
695
|
+
]=]
|
|
696
|
+
function MetricUnit.isZero(value: MetricUnit | number): boolean
|
|
697
|
+
if type(value) == "number" then
|
|
698
|
+
return value == 0
|
|
699
|
+
end
|
|
700
|
+
MetricUnit._assertUnit(value, "isZero")
|
|
701
|
+
return value._amount == 0
|
|
702
|
+
end
|
|
703
|
+
|
|
704
|
+
--[=[
|
|
705
|
+
-1, 0 or 1 as `value` is negative, zero or positive; an absolute temperature is measured from absolute zero.
|
|
706
|
+
|
|
707
|
+
@param value MetricUnit | number
|
|
708
|
+
@return number
|
|
709
|
+
]=]
|
|
710
|
+
function MetricUnit.sign(value: MetricUnit | number): number
|
|
711
|
+
if type(value) == "number" then
|
|
712
|
+
return math.sign(value)
|
|
713
|
+
end
|
|
714
|
+
MetricUnit._assertUnit(value, "sign")
|
|
715
|
+
return math.sign(value._amount)
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
--[=[
|
|
719
|
+
How many of `unit` this is. `unit` is a unit id, any of `@quenty/time`'s names for a time unit, or another
|
|
720
|
+
[MetricUnit] to take the ratio with. Errors, naming both dimensions, when they differ.
|
|
721
|
+
|
|
722
|
+
A point reads from the unit's zero, so a temperature reads on its scale and an instant as the time since its
|
|
723
|
+
epoch; a difference reads as it is. Rates read per a time unit with the `per` ids.
|
|
724
|
+
|
|
725
|
+
```lua
|
|
726
|
+
MetricUnit.kilometers(2):In("meter") -- 2000
|
|
727
|
+
MetricUnit.minutes(90):In("hours") -- 1.5
|
|
728
|
+
MetricUnit.perDay(48):In("perHour") -- 2
|
|
729
|
+
MetricUnit.celsius(100):In("fahrenheit") -- 212
|
|
730
|
+
(MetricUnit.meters(10) / MetricUnit.seconds(1)):In(MetricUnit.kilometers(1) / MetricUnit.hours(1)) -- 36
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
@param unit UnitLike | MetricUnit
|
|
734
|
+
@return number
|
|
735
|
+
]=]
|
|
736
|
+
function MetricUnit.In(self: MetricUnit, unit: UnitLike | MetricUnit): number
|
|
737
|
+
MetricUnit._assertUnit(self, "In")
|
|
738
|
+
if not MetricUnit.isUnit(unit) then
|
|
739
|
+
local definition = MetricUnit._getUnit(unit :: UnitLike)
|
|
740
|
+
if not definition then
|
|
741
|
+
error(string.format("MetricUnit:In knows no unit %q", tostring(unit)), 2)
|
|
742
|
+
end
|
|
743
|
+
return MetricUnit._readUnit(self, definition)
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
MetricUnit._assertUnit(unit, "In")
|
|
747
|
+
local other = unit :: MetricUnit
|
|
748
|
+
MetricUnit._assertDifference(self, "take the ratio of")
|
|
749
|
+
MetricUnit._assertDifference(other, "take the ratio of")
|
|
750
|
+
if not MetricUnitDimensionUtils.equal(self._dimensions, other._dimensions) then
|
|
751
|
+
error(
|
|
752
|
+
string.format(
|
|
753
|
+
"MetricUnit:In cannot read %s in %s",
|
|
754
|
+
MetricUnitDimensionUtils.describe(self._dimensions),
|
|
755
|
+
MetricUnitDimensionUtils.describe(other._dimensions)
|
|
756
|
+
),
|
|
757
|
+
2
|
|
758
|
+
)
|
|
759
|
+
end
|
|
760
|
+
return self._amount / other._amount
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
--[=[
|
|
764
|
+
This time in exact seconds, the same as `:In("second")`: the number to hand to `task.wait`, `os.time` arithmetic
|
|
765
|
+
or `TimeDurationUtils`.
|
|
766
|
+
|
|
767
|
+
```lua
|
|
768
|
+
task.wait(MetricUnit.minutes(2):InSeconds())
|
|
769
|
+
TimeDurationUtils.format(MetricUnit.hours(36):InSeconds(), "h:mm") --> 36:00
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
@return number
|
|
773
|
+
]=]
|
|
774
|
+
function MetricUnit.InSeconds(self: MetricUnit): number
|
|
775
|
+
MetricUnit._assertUnit(self, "InSeconds")
|
|
776
|
+
return MetricUnit._readUnit(self, (assert(MetricUnitDefinitions.getUnit("second"), "No second")))
|
|
777
|
+
end
|
|
778
|
+
|
|
779
|
+
--[=[
|
|
780
|
+
This instant as a `DateTime`, to the nearest millisecond, taking its epoch to be the Unix epoch as
|
|
781
|
+
[MetricUnit.fromDateTime] does. Errors on anything but an instant. To move a date by exact time, add a span:
|
|
782
|
+
`(MetricUnit.fromDateTime(dateTime) + MetricUnit.days(7)):ToDateTime()`. To move by calendar months, use
|
|
783
|
+
`Time.add` from `@quenty/time`.
|
|
784
|
+
|
|
785
|
+
@return DateTime
|
|
786
|
+
]=]
|
|
787
|
+
function MetricUnit.ToDateTime(self: MetricUnit): DateTime
|
|
788
|
+
MetricUnit._assertUnit(self, "ToDateTime")
|
|
789
|
+
if not (self._absolute and MetricUnitDimensionUtils.equal(self._dimensions, MetricUnitKinds.TIME)) then
|
|
790
|
+
error(
|
|
791
|
+
string.format(
|
|
792
|
+
"MetricUnit:ToDateTime takes an instant, not %s",
|
|
793
|
+
MetricUnitKinds.describeKind(self._dimensions, self._absolute)
|
|
794
|
+
),
|
|
795
|
+
2
|
|
796
|
+
)
|
|
797
|
+
end
|
|
798
|
+
local seconds = self._amount
|
|
799
|
+
return DateTime.fromUnixTimestampMillis(math.floor(seconds * 1000 + 0.5))
|
|
800
|
+
end
|
|
801
|
+
|
|
802
|
+
--[=[
|
|
803
|
+
This value's amount in SI base units: seconds, metres, kilograms, bytes, kelvin, or their products.
|
|
804
|
+
|
|
805
|
+
@return number
|
|
806
|
+
]=]
|
|
807
|
+
function MetricUnit.GetBaseAmount(self: MetricUnit): number
|
|
808
|
+
return self._amount
|
|
809
|
+
end
|
|
810
|
+
|
|
811
|
+
--[=[
|
|
812
|
+
This value's frozen dimension exponents, such as `{ length = 1, time = -1 }` for a speed.
|
|
813
|
+
|
|
814
|
+
@return Dimensions
|
|
815
|
+
]=]
|
|
816
|
+
function MetricUnit.GetDimensions(self: MetricUnit): Dimensions
|
|
817
|
+
return self._dimensions
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
--[=[
|
|
821
|
+
What this value measures, such as `"length"`, `"speed"` or `"temperature"`, or nil for a compound without a
|
|
822
|
+
name, such as length to the fourth.
|
|
823
|
+
|
|
824
|
+
@return KindName?
|
|
825
|
+
]=]
|
|
826
|
+
function MetricUnit.GetKind(self: MetricUnit): KindName?
|
|
827
|
+
return MetricUnitKinds.getKind(self._dimensions, self._absolute)
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
--[=[
|
|
831
|
+
Whether this value is of `kind`, a [KindName] or a table of exponents.
|
|
832
|
+
|
|
833
|
+
@param kind KindName | Dimensions
|
|
834
|
+
@return boolean
|
|
835
|
+
]=]
|
|
836
|
+
function MetricUnit.IsKind(self: MetricUnit, kind: KindName | Dimensions): boolean
|
|
837
|
+
return MetricUnit.isKind(self, kind)
|
|
838
|
+
end
|
|
839
|
+
|
|
840
|
+
--[=[
|
|
841
|
+
Whether this value is absolute, a point on a scale such as a temperature, rather than a difference.
|
|
842
|
+
|
|
843
|
+
@return boolean
|
|
844
|
+
]=]
|
|
845
|
+
function MetricUnit.IsAbsolute(self: MetricUnit): boolean
|
|
846
|
+
return self._absolute
|
|
847
|
+
end
|
|
848
|
+
|
|
849
|
+
--[=[
|
|
850
|
+
The ids of every unit this value can be read in or formatted as, for a settings menu to offer. Metric units come
|
|
851
|
+
first, then US customary ones, then Roblox studs.
|
|
852
|
+
|
|
853
|
+
```lua
|
|
854
|
+
MetricUnit.meters(1):GetUnitIds() -- { "millimeter", "centimeter", "meter", "kilometer", "inch", ... }
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
@return { UnitId }
|
|
858
|
+
]=]
|
|
859
|
+
function MetricUnit.GetUnitIds(self: MetricUnit): { UnitId }
|
|
860
|
+
return MetricUnitDefinitions.getUnitIds(self._dimensions)
|
|
861
|
+
end
|
|
862
|
+
|
|
863
|
+
--[=[
|
|
864
|
+
This value, preferring United States customary units when it is formatted: miles, feet and inches; US gallons,
|
|
865
|
+
quarts and cups; short tons, pounds and ounces; mph, mpg (US), BTU, horsepower, psi and °F where its dimension
|
|
866
|
+
has them, and metric units elsewhere. The amount is unchanged, so arithmetic, comparison, [MetricUnit.In] and
|
|
867
|
+
saving behave exactly as before. A locale never chooses these units, not even `en-us`: call this from a
|
|
868
|
+
player's setting.
|
|
869
|
+
|
|
870
|
+
```lua
|
|
871
|
+
MetricUnit.format(MetricUnit.kilometers(5):ToUSCustomary()) --> 3.107 mi
|
|
872
|
+
MetricUnit.format(MetricUnit.new(2, "liter"):ToUSCustomary()) --> 2.113 qt
|
|
873
|
+
local shown = if settings.useImperial then distance:ToUSCustomary() else distance
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
@return MetricUnit
|
|
877
|
+
]=]
|
|
878
|
+
function MetricUnit.ToUSCustomary(self: MetricUnit): MetricUnit
|
|
879
|
+
return MetricUnit.ToSystem(self, MetricUnitSystems.US_CUSTOMARY)
|
|
880
|
+
end
|
|
881
|
+
--[=[
|
|
882
|
+
This value, preferring British imperial units when it is formatted: imperial gallons, pints and fluid ounces;
|
|
883
|
+
stone, pounds and ounces; mpg (UK); and the units Britain shares with the United States (miles, mph, BTU,
|
|
884
|
+
horsepower, psi, °F). The amount is unchanged. See [MetricUnit.ToUSCustomary] for US units.
|
|
885
|
+
|
|
886
|
+
```lua
|
|
887
|
+
MetricUnit.format(MetricUnit.new(2, "liter"):ToImperial()) --> 3.52 imp pt
|
|
888
|
+
MetricUnit.format(MetricUnit.kilograms(80):ToImperial()) --> 12.598 st
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
@return MetricUnit
|
|
892
|
+
]=]
|
|
893
|
+
function MetricUnit.ToImperial(self: MetricUnit): MetricUnit
|
|
894
|
+
return MetricUnit.ToSystem(self, MetricUnitSystems.IMPERIAL)
|
|
895
|
+
end
|
|
896
|
+
--[=[
|
|
897
|
+
This value, preferring metric units when it is formatted. Metric is already the default; this clears a US
|
|
898
|
+
customary or imperial preference or a pinned unit.
|
|
899
|
+
|
|
900
|
+
@return MetricUnit
|
|
901
|
+
]=]
|
|
902
|
+
function MetricUnit.ToMetric(self: MetricUnit): MetricUnit
|
|
903
|
+
return MetricUnit.ToSystem(self, MetricUnitSystems.METRIC)
|
|
904
|
+
end
|
|
905
|
+
|
|
906
|
+
--[=[
|
|
907
|
+
This value, preferring `system`'s units when it is formatted. The amount is unchanged, so arithmetic,
|
|
908
|
+
comparison, [MetricUnit.In] and saving behave exactly as before. [MetricUnit.ToUSCustomary],
|
|
909
|
+
[MetricUnit.ToImperial] and [MetricUnit.ToMetric] are this with the well-known systems.
|
|
910
|
+
|
|
911
|
+
```lua
|
|
912
|
+
MetricUnit.format(MetricUnit.meters(0.14):ToSystem(MINI)) --> 5 studs, in a game's own system
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
@param system MetricUnitSystem
|
|
916
|
+
@return MetricUnit
|
|
917
|
+
]=]
|
|
918
|
+
function MetricUnit.ToSystem(self: MetricUnit, system: System): MetricUnit
|
|
919
|
+
MetricUnit._assertUnit(self, "ToSystem")
|
|
920
|
+
if not MetricUnitSystem.isSystem(system) then
|
|
921
|
+
error(string.format("MetricUnit:ToSystem takes a MetricUnitSystem, not %s", typeof(system)), 2)
|
|
922
|
+
end
|
|
923
|
+
return MetricUnit._new(self._amount, self._dimensions, self._absolute, table.freeze({ system = system }))
|
|
924
|
+
end
|
|
925
|
+
--[=[
|
|
926
|
+
This value, always formatted in `unit`, which must measure the same thing. The amount is unchanged.
|
|
927
|
+
|
|
928
|
+
```lua
|
|
929
|
+
MetricUnit.format(MetricUnit.kilometers(5):As("mile")) --> 3.107 mi
|
|
930
|
+
MetricUnit.format(MetricUnit.meters(28):As("stud")) --> 100 studs
|
|
931
|
+
MetricUnit.format(height:As(MINI:GetUnit("stud"))) --> a game's own stud
|
|
932
|
+
```
|
|
933
|
+
|
|
934
|
+
@param unit UnitLike
|
|
935
|
+
@return MetricUnit
|
|
936
|
+
]=]
|
|
937
|
+
function MetricUnit.As(self: MetricUnit, unit: UnitLike): MetricUnit
|
|
938
|
+
MetricUnit._assertUnit(self, "As")
|
|
939
|
+
local definition = MetricUnit._getUnit(unit)
|
|
940
|
+
if not definition then
|
|
941
|
+
error(string.format("MetricUnit:As knows no unit %q", tostring(unit)), 2)
|
|
942
|
+
elseif not MetricUnitDimensionUtils.equal(definition.dimensions, self._dimensions) then
|
|
943
|
+
error(
|
|
944
|
+
string.format(
|
|
945
|
+
"MetricUnit:As(%q) is %s, not %s",
|
|
946
|
+
definition.id,
|
|
947
|
+
MetricUnitDimensionUtils.describe(definition.dimensions),
|
|
948
|
+
MetricUnitKinds.describeKind(self._dimensions, self._absolute)
|
|
949
|
+
),
|
|
950
|
+
2
|
|
951
|
+
)
|
|
952
|
+
end
|
|
953
|
+
local display = table.freeze({
|
|
954
|
+
system = MetricUnitSystems.getSystemOfUnit(definition),
|
|
955
|
+
unit = definition,
|
|
956
|
+
})
|
|
957
|
+
return MetricUnit._new(self._amount, self._dimensions, self._absolute, display)
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
--[=[
|
|
961
|
+
The system this value prefers to be formatted in, from [MetricUnit.ToSystem] or [MetricUnit.As], or nil for the
|
|
962
|
+
default (metric). A display concern only: it is not saved by [MetricUnit.toData] and never affects arithmetic.
|
|
963
|
+
|
|
964
|
+
@return MetricUnitSystem?
|
|
965
|
+
]=]
|
|
966
|
+
function MetricUnit.GetDisplaySystem(self: MetricUnit): System?
|
|
967
|
+
local display = self._display
|
|
968
|
+
return if display then display.system else nil
|
|
969
|
+
end
|
|
970
|
+
|
|
971
|
+
--[=[
|
|
972
|
+
The unit this value is always formatted in, from [MetricUnit.As], or nil.
|
|
973
|
+
|
|
974
|
+
@return UnitDefinition?
|
|
975
|
+
]=]
|
|
976
|
+
function MetricUnit.GetDisplayUnit(self: MetricUnit): MetricUnitDefinitions.UnitDefinition?
|
|
977
|
+
local display = self._display
|
|
978
|
+
return if display then display.unit else nil
|
|
979
|
+
end
|
|
980
|
+
|
|
981
|
+
--[=[
|
|
982
|
+
Text for players, localized, in a unit that suits the size. See [MetricUnitFormatUtils.format] for the options.
|
|
983
|
+
An instant is written as the time since its epoch, since only the caller knows what the epoch is; for a calendar
|
|
984
|
+
date, use `Time.format(x:ToDateTime(), ...)` from `@quenty/time`.
|
|
985
|
+
|
|
986
|
+
```lua
|
|
987
|
+
MetricUnit.format(MetricUnit.meters(2500)) --> 2.5 km
|
|
988
|
+
MetricUnit.format(MetricUnit.meters(2500), { unitDisplay = "long", locale = "en-gb" }) --> 2.5 kilometres
|
|
989
|
+
MetricUnit.format(MetricUnit.new(90, "kilometerPerHour"), { unit = "kilometerPerHour" }) --> 90 km/h
|
|
990
|
+
MetricUnit.format(MetricUnit.kilometers(5):ToUSCustomary()) --> 3.107 mi
|
|
991
|
+
MetricUnit.format(MetricUnit.years(1)) --> 1 year
|
|
992
|
+
MetricUnit.format(MetricUnit.minutes(90), { locale = "es-es" }) --> 1 hora, 30 minutos
|
|
993
|
+
```
|
|
994
|
+
|
|
995
|
+
@param value MetricUnit
|
|
996
|
+
@param options FormatOptions?
|
|
997
|
+
@return string
|
|
998
|
+
]=]
|
|
999
|
+
function MetricUnit.format(value: MetricUnit, options: FormatOptions?): string
|
|
1000
|
+
MetricUnit._assertUnit(value, "format")
|
|
1001
|
+
return MetricUnitFormatUtils.format(value, options)
|
|
1002
|
+
end
|
|
1003
|
+
|
|
1004
|
+
--[=[
|
|
1005
|
+
`value` as plain data, for saves.
|
|
1006
|
+
|
|
1007
|
+
@param value MetricUnit
|
|
1008
|
+
@return MetricUnitData
|
|
1009
|
+
]=]
|
|
1010
|
+
function MetricUnit.toData(value: MetricUnit): MetricUnitData
|
|
1011
|
+
MetricUnit._assertUnit(value, "toData")
|
|
1012
|
+
return {
|
|
1013
|
+
amount = value._amount,
|
|
1014
|
+
dimensions = table.clone(value._dimensions),
|
|
1015
|
+
absolute = if value._absolute then true else nil,
|
|
1016
|
+
}
|
|
1017
|
+
end
|
|
1018
|
+
|
|
1019
|
+
--[=[
|
|
1020
|
+
The [MetricUnit] that `data` from [MetricUnit.toData] holds.
|
|
1021
|
+
|
|
1022
|
+
@param data MetricUnitData
|
|
1023
|
+
@return MetricUnit
|
|
1024
|
+
]=]
|
|
1025
|
+
function MetricUnit.fromData(data: MetricUnitData): MetricUnit
|
|
1026
|
+
if
|
|
1027
|
+
type(data) ~= "table"
|
|
1028
|
+
or type(data.amount) ~= "number"
|
|
1029
|
+
or not MetricUnitDimensionUtils.isDimensions(data.dimensions)
|
|
1030
|
+
then
|
|
1031
|
+
error("MetricUnit.fromData takes { amount: number, dimensions: { [string]: integer }, absolute: boolean? }", 2)
|
|
1032
|
+
end
|
|
1033
|
+
return MetricUnit._new(data.amount, table.freeze(table.clone(data.dimensions)), data.absolute == true)
|
|
1034
|
+
end
|
|
1035
|
+
|
|
1036
|
+
--[=[
|
|
1037
|
+
The size of `value`, a difference.
|
|
1038
|
+
|
|
1039
|
+
@param value MetricUnit
|
|
1040
|
+
@return MetricUnit
|
|
1041
|
+
]=]
|
|
1042
|
+
function MetricUnit.abs(value: MetricUnit): MetricUnit
|
|
1043
|
+
MetricUnit._assertUnit(value, "abs")
|
|
1044
|
+
MetricUnit._assertDifference(value, "take the size of")
|
|
1045
|
+
return MetricUnit._new(math.abs(value._amount), value._dimensions, false, value._display)
|
|
1046
|
+
end
|
|
1047
|
+
|
|
1048
|
+
--[=[
|
|
1049
|
+
The square root of `value`, halving its exponents, erroring if any is odd.
|
|
1050
|
+
|
|
1051
|
+
@param value MetricUnit
|
|
1052
|
+
@return MetricUnit
|
|
1053
|
+
]=]
|
|
1054
|
+
function MetricUnit.sqrt(value: MetricUnit): MetricUnit
|
|
1055
|
+
MetricUnit._assertUnit(value, "sqrt")
|
|
1056
|
+
MetricUnit._assertDifference(value, "take the square root of")
|
|
1057
|
+
if value._amount < 0 then
|
|
1058
|
+
error(
|
|
1059
|
+
string.format("MetricUnit.sqrt of a negative %s", MetricUnitDimensionUtils.describe(value._dimensions)),
|
|
1060
|
+
2
|
|
1061
|
+
)
|
|
1062
|
+
end
|
|
1063
|
+
return MetricUnit._new(
|
|
1064
|
+
math.sqrt(value._amount),
|
|
1065
|
+
MetricUnitDimensionUtils.scale(value._dimensions, 0.5),
|
|
1066
|
+
false,
|
|
1067
|
+
MetricUnit._systemOnly(value._display)
|
|
1068
|
+
)
|
|
1069
|
+
end
|
|
1070
|
+
|
|
1071
|
+
--[=[
|
|
1072
|
+
`value` to the whole power `power`, multiplying its exponents.
|
|
1073
|
+
|
|
1074
|
+
@param value MetricUnit
|
|
1075
|
+
@param power number -- a whole number
|
|
1076
|
+
@return MetricUnit
|
|
1077
|
+
]=]
|
|
1078
|
+
function MetricUnit.pow(value: MetricUnit, power: number): MetricUnit
|
|
1079
|
+
MetricUnit._assertUnit(value, "pow")
|
|
1080
|
+
MetricUnit._assertDifference(value, "exponentiate")
|
|
1081
|
+
if type(power) ~= "number" or power ~= math.floor(power) then
|
|
1082
|
+
error(string.format("MetricUnit.pow takes a whole power, not %s", tostring(power)), 2)
|
|
1083
|
+
end
|
|
1084
|
+
return MetricUnit._new(
|
|
1085
|
+
value._amount ^ power,
|
|
1086
|
+
MetricUnitDimensionUtils.scale(value._dimensions, power),
|
|
1087
|
+
false,
|
|
1088
|
+
MetricUnit._systemOnly(value._display)
|
|
1089
|
+
)
|
|
1090
|
+
end
|
|
1091
|
+
|
|
1092
|
+
--[=[
|
|
1093
|
+
`value` rounded to a whole number of `step`, halves rounding up. `step` is a unit id, such as `"minute"`, or a
|
|
1094
|
+
value to snap to a multiple of. A temperature rounds on its own scale (`round(celsius(21.6), "celsius")` is
|
|
1095
|
+
22 °C) and an instant rounds from its epoch.
|
|
1096
|
+
|
|
1097
|
+
```lua
|
|
1098
|
+
MetricUnit.round(MetricUnit.seconds(95), "minute") -- 2 minutes
|
|
1099
|
+
MetricUnit.round(MetricUnit.meters(12.4), MetricUnit.meters(5)) -- 10 m
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
@param value MetricUnit
|
|
1103
|
+
@param step UnitLike | MetricUnit
|
|
1104
|
+
@return MetricUnit
|
|
1105
|
+
]=]
|
|
1106
|
+
function MetricUnit.round(value: MetricUnit, step: UnitLike | MetricUnit): MetricUnit
|
|
1107
|
+
return MetricUnit._snap(value, step, function(count: number): number
|
|
1108
|
+
return math.floor(count + 0.5)
|
|
1109
|
+
end, "round")
|
|
1110
|
+
end
|
|
1111
|
+
|
|
1112
|
+
--[=[
|
|
1113
|
+
`value` rounded down to a whole number of `step`. See [MetricUnit.round].
|
|
1114
|
+
|
|
1115
|
+
@param value MetricUnit
|
|
1116
|
+
@param step UnitLike | MetricUnit
|
|
1117
|
+
@return MetricUnit
|
|
1118
|
+
]=]
|
|
1119
|
+
function MetricUnit.floor(value: MetricUnit, step: UnitLike | MetricUnit): MetricUnit
|
|
1120
|
+
return MetricUnit._snap(value, step, math.floor, "floor")
|
|
1121
|
+
end
|
|
1122
|
+
|
|
1123
|
+
--[=[
|
|
1124
|
+
`value` rounded up to a whole number of `step`. See [MetricUnit.round].
|
|
1125
|
+
|
|
1126
|
+
@param value MetricUnit
|
|
1127
|
+
@param step UnitLike | MetricUnit
|
|
1128
|
+
@return MetricUnit
|
|
1129
|
+
]=]
|
|
1130
|
+
function MetricUnit.ceil(value: MetricUnit, step: UnitLike | MetricUnit): MetricUnit
|
|
1131
|
+
return MetricUnit._snap(value, step, math.ceil, "ceil")
|
|
1132
|
+
end
|
|
1133
|
+
|
|
1134
|
+
--[=[
|
|
1135
|
+
The smallest of `...`, all of one dimension.
|
|
1136
|
+
|
|
1137
|
+
@param ... MetricUnit | number
|
|
1138
|
+
@return MetricUnit
|
|
1139
|
+
]=]
|
|
1140
|
+
function MetricUnit.min(...: MetricUnit | number): MetricUnit
|
|
1141
|
+
return MetricUnit._extreme("min", math.min, ...)
|
|
1142
|
+
end
|
|
1143
|
+
|
|
1144
|
+
--[=[
|
|
1145
|
+
The largest of `...`, all of one dimension.
|
|
1146
|
+
|
|
1147
|
+
@param ... MetricUnit | number
|
|
1148
|
+
@return MetricUnit
|
|
1149
|
+
]=]
|
|
1150
|
+
function MetricUnit.max(...: MetricUnit | number): MetricUnit
|
|
1151
|
+
return MetricUnit._extreme("max", math.max, ...)
|
|
1152
|
+
end
|
|
1153
|
+
|
|
1154
|
+
--[=[
|
|
1155
|
+
`value` held between `low` and `high`, all of one dimension.
|
|
1156
|
+
|
|
1157
|
+
@param value MetricUnit | number
|
|
1158
|
+
@param low MetricUnit | number
|
|
1159
|
+
@param high MetricUnit | number
|
|
1160
|
+
@return MetricUnit
|
|
1161
|
+
]=]
|
|
1162
|
+
function MetricUnit.clamp(value: MetricUnit | number, low: MetricUnit | number, high: MetricUnit | number): MetricUnit
|
|
1163
|
+
return MetricUnit.min(MetricUnit.max(value, low), high)
|
|
1164
|
+
end
|
|
1165
|
+
|
|
1166
|
+
--[=[
|
|
1167
|
+
The value `t` of the way from `a` to `b`, of one dimension, for animation. Follows the absolute/difference rules,
|
|
1168
|
+
so lerping two temperatures or two instants gives a temperature or an instant.
|
|
1169
|
+
|
|
1170
|
+
@param a MetricUnit | number
|
|
1171
|
+
@param b MetricUnit | number
|
|
1172
|
+
@param t number -- 0 gives `a`, 1 gives `b`
|
|
1173
|
+
@return MetricUnit
|
|
1174
|
+
]=]
|
|
1175
|
+
function MetricUnit.lerp(a: MetricUnit | number, b: MetricUnit | number, t: number): MetricUnit
|
|
1176
|
+
if type(t) ~= "number" then
|
|
1177
|
+
error(string.format("MetricUnit.lerp takes a number t, not %s", typeof(t)), 2)
|
|
1178
|
+
end
|
|
1179
|
+
return ((a :: any) + ((b :: any) - (a :: any)) * t) :: MetricUnit
|
|
1180
|
+
end
|
|
1181
|
+
|
|
1182
|
+
--[=[
|
|
1183
|
+
The sum of `values`, all of one dimension. Absolutes cannot be summed, as with `+`.
|
|
1184
|
+
|
|
1185
|
+
@param values { MetricUnit | number }
|
|
1186
|
+
@return MetricUnit
|
|
1187
|
+
]=]
|
|
1188
|
+
function MetricUnit.sum(values: { MetricUnit | number }): MetricUnit
|
|
1189
|
+
if type(values) ~= "table" or #values == 0 then
|
|
1190
|
+
error("MetricUnit.sum takes a list of at least one value", 2)
|
|
1191
|
+
end
|
|
1192
|
+
local total: any = values[1]
|
|
1193
|
+
for index = 2, #values do
|
|
1194
|
+
total += values[index]
|
|
1195
|
+
end
|
|
1196
|
+
if type(total) == "number" then
|
|
1197
|
+
return MetricUnit.none(total)
|
|
1198
|
+
end
|
|
1199
|
+
return total
|
|
1200
|
+
end
|
|
1201
|
+
|
|
1202
|
+
--[=[
|
|
1203
|
+
The mean of `values`, all of one dimension. Absolutes average too: the mean of two temperatures is the
|
|
1204
|
+
temperature halfway between them. For the smallest or largest of a list, use
|
|
1205
|
+
`MetricUnit.min(table.unpack(values))`.
|
|
1206
|
+
|
|
1207
|
+
@param values { MetricUnit | number }
|
|
1208
|
+
@return MetricUnit
|
|
1209
|
+
]=]
|
|
1210
|
+
function MetricUnit.mean(values: { MetricUnit | number }): MetricUnit
|
|
1211
|
+
if type(values) ~= "table" or #values == 0 then
|
|
1212
|
+
error("MetricUnit.mean takes a list of at least one value", 2)
|
|
1213
|
+
end
|
|
1214
|
+
local first: any = values[1]
|
|
1215
|
+
local sumOfDifferencesFromFirst: any = 0
|
|
1216
|
+
for index = 2, #values do
|
|
1217
|
+
sumOfDifferencesFromFirst += (values[index] :: any) - first
|
|
1218
|
+
end
|
|
1219
|
+
local result: any = first + sumOfDifferencesFromFirst / #values
|
|
1220
|
+
if type(result) == "number" then
|
|
1221
|
+
return MetricUnit.none(result)
|
|
1222
|
+
end
|
|
1223
|
+
return result
|
|
1224
|
+
end
|
|
1225
|
+
|
|
1226
|
+
--[=[
|
|
1227
|
+
Whether `a` and `b`, of one dimension, are within `epsilon` of each other. `epsilon` is a [MetricUnit] of their
|
|
1228
|
+
dimension, or a plain number in SI base units (1e-9 by default).
|
|
1229
|
+
|
|
1230
|
+
@param a MetricUnit | number
|
|
1231
|
+
@param b MetricUnit | number
|
|
1232
|
+
@param epsilon (MetricUnit | number)?
|
|
1233
|
+
@return boolean
|
|
1234
|
+
]=]
|
|
1235
|
+
function MetricUnit.fuzzyEq(a: MetricUnit | number, b: MetricUnit | number, epsilon: (MetricUnit | number)?): boolean
|
|
1236
|
+
local aAmount, bAmount, dimensions = MetricUnit._pair(a, b, "compare")
|
|
1237
|
+
local tolerance = 1e-9
|
|
1238
|
+
if epsilon ~= nil then
|
|
1239
|
+
if type(epsilon) == "number" then
|
|
1240
|
+
tolerance = epsilon
|
|
1241
|
+
else
|
|
1242
|
+
tolerance = MetricUnit.expect(epsilon, dimensions, "fuzzyEq's epsilon")
|
|
1243
|
+
end
|
|
1244
|
+
end
|
|
1245
|
+
return math.abs(aAmount - bAmount) <= tolerance
|
|
1246
|
+
end
|
|
1247
|
+
|
|
1248
|
+
--[=[
|
|
1249
|
+
The length an angle sweeps along a circle of `radius`, or, for an angular velocity, the speed along it.
|
|
1250
|
+
Angles never mix with lengths on their own, so this is the one place they meet.
|
|
1251
|
+
|
|
1252
|
+
```lua
|
|
1253
|
+
MetricUnit.arcLength(MetricUnit.degrees(180), MetricUnit.meters(2)):In("meter") -- 6.283
|
|
1254
|
+
MetricUnit.arcLength(MetricUnit.new(60, "revolutionPerMinute"), MetricUnit.meters(1)):In("meterPerSecond") -- 6.283
|
|
1255
|
+
```
|
|
1256
|
+
|
|
1257
|
+
@param angular MetricUnit -- an angle, or an angle per time
|
|
1258
|
+
@param radius MetricUnit -- a length
|
|
1259
|
+
@return MetricUnit
|
|
1260
|
+
]=]
|
|
1261
|
+
function MetricUnit.arcLength(angular: MetricUnit, radius: MetricUnit): MetricUnit
|
|
1262
|
+
MetricUnit._assertUnit(angular, "arcLength")
|
|
1263
|
+
MetricUnit._assertDifference(angular, "take the arc length of")
|
|
1264
|
+
if angular._dimensions.angle ~= 1 then
|
|
1265
|
+
error(
|
|
1266
|
+
string.format(
|
|
1267
|
+
"MetricUnit.arcLength takes an angle or an angle per time, not %s",
|
|
1268
|
+
MetricUnitDimensionUtils.describe(angular._dimensions)
|
|
1269
|
+
),
|
|
1270
|
+
2
|
|
1271
|
+
)
|
|
1272
|
+
end
|
|
1273
|
+
local meters = MetricUnit.expect(radius, "length", "arcLength's radius")
|
|
1274
|
+
local dimensions = MetricUnitDimensionUtils.combine(
|
|
1275
|
+
MetricUnitDimensionUtils.combine(angular._dimensions, MetricUnitKinds.ANGLE, -1),
|
|
1276
|
+
MetricUnitKinds.LENGTH,
|
|
1277
|
+
1
|
|
1278
|
+
)
|
|
1279
|
+
return MetricUnit._new(
|
|
1280
|
+
angular._amount * meters,
|
|
1281
|
+
dimensions,
|
|
1282
|
+
false,
|
|
1283
|
+
MetricUnit._systemOnly(MetricUnit._displayOf(radius))
|
|
1284
|
+
)
|
|
1285
|
+
end
|
|
1286
|
+
|
|
1287
|
+
--[=[
|
|
1288
|
+
The sum of two values of one dimension; an absolute and a difference give an absolute.
|
|
1289
|
+
|
|
1290
|
+
@param a MetricUnit | number
|
|
1291
|
+
@param b MetricUnit | number
|
|
1292
|
+
@return MetricUnit
|
|
1293
|
+
]=]
|
|
1294
|
+
function MetricUnit.__add(a: MetricUnit | number, b: MetricUnit | number): MetricUnit
|
|
1295
|
+
local aAmount, bAmount, dimensions, aAbsolute, bAbsolute = MetricUnit._pair(a, b, "add")
|
|
1296
|
+
if aAbsolute and bAbsolute then
|
|
1297
|
+
error(
|
|
1298
|
+
string.format(
|
|
1299
|
+
"MetricUnit: cannot add two %ss; subtract them for a difference, or add a difference to one",
|
|
1300
|
+
MetricUnitKinds.describeKind(dimensions, true)
|
|
1301
|
+
),
|
|
1302
|
+
2
|
|
1303
|
+
)
|
|
1304
|
+
end
|
|
1305
|
+
local display = MetricUnit._mergeDisplay(MetricUnit._displayOf(a), MetricUnit._displayOf(b))
|
|
1306
|
+
return MetricUnit._new(aAmount + bAmount, dimensions, aAbsolute or bAbsolute, display)
|
|
1307
|
+
end
|
|
1308
|
+
|
|
1309
|
+
--[=[
|
|
1310
|
+
The difference of two values of one dimension; two absolutes give a difference.
|
|
1311
|
+
|
|
1312
|
+
@param a MetricUnit | number
|
|
1313
|
+
@param b MetricUnit | number
|
|
1314
|
+
@return MetricUnit
|
|
1315
|
+
]=]
|
|
1316
|
+
function MetricUnit.__sub(a: MetricUnit | number, b: MetricUnit | number): MetricUnit
|
|
1317
|
+
local aAmount, bAmount, dimensions, aAbsolute, bAbsolute = MetricUnit._pair(a, b, "subtract")
|
|
1318
|
+
if bAbsolute and not aAbsolute then
|
|
1319
|
+
error(
|
|
1320
|
+
string.format(
|
|
1321
|
+
"MetricUnit: cannot subtract an %s from a %s",
|
|
1322
|
+
MetricUnitKinds.describeKind(dimensions, true),
|
|
1323
|
+
MetricUnitKinds.describeKind(dimensions, false)
|
|
1324
|
+
),
|
|
1325
|
+
2
|
|
1326
|
+
)
|
|
1327
|
+
end
|
|
1328
|
+
local display = MetricUnit._mergeDisplay(MetricUnit._displayOf(a), MetricUnit._displayOf(b))
|
|
1329
|
+
return MetricUnit._new(aAmount - bAmount, dimensions, aAbsolute and not bAbsolute, display)
|
|
1330
|
+
end
|
|
1331
|
+
|
|
1332
|
+
--[=[
|
|
1333
|
+
The product, combining dimensions; a plain number scales.
|
|
1334
|
+
|
|
1335
|
+
@param a MetricUnit | number
|
|
1336
|
+
@param b MetricUnit | number
|
|
1337
|
+
@return MetricUnit
|
|
1338
|
+
]=]
|
|
1339
|
+
function MetricUnit.__mul(a: MetricUnit | number, b: MetricUnit | number): MetricUnit
|
|
1340
|
+
local aAmount, aDimensions = MetricUnit._scalable(a, "multiply")
|
|
1341
|
+
local bAmount, bDimensions = MetricUnit._scalable(b, "multiply")
|
|
1342
|
+
local display = MetricUnit._productDisplay(a, b)
|
|
1343
|
+
return MetricUnit._new(
|
|
1344
|
+
aAmount * bAmount,
|
|
1345
|
+
MetricUnitDimensionUtils.combine(aDimensions, bDimensions, 1),
|
|
1346
|
+
false,
|
|
1347
|
+
display
|
|
1348
|
+
)
|
|
1349
|
+
end
|
|
1350
|
+
|
|
1351
|
+
--[=[
|
|
1352
|
+
The quotient, combining dimensions; a plain number scales, or over a unit inverts it.
|
|
1353
|
+
|
|
1354
|
+
@param a MetricUnit | number
|
|
1355
|
+
@param b MetricUnit | number
|
|
1356
|
+
@return MetricUnit
|
|
1357
|
+
]=]
|
|
1358
|
+
function MetricUnit.__div(a: MetricUnit | number, b: MetricUnit | number): MetricUnit
|
|
1359
|
+
local aAmount, aDimensions = MetricUnit._scalable(a, "divide")
|
|
1360
|
+
local bAmount, bDimensions = MetricUnit._scalable(b, "divide")
|
|
1361
|
+
local display = MetricUnit._quotientDisplay(a, b)
|
|
1362
|
+
return MetricUnit._new(
|
|
1363
|
+
aAmount / bAmount,
|
|
1364
|
+
MetricUnitDimensionUtils.combine(aDimensions, bDimensions, -1),
|
|
1365
|
+
false,
|
|
1366
|
+
display
|
|
1367
|
+
)
|
|
1368
|
+
end
|
|
1369
|
+
|
|
1370
|
+
--[=[
|
|
1371
|
+
The negation of a difference.
|
|
1372
|
+
|
|
1373
|
+
@param a MetricUnit
|
|
1374
|
+
@return MetricUnit
|
|
1375
|
+
]=]
|
|
1376
|
+
function MetricUnit.__unm(a: MetricUnit): MetricUnit
|
|
1377
|
+
MetricUnit._assertDifference(a, "negate")
|
|
1378
|
+
return MetricUnit._new(-a._amount, a._dimensions, false, a._display)
|
|
1379
|
+
end
|
|
1380
|
+
|
|
1381
|
+
--[=[
|
|
1382
|
+
Whether `a` equals `b`, both of one kind.
|
|
1383
|
+
|
|
1384
|
+
@param a MetricUnit
|
|
1385
|
+
@param b MetricUnit
|
|
1386
|
+
@return boolean
|
|
1387
|
+
]=]
|
|
1388
|
+
function MetricUnit.__eq(a: MetricUnit, b: MetricUnit): boolean
|
|
1389
|
+
local aAmount, bAmount = MetricUnit._pair(a, b, "compare")
|
|
1390
|
+
return aAmount == bAmount
|
|
1391
|
+
end
|
|
1392
|
+
|
|
1393
|
+
--[=[
|
|
1394
|
+
Whether `a` is less than `b`, both of one kind.
|
|
1395
|
+
|
|
1396
|
+
@param a MetricUnit
|
|
1397
|
+
@param b MetricUnit
|
|
1398
|
+
@return boolean
|
|
1399
|
+
]=]
|
|
1400
|
+
function MetricUnit.__lt(a: MetricUnit, b: MetricUnit): boolean
|
|
1401
|
+
local aAmount, bAmount = MetricUnit._pair(a, b, "compare")
|
|
1402
|
+
return aAmount < bAmount
|
|
1403
|
+
end
|
|
1404
|
+
|
|
1405
|
+
--[=[
|
|
1406
|
+
Whether `a` is at most `b`, both of one kind.
|
|
1407
|
+
|
|
1408
|
+
@param a MetricUnit
|
|
1409
|
+
@param b MetricUnit
|
|
1410
|
+
@return boolean
|
|
1411
|
+
]=]
|
|
1412
|
+
function MetricUnit.__le(a: MetricUnit, b: MetricUnit): boolean
|
|
1413
|
+
local aAmount, bAmount = MetricUnit._pair(a, b, "compare")
|
|
1414
|
+
return aAmount <= bAmount
|
|
1415
|
+
end
|
|
1416
|
+
|
|
1417
|
+
--[=[
|
|
1418
|
+
[MetricUnit.format] in English with four significant digits, the same in every locale, for logs and test failures.
|
|
1419
|
+
An instant is written as its time since the epoch, such as "epoch + 1 minute, 30 seconds".
|
|
1420
|
+
|
|
1421
|
+
@return string
|
|
1422
|
+
]=]
|
|
1423
|
+
function MetricUnit.__tostring(self: MetricUnit): string
|
|
1424
|
+
local text = MetricUnitFormatUtils.format(self, TOSTRING_FORMAT_OPTIONS)
|
|
1425
|
+
if self._absolute and MetricUnitDimensionUtils.equal(self._dimensions, MetricUnitKinds.TIME) then
|
|
1426
|
+
return "epoch + " .. text
|
|
1427
|
+
end
|
|
1428
|
+
return text
|
|
1429
|
+
end
|
|
1430
|
+
|
|
1431
|
+
function MetricUnit._new(
|
|
1432
|
+
amount: number,
|
|
1433
|
+
dimensions: Dimensions,
|
|
1434
|
+
absolute: boolean,
|
|
1435
|
+
display: DisplayPreference?
|
|
1436
|
+
): MetricUnit
|
|
1437
|
+
local unit: MetricUnit = setmetatable({} :: any, MetricUnit)
|
|
1438
|
+
unit._amount = amount
|
|
1439
|
+
unit._dimensions = dimensions
|
|
1440
|
+
unit._absolute = absolute
|
|
1441
|
+
unit._display = display
|
|
1442
|
+
table.freeze(unit :: any)
|
|
1443
|
+
return unit
|
|
1444
|
+
end
|
|
1445
|
+
function MetricUnit._fromUnit(n: number, unit: string | MetricUnitDefinitions.UnitDefinition, name: string): MetricUnit
|
|
1446
|
+
local definition = MetricUnit._definitionFor(n, unit, name, 4)
|
|
1447
|
+
local display = MetricUnit._displayOfUnit(definition)
|
|
1448
|
+
local scaleZero = definition.zero
|
|
1449
|
+
if scaleZero ~= nil then
|
|
1450
|
+
return MetricUnit._new(n * definition.factor + scaleZero, definition.dimensions, true, display)
|
|
1451
|
+
end
|
|
1452
|
+
return MetricUnit._new(n * definition.factor, definition.dimensions, false, display)
|
|
1453
|
+
end
|
|
1454
|
+
|
|
1455
|
+
function MetricUnit._displayOfUnit(definition: MetricUnitDefinitions.UnitDefinition): DisplayPreference?
|
|
1456
|
+
local system = MetricUnitSystems.getSystemOfUnit(definition)
|
|
1457
|
+
if system == nil or rawequal(system, MetricUnitSystems.METRIC) then
|
|
1458
|
+
return nil
|
|
1459
|
+
end
|
|
1460
|
+
return table.freeze({ system = system })
|
|
1461
|
+
end
|
|
1462
|
+
|
|
1463
|
+
function MetricUnit._getUnit(
|
|
1464
|
+
unit: string | MetricUnitDefinitions.UnitDefinition,
|
|
1465
|
+
system: System?
|
|
1466
|
+
): MetricUnitDefinitions.UnitDefinition?
|
|
1467
|
+
if system ~= nil and not MetricUnitSystem.isSystem(system) then
|
|
1468
|
+
error(string.format("Bad system, expected a MetricUnitSystem, not %s", typeof(system)), 4)
|
|
1469
|
+
end
|
|
1470
|
+
return if system then system:GetUnit(unit) else MetricUnitDefinitions.getUnit(unit)
|
|
1471
|
+
end
|
|
1472
|
+
function MetricUnit._definitionFor(
|
|
1473
|
+
n: number,
|
|
1474
|
+
unit: any,
|
|
1475
|
+
name: string,
|
|
1476
|
+
errorLevel: number
|
|
1477
|
+
): MetricUnitDefinitions.UnitDefinition
|
|
1478
|
+
if type(n) ~= "number" then
|
|
1479
|
+
error(string.format("MetricUnit.%s takes a number, not %s", name, typeof(n)), errorLevel)
|
|
1480
|
+
end
|
|
1481
|
+
local definition = MetricUnit._getUnit(unit)
|
|
1482
|
+
if not definition then
|
|
1483
|
+
error(string.format("MetricUnit.%s knows no unit %q", name, tostring(unit)), errorLevel)
|
|
1484
|
+
end
|
|
1485
|
+
return definition
|
|
1486
|
+
end
|
|
1487
|
+
|
|
1488
|
+
function MetricUnit._assertNumber(n: any, name: string)
|
|
1489
|
+
if type(n) ~= "number" then
|
|
1490
|
+
error(string.format("MetricUnit.%s takes a number, not %s", name, typeof(n)), 4)
|
|
1491
|
+
end
|
|
1492
|
+
end
|
|
1493
|
+
|
|
1494
|
+
function MetricUnit._assertUnit(value: any, name: string)
|
|
1495
|
+
if not MetricUnit.isUnit(value) then
|
|
1496
|
+
error(string.format("MetricUnit.%s takes a MetricUnit, not %s", name, typeof(value)), 4)
|
|
1497
|
+
end
|
|
1498
|
+
end
|
|
1499
|
+
|
|
1500
|
+
function MetricUnit._assertDifference(value: MetricUnit, verb: string)
|
|
1501
|
+
if value._absolute then
|
|
1502
|
+
error(
|
|
1503
|
+
string.format(
|
|
1504
|
+
"MetricUnit: cannot %s an %s; subtract another to get a difference",
|
|
1505
|
+
verb,
|
|
1506
|
+
MetricUnitKinds.describeKind(value._dimensions, true)
|
|
1507
|
+
),
|
|
1508
|
+
4
|
|
1509
|
+
)
|
|
1510
|
+
end
|
|
1511
|
+
end
|
|
1512
|
+
|
|
1513
|
+
function MetricUnit._readUnit(self: MetricUnit, definition: MetricUnitDefinitions.UnitDefinition): number
|
|
1514
|
+
if not MetricUnitDimensionUtils.equal(self._dimensions, definition.dimensions) then
|
|
1515
|
+
error(
|
|
1516
|
+
string.format(
|
|
1517
|
+
"MetricUnit:In(%q) reads %s, not %s",
|
|
1518
|
+
definition.id,
|
|
1519
|
+
MetricUnitDimensionUtils.describe(definition.dimensions),
|
|
1520
|
+
MetricUnitKinds.describeKind(self._dimensions, self._absolute)
|
|
1521
|
+
),
|
|
1522
|
+
3
|
|
1523
|
+
)
|
|
1524
|
+
end
|
|
1525
|
+
local fromZero = if self._absolute then self._amount - (definition.zero or 0) else self._amount
|
|
1526
|
+
return fromZero / definition.factor
|
|
1527
|
+
end
|
|
1528
|
+
|
|
1529
|
+
function MetricUnit._snap(
|
|
1530
|
+
self: MetricUnit,
|
|
1531
|
+
step: UnitLike | MetricUnit,
|
|
1532
|
+
snap: (number) -> number,
|
|
1533
|
+
name: string
|
|
1534
|
+
): MetricUnit
|
|
1535
|
+
MetricUnit._assertUnit(self, name)
|
|
1536
|
+
if not MetricUnit.isUnit(step) then
|
|
1537
|
+
local definition = MetricUnit._getUnit(step :: UnitLike, self:GetDisplaySystem())
|
|
1538
|
+
if not definition then
|
|
1539
|
+
error(string.format("MetricUnit.%s knows no unit %q", name, tostring(step)), 3)
|
|
1540
|
+
end
|
|
1541
|
+
local count = snap(MetricUnit._readUnit(self, definition))
|
|
1542
|
+
local amount = if self._absolute
|
|
1543
|
+
then count * definition.factor + (definition.zero or 0)
|
|
1544
|
+
else count * definition.factor
|
|
1545
|
+
return MetricUnit._new(amount, self._dimensions, self._absolute, self._display)
|
|
1546
|
+
end
|
|
1547
|
+
|
|
1548
|
+
MetricUnit._assertUnit(step, name)
|
|
1549
|
+
local unit = step :: MetricUnit
|
|
1550
|
+
MetricUnit._assertDifference(unit, "round to")
|
|
1551
|
+
if not MetricUnitDimensionUtils.equal(unit._dimensions, self._dimensions) or unit._amount <= 0 then
|
|
1552
|
+
error(
|
|
1553
|
+
string.format(
|
|
1554
|
+
"MetricUnit.%s takes a positive step of %s, not %s",
|
|
1555
|
+
name,
|
|
1556
|
+
MetricUnitDimensionUtils.describe(self._dimensions),
|
|
1557
|
+
MetricUnitDimensionUtils.describe(unit._dimensions)
|
|
1558
|
+
),
|
|
1559
|
+
3
|
|
1560
|
+
)
|
|
1561
|
+
end
|
|
1562
|
+
local scaleZero = if self._absolute then MetricUnit._displayScaleZero(self) else 0
|
|
1563
|
+
return MetricUnit._new(
|
|
1564
|
+
snap((self._amount - scaleZero) / unit._amount) * unit._amount + scaleZero,
|
|
1565
|
+
self._dimensions,
|
|
1566
|
+
self._absolute,
|
|
1567
|
+
self._display
|
|
1568
|
+
)
|
|
1569
|
+
end
|
|
1570
|
+
|
|
1571
|
+
function MetricUnit._displayScaleZero(self: MetricUnit): number
|
|
1572
|
+
local display = self._display
|
|
1573
|
+
local pinned = if display then display.unit else nil
|
|
1574
|
+
if pinned then
|
|
1575
|
+
return pinned.zero or 0
|
|
1576
|
+
end
|
|
1577
|
+
local units = MetricUnitDisplayUnitUtils.getDisplayUnits(self._dimensions, self:GetDisplaySystem(), true)
|
|
1578
|
+
local scale = if units then units[1] else nil
|
|
1579
|
+
return if scale and scale.zero then scale.zero else 0
|
|
1580
|
+
end
|
|
1581
|
+
|
|
1582
|
+
function MetricUnit._dimensionsOf(value: MetricUnit | number): Dimensions
|
|
1583
|
+
if type(value) == "number" then
|
|
1584
|
+
return MetricUnitDimensionUtils.NONE
|
|
1585
|
+
end
|
|
1586
|
+
MetricUnit._assertUnit(value, "sameDimension")
|
|
1587
|
+
return value._dimensions
|
|
1588
|
+
end
|
|
1589
|
+
|
|
1590
|
+
function MetricUnit._scalable(value: MetricUnit | number, verb: string): (number, Dimensions)
|
|
1591
|
+
if type(value) == "number" then
|
|
1592
|
+
return value, MetricUnitDimensionUtils.NONE
|
|
1593
|
+
end
|
|
1594
|
+
MetricUnit._assertUnit(value, verb)
|
|
1595
|
+
MetricUnit._assertDifference(value, verb)
|
|
1596
|
+
return value._amount, value._dimensions
|
|
1597
|
+
end
|
|
1598
|
+
|
|
1599
|
+
function MetricUnit._pair(
|
|
1600
|
+
a: MetricUnit | number,
|
|
1601
|
+
b: MetricUnit | number,
|
|
1602
|
+
verb: string
|
|
1603
|
+
): (number, number, Dimensions, boolean, boolean)
|
|
1604
|
+
if type(a) == "number" and type(b) == "number" then
|
|
1605
|
+
return a, b, MetricUnitDimensionUtils.NONE, false, false
|
|
1606
|
+
elseif type(a) == "number" then
|
|
1607
|
+
MetricUnit._assertUnit(b, verb)
|
|
1608
|
+
local unit = b :: MetricUnit
|
|
1609
|
+
return a, unit._amount, MetricUnit._numberDimensions(a, unit, verb), false, unit._absolute
|
|
1610
|
+
elseif type(b) == "number" then
|
|
1611
|
+
MetricUnit._assertUnit(a, verb)
|
|
1612
|
+
return a._amount, b, MetricUnit._numberDimensions(b, a, verb), a._absolute, false
|
|
1613
|
+
end
|
|
1614
|
+
MetricUnit._assertUnit(a, verb)
|
|
1615
|
+
MetricUnit._assertUnit(b, verb)
|
|
1616
|
+
if not MetricUnitDimensionUtils.equal(a._dimensions, b._dimensions) then
|
|
1617
|
+
error(
|
|
1618
|
+
string.format(
|
|
1619
|
+
"MetricUnit: cannot %s %s and %s",
|
|
1620
|
+
verb,
|
|
1621
|
+
MetricUnitKinds.describeKind(a._dimensions, a._absolute),
|
|
1622
|
+
MetricUnitKinds.describeKind(b._dimensions, b._absolute)
|
|
1623
|
+
),
|
|
1624
|
+
3
|
|
1625
|
+
)
|
|
1626
|
+
end
|
|
1627
|
+
if verb == "compare" and a._absolute ~= b._absolute then
|
|
1628
|
+
error(
|
|
1629
|
+
string.format(
|
|
1630
|
+
"MetricUnit: cannot compare an %s and a %s",
|
|
1631
|
+
MetricUnitKinds.describeKind(a._dimensions, true),
|
|
1632
|
+
MetricUnitKinds.describeKind(a._dimensions, false)
|
|
1633
|
+
),
|
|
1634
|
+
3
|
|
1635
|
+
)
|
|
1636
|
+
end
|
|
1637
|
+
return a._amount, b._amount, a._dimensions, a._absolute, b._absolute
|
|
1638
|
+
end
|
|
1639
|
+
|
|
1640
|
+
function MetricUnit._numberDimensions(n: number, unit: MetricUnit, verb: string): Dimensions
|
|
1641
|
+
if n == 0 and not (verb == "compare" and unit._absolute) then
|
|
1642
|
+
return unit._dimensions
|
|
1643
|
+
elseif next(unit._dimensions) == nil and not unit._absolute then
|
|
1644
|
+
return unit._dimensions
|
|
1645
|
+
end
|
|
1646
|
+
error(
|
|
1647
|
+
string.format(
|
|
1648
|
+
"MetricUnit: cannot %s %s and the plain number %s",
|
|
1649
|
+
verb,
|
|
1650
|
+
MetricUnitKinds.describeKind(unit._dimensions, unit._absolute),
|
|
1651
|
+
tostring(n)
|
|
1652
|
+
),
|
|
1653
|
+
4
|
|
1654
|
+
)
|
|
1655
|
+
end
|
|
1656
|
+
|
|
1657
|
+
function MetricUnit._extreme(name: string, pick: (number, number) -> number, ...: MetricUnit | number): MetricUnit
|
|
1658
|
+
local count = select("#", ...)
|
|
1659
|
+
if count == 0 then
|
|
1660
|
+
error(string.format("MetricUnit.%s takes at least one value", name), 3)
|
|
1661
|
+
end
|
|
1662
|
+
local reference: MetricUnit | number = 0
|
|
1663
|
+
for index = 1, count do
|
|
1664
|
+
local value = select(index, ...)
|
|
1665
|
+
if value ~= 0 then
|
|
1666
|
+
reference = value
|
|
1667
|
+
break
|
|
1668
|
+
end
|
|
1669
|
+
end
|
|
1670
|
+
local bestAmount, _, dimensions, absolute = MetricUnit._pair(select(1, ...), reference, "compare")
|
|
1671
|
+
local display = MetricUnit._displayOf(select(1, ...))
|
|
1672
|
+
for index = 2, count do
|
|
1673
|
+
local value = select(index, ...)
|
|
1674
|
+
local amount = MetricUnit._pair(value, reference, "compare")
|
|
1675
|
+
bestAmount = pick(bestAmount, amount)
|
|
1676
|
+
display = MetricUnit._mergeDisplay(display, MetricUnit._displayOf(value))
|
|
1677
|
+
end
|
|
1678
|
+
return MetricUnit._new(bestAmount, dimensions, absolute, display)
|
|
1679
|
+
end
|
|
1680
|
+
|
|
1681
|
+
function MetricUnit._displayOf(value: any): DisplayPreference?
|
|
1682
|
+
return if MetricUnit.isUnit(value) then value._display else nil
|
|
1683
|
+
end
|
|
1684
|
+
|
|
1685
|
+
function MetricUnit._mergeDisplay(a: DisplayPreference?, b: DisplayPreference?): DisplayPreference?
|
|
1686
|
+
if a == nil then
|
|
1687
|
+
return b
|
|
1688
|
+
elseif b == nil or (a.system == b.system and a.unit == b.unit) then
|
|
1689
|
+
return a
|
|
1690
|
+
end
|
|
1691
|
+
return MetricUnit._sharedSystem(a, b)
|
|
1692
|
+
end
|
|
1693
|
+
|
|
1694
|
+
function MetricUnit._productDisplay(a: any, b: any): DisplayPreference?
|
|
1695
|
+
if type(b) == "number" then
|
|
1696
|
+
return MetricUnit._displayOf(a)
|
|
1697
|
+
elseif type(a) == "number" then
|
|
1698
|
+
return MetricUnit._displayOf(b)
|
|
1699
|
+
end
|
|
1700
|
+
return MetricUnit._newDimensionDisplay(a, b)
|
|
1701
|
+
end
|
|
1702
|
+
|
|
1703
|
+
function MetricUnit._quotientDisplay(a: any, b: any): DisplayPreference?
|
|
1704
|
+
if type(b) == "number" then
|
|
1705
|
+
return MetricUnit._displayOf(a)
|
|
1706
|
+
end
|
|
1707
|
+
return MetricUnit._newDimensionDisplay(a, b)
|
|
1708
|
+
end
|
|
1709
|
+
|
|
1710
|
+
function MetricUnit._newDimensionDisplay(a: any, b: any): DisplayPreference?
|
|
1711
|
+
local aDisplay = MetricUnit._systemOnly(MetricUnit._displayOf(a))
|
|
1712
|
+
local bDisplay = MetricUnit._systemOnly(MetricUnit._displayOf(b))
|
|
1713
|
+
if aDisplay == nil then
|
|
1714
|
+
return bDisplay
|
|
1715
|
+
elseif bDisplay == nil then
|
|
1716
|
+
return aDisplay
|
|
1717
|
+
end
|
|
1718
|
+
return MetricUnit._sharedSystem(aDisplay, bDisplay)
|
|
1719
|
+
end
|
|
1720
|
+
|
|
1721
|
+
function MetricUnit._sharedSystem(a: DisplayPreference, b: DisplayPreference): DisplayPreference?
|
|
1722
|
+
return if a.system ~= nil and a.system == b.system then table.freeze({ system = a.system }) else nil
|
|
1723
|
+
end
|
|
1724
|
+
|
|
1725
|
+
function MetricUnit._systemOnly(display: DisplayPreference?): DisplayPreference?
|
|
1726
|
+
local system = if display then display.system else nil
|
|
1727
|
+
return if system then table.freeze({ system = system }) else nil
|
|
1728
|
+
end
|
|
1729
|
+
|
|
1730
|
+
return MetricUnit
|