Skip to content

Number With Unit

Info

New in Atfinity 17.

A Number (with unit) field holds an amount together with the unit it is measured in, so one salary field can carry 5000, CHF and month.

Reading The Parts

The field behaves like a dictionary holding three keys:

p is Person
p.salary["decimal_number"]      # the amount, 5000
p.salary["unit"]                # the unit, "CHF"
p.salary["item"]                # the "per" part, "month"
p.salary["currency"]            # an unknown key, so nothing

Reading the field itself gives you the amount, so p.salary > 5000 compares the number.

Number With Unit Operations

Arithmetic keeps the unit and the item, taking them from the left value, or from the right one when the left is a plain number:

p.salary * 2                    # 10000 CHF per month

Nothing converts between units. An operation on two values in different units keeps the unit of the left one, and a comparison reads the amounts alone, so p.salary > q.salary is true whenever the left amount is larger, whichever units the two carry.

A list aggregate keeps the unit as well. SUM, AVG, MIN, MAX and SUM_PRODUCT, along with the _OF_ANY variants of the first four, all take the unit and the item from the first value that carries one, and CLAMP takes them from the first of its three arguments that carries one:

ps is all Person
AVG(ps.map(p => p.salary))      # 4000 CHF per month, for salaries of 5000 and 3000
MIN((p.salary, 3000))           # 3000 CHF per month, the unit survives the plain number winning
CLAMP(p.salary, 1000, 4000)     # 4000 CHF per month, for a salary of 5000

ROUND, FLOOR, CEIL and ABS keep the unit and the item of the value they are given:

ROUND(p.salary, 2)              # 5000.56 CHF per month, for a salary of 5000.555

FORMAT_NUMBER, COMB, SQRT, SIGN, TO_DECIMAL, TO_INT, TO_STRING, CONCAT and JOIN read the amount and give back a plain number or a string, so the unit does not survive them:

FORMAT_NUMBER(p.salary, 2)      # "5,000.00", without the unit
CONCAT('earns ', p.salary)      # "earns 5000", without the unit

SET, UNION, UNION_OF_ANY, INTERSECTION and SET_DIFFERENCE treat two values as the same one only when their amount, unit and item all match, so SET keeps both 1000 CHF and 1000 EUR. Mind the difference from a comparison: p.salary = q.salary is true for 1000 CHF and 1000 EUR because it reads the amounts alone, while these five keep the two apart.

Three functions work on the type itself:

  • NUMBER_WITH_UNIT(amount, unit, item) builds a value. The unit is required, the item is optional.
  • SAME_UNIT(a, b) is true when both values carry the same unit and the same item.
  • SAME_NUMBER_WITH_UNIT(a, b) is true when they carry the same amount as well.

Both are false as soon as one side is a plain number, because a plain number carries no unit to match.

Assigning One Part

Assign a single key to change one part and leave the other two alone:

p is Person
---
p.salary["unit"] := "CHF"

Assigning the field itself sets all three parts, and assigning a plain number to it keeps the unit and the item the field already has:

p.salary := 5000
  • decimal_number, unit and item are the only keys.
  • Assigning unit or item while the field has no value does nothing, because a unit without a number is not a value. Assign the whole field instead.
  • Assigning decimal_number while the field has no value uses the field's default unit and default unit item.
  • Assigning null to item clears it. decimal_number and unit cannot be cleared, because a value always needs both, so assign null to the whole field instead.
  • A Number (with unit) cannot be assigned to a Number or a Whole Number field, because those hold the amount alone. Assign TO_DECIMAL(p.salary) or p.salary["decimal_number"] instead.
  • A structure member works the same way: p.pets[0].weight["unit"] := "kg".
  • The key can be a variable holding one of the three names (new in Atfinity 17).