Skip to content

Commit

Permalink
Units part #1
Browse files Browse the repository at this point in the history
Part way through writing a meta-library for units that cost nothing at runtime but give unit-type safety at compile time.
  • Loading branch information
fallenleavesgocrunch committed Dec 17, 2024
1 parent 352a946 commit ad253db
Show file tree
Hide file tree
Showing 8 changed files with 309 additions and 30 deletions.
15 changes: 15 additions & 0 deletions core/numeric/core.primes.stz
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[ n seive-of-sundaram-into: primes | uint, &list of: uint -> ø |
limit = (n - 1) / 2

marked: (array of: bool length: limit + 1)
marked fill-with: false

1 to: limit do: [ i |
j = i
[(i + j + 2 * i * j) ≤ limit] while-true: [
marked[i + j + 2 * i * j] = true
j += 1]

n ≥ 2 then: [list add: 2]
1 to: limit do: [ i |
marked[i] then: [list add: 2 * i + 1] ] ]
12 changes: 0 additions & 12 deletions core/numeric/core.quantity.stz

This file was deleted.

18 changes: 0 additions & 18 deletions core/numeric/core.unit.stz

This file was deleted.

24 changes: 24 additions & 0 deletions core/unit/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Units and Quantities

It is smarter to program with units than without. What is a 10 after all when it should be 10 bytes?
We can create an array as either (array of: u8 length: 10) but how do we describe what we would create?
with a quantity: {quantity of: u8 | magnitude: 10}

SI units and their derived units exist as pre-made quantity classes which lets you write:
100 seconds // returns a {quantity of: second | magniature: 100}

You can describe the processing rate of your data with your own classes combined with SI units.
processing-speed-type = person / second
processing-speed-quantity = quantity of: processing-speed-type

When adding or substracting two quantities if their base unit doesn't match there is a compile time error.
This can avoid a lot of numerical type bugs. The unit system has no overhead on runtime, only compile time.

When describing bytes or bits you can define an array type without having to specify a second parameter
for length:
my-buffer = array of: 4 kilobytes


Every base and derived unit used is assigned a unique id that is a prime number allowing any combination
of unit to be combined through multiplication and division. meter^2 is meter id * meter id while byte/second
is byte id / second id. These id's are assigned lazily as they are needed in your code at compile time.
82 changes: 82 additions & 0 deletions core/unit/quantity.stz
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#package core

quantity = (
#traits: (number-t,)
@of: (unit: unit-t)
magnitude: number-t )


/* number-t */
[ a + b
| quantity of: a unit, quantity of: a unit -> quantity of: a unit
| {magnitude: a magnitude + b magnitude} ]

[ a + b
| quantity of: a unit base, quantity of: a unit base -> quantity of: a unit base
| {magnitude: a magnitude + b magnitude * b unit conversion / a unit conversion} ]

[ a - b
| quantity of: a unit, quantity of: a unit -> quantity of: a unit
| {magnitude: a magnitude - b magnitude} ]

[ a - b
| quantity of: a unit base, quantity of: a unit base -> quantity of: a unit base
| {magnitude: a magnitude - b magnitude * b unit conversion / a unit conversion} ]

[ a * b
| quantity, quantity -> quantity of: a unit * b unit
| {magnitude: a magnitude * b magnitude} ]

[ a * magnitude
| quantity, number-t -> quantity of: a unit
| {magnitude: a magnitude * magnitude} ]

[ magnitude * a
| number-t, quantity -> quantity of: a unit
| {magnitude: magnitude * a magnitude} ]

[ a / b
| quantity, quantity -> quantity of: a unit / b unit
| {magnitude: a magnitude / b magnitude} ]

[ a / magnitude
| quantity, number-t -> quantity of: a unit
| {magnitude: a magnitude / magnitude} ]

[ magnitude / a
| number-t, quantity -> quantity of: 1 / a unit
| {magnitude: magnitude * a magnitude} ]

[ a floor
| quantity -> a
| {magnitude: magnitude floor} ]

[ a ceiling
| quantity -> a
| {magnitude: magnitude ceiling} ]

[ a round
| quantity -> a
| {magnitude: magnitude round} ]

[ a truncate
| quantity -> a
| {magnitude: magnitude truncate} ]


/* comparable-t */
[ a < b
| quantity of: a unit base, quantity of: a unit base -> bool
| a magnitude < b magnitude ]

[ a == b
| quantity of: a unit base, quantity of: a unit base -> bool
| a magnitude == b magnitude ]


/* hashable-t */
[ a hash | quantity | magnitude hash + unit hash ]


/* convertable-t */
[ a as: new-unit | quantity -> quantity | unit convert: magnitude to: new-unit ]
100 changes: 100 additions & 0 deletions core/unit/unit-init.stz
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#package core

unitless = base-unit-t symbol: empty'empty'
primary-unit-library = {&unit-library | new init}
primary-unit-library unit-ids[unitless]: 1

meter = base-unit-t symbol: 'm'
second = base-unit-t symbol: 's'
gram = base-unit-t symbol: 'g' // the si unit is kilogram but that makes the init code harder
mole = base-unit-t symbol: 'mol'
kelvin = base-unit-t symbol: 'L'
ampere = base-unit-t symbol: 'A'
candela = base-unit-t symbol: 'cd'

// useful but not an SI unit
byte = base-unit-t symbol: 'B'

// controversial apparently

// si scaled versions of base units
( meter, second, gram, mole, kelvin, ampere, candela, ) do: [ unit | register-derived-si-prefixes: si-negative-prefixes ]
( meter, gram, mole, kelvin, ampere, candela, byte ) do: [ unit | register-derived-si-prefixes: si-positive-prefixes ]

// si approved derived units
hertz = derived-unit-t base: 1 / second symbol: 'Hz'
newton = derived-unit-t base: kilogram * meter * (second ** -2) symbol: 'N'
pascal = derived-unit-t base: newton * (meter ** -2) symbol: 'Pa'
joule = derived-unit-t base: newton * meter symbol: 'J'
watt = derived-unit-t base: joule / second symbol: 'W'
coulomb = derived-unit-t base: ampere * second symbol: 'C'
volt = derived-unit-t base: watt / ampere symbol: 'V'
farad = derived-unit-t base: coulomb / volt symbol: 'F'
ohm = derived-unit-t base: volt / ampere symbol: 'Ω'
siemens = derived-unit-t base: ampere / volt symbol: 'S'
webere = derived-unit-t base: volt * second symbol: 'Wb'
tesla = derived-unit-t base: weber * (meter ** -2) symbol: 'T'
henry = derived-unit-t base: weber / ampere symbol: 'H'
celcius = derived-unit-t base: kelvin symbol: 'ºC' offset: -273.15
radian = base-unit-t symbol: 'rad'
steradian = base-unit-t symbol: 'sr'
lumen = derived-unit-t base: candela * steradian symbol: 'lm'
lux = derived-unit-t base: lumen * (meter ** -2) symbol: 'lx'
becquerel = derived-unit-t base: 1 / second symbol: 'Bq'
gray = derived-unit-t base: joule / kilogram symbol: 'Gy'
katal = derived-unit-t base: mole / second symbol: 'kat'

( hertz, newton, pascal, joule, watt, coulomb, volt, farad, ohm, siemens, weber,
tesla, henry, celcius, radian, steradian, lumen, lux, becquerl, gray, katal, ) do: [ unit |
register-derived-si-prefixes: si-negative-prefixes
register-derived-si-prefixes: si-positive-prefixes ]

// derived distance
inch = derived-unit-t base: meter symbol: 'in' scale: 254 / 10_000
foot = derived-unit-t base: meter symbol: "ft" scale: 3_048 / 10_000
yard = derived-unit-t base: meter symbol: 'yd' scale: 9_144 / 10_000
mile = derived-unit-t base: meter symbol: 'mi' scale: 1_609_344 / 1_000
light-year = derived-unit-t base: meter symbol: 'lightyear' scale: 9_460_500_000_000_000
parsec = derived-unit-t base: meter symbol: 'parsec' scale: 30_856_770_000_000_000

// derived time
minute = derived-unit-t base: second symbol: 'min' scale: 60
hour = derived-unit-t base: second symbol: 'hr' scale: 60
day = derived-unit-t base: second symbol: 'd' scale: 24
week = derived-unit-t base: second symbol: 'wk' scale: 7
year = derived-unit-t base: second symbol: 'y' scale: 315_569_259_747 / 10_000
decade = derived-unit-t base: second symbol: 'decade' symbol: 'decade' scale: 315_569_259_747 / 1_000
century = derived-unit-t base: second symbol: 'century' symbol: 'century' scale: 315_569_259_747 / 100
millenium = derived-unit-t base: second symbol: 'millenium' symbol: 'millenium' scale: 315_569_259_747 / 10

// derived weight
tonne = derived-unit-t base: gram symbol: 't' scale: 1_000_000
stone = derived-unit-t base: gram symbol: 'st' scale: 6_350_29_318 / 100_000
pound = derived-unit-t base: gram symbol: 'lb' scale: 453_59_237 / 100_000
ounce = derived-unit-t base: gram symbol: 'oz' scale: 28_349_523_125 / 1_000_000_000
carat = derived-unit-t base: gram symbol: 'ct' scale: 2 / 10

// derived information
bit = derived-unit-t base: byte symbol: 'b' scale: 1 / 8
bit register-si-derived-units: si-positive-prefixes scale: 1 / 8

// derived angle
degree = derived-unit-t base: radian symbol: 'º' scale: 1_000_000_000_000 / 57_295_779_513_082
arc-minute = derived-unit-t base: radian symbol: "'" scale: 1000000000000 / 57295779513082 / 60
arc-second = derived-unit-t base: radian symbol: '"' scale: 1000000000000 / 57295779513082 / 3600

// derived areas
meter-squared = meter * meter
acre = derived-unit-t base: meter-squared symbol: 'ac' scale: 40_468_564_224 / 10_000_000
hectare = derived-unit-t base: meter-squared symbol: 'ha' scale: 10_000

// derived powers
horse-power = derived-unit-t base: watt base symbol: 'hp' scale: 7457 / 10

// derived pressures
atmosphere = derived-unit-t base: pascal base symbol: 'atm' scale: 101_325
bar = derived-unit-t base: pascal base symbol: 'bar' scale: 100_000
psi = derived-unit-t base: pascal base symbol: 'psi' scale: 6_894_757 / 1_000

// derived volumes
litre = derived-unit-t base: m ** -3 symbol: 'L' scale: 1 / 1_000
84 changes: 84 additions & 0 deletions core/unit/unit-library.stz
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#package core

unit-library = (
next-id: uint = 2
ids: ( class, uint | ) )

[ library unit-id-for: unit | &!unit-library, class -> fraction |
^ids[unit] else [
id = next-id
next-id = next-id next-prime
ids[unit]: id ] ]

#scope private
[ library unit-suffix-for: unit | &unit-library, class -> string |
^unit name ]

[ library unit-suffix-for: unit | &unit-library, base-unit-t -> string |
^unit class symbol ]

[ library unit-suffix-for: unit | &unit-library, derived-unit-t -> string |
^unit class symbol ]

[ library unit-scaling-for: unit | &unit-library, class -> int |
^1 ]

[ library unit-scaling-for: unit | &unit-library, derived-unit-t -> int |
^unit class scale ]

[ library unit-base-for: unit | &unit-library, class -> base-unit-t |
^unit ]

[ library unit-base-for: unit | &unit-library, derived-unit-t -> base-unit-t |
^unit base ]


#scope package

si-unit-prefix = (prefix: string, symbol: string, scale: int)

positive-prefixes = ( si-unit-prefix |
{prefix: "quetta", symbol: "Q", base: 10 ** 30},
{prefix: "ronna", symbol: "R", base: 10 ** 27},
{prefix: "yotta", symbol: "Y", base: 10 ** 24},
{prefix: "zetta", symbol: "Z", base: 10 ** 21},
{prefix: "exa", symbol: "E", base: 10 ** 18},
{prefix: "peta", symbol: "P", base: 10 ** 15},
{prefix: "tera", symbol: "T", base: 10 ** 12},
{prefix: "giga", symbol: "G", base: 10 ** 9},
{prefix: "mega", symbol: "M", base: 10 ** 6},
{prefix: "kilo", symbol: "K", base: 10 ** 3},
{prefix: "hecto", symbol: "h", base: 10 ** 2},
{prefix: "deca", sybmol: "da", base: 10})

negative-prefixes = ( si-unit-prefix |
{prefix: "deci", symbol: "d", base: -10},
{prefix: "centi", symbol: "c", base: 10 ** -2},
{prefix: "milli", symbol: "m", base: 10 ** -3},
{prefix: "micro", symbol: "µ", base: 10 ** -6},
{prefix: "nano", symbol: "n", base: 10 ** -9},
{prefix: "pico", symbol: "p", base: 10 ** -12},
{prefix: "femto", symbol: "f", base: 10 ** -15},
{prefix: "atto", symbol: "a", base: 10 ** -18},
{prefix: "zepto", symbol: "z", base: 10 ** -21},
{prefix: "yocto", symbol: "y", base: 10 ** -24},
{prefix: "ronto", symbol: "r", base: 10 ** -27},
{prefix: "quecto", symbol: "q", base: 10 ** -30})

si-prefixes = positive-prefixes + negative-prefixes

[ base-unit register-derived-unit: unit-name symbol: unit-symbol | unit-t, string, string, int |
base-unit register-derived-unit: unit-name symbol: unit-symbol scale: 1 ]

[ base-unit register-derived-unit: unit-name symbol: unit-symbol scale: scale | unit-t, string, string, int |
#context public[unit-name]: (derived-unit base: base-unit symbol: unit-symbol scale: scale) ] ]

[ base-unit register-derived-si-prefixes: prefixes | base-unit-t, &list of: si-unit-prefix |
base-unit register-derived-si-prefixes: prefixes scale: 1 ]

[ base-unit register-derived-si-prefixes: prefixes scale: scale | base-unit-t, &list of: si-unit-prefix, int |
base-unit-name = #context public key-for: base-unit
prefixes do: [ prefix |
unit-name = prefix name + base-unit-name
unit-symbol = prefix symbol + base-unit symbol
base-unit register-derived-unit: unit-name symbol: unit-symbol scale: prefix base * int ] ]
4 changes: 4 additions & 0 deletions core/unit/unit-t.stz
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#package core

base-unit-t = (@symbol: string,)
derived-unit-t = (@base: base-unit-t, @scale: int, @symbol: string)

0 comments on commit ad253db

Please sign in to comment.