Standard behaviours#

forallpeople has chosen a “convention over configuration” approach. While units environments are intended to be customizable for each individual person’s preference, certain behaviours have been designed as “standard” and are described below.

[2]:
import forallpeople as si
si.environment('default')

1. Conventional Arithmetic#

forallpeople defines the methods required for conventional arithmetic between Physical instances.

  • Addition and subtraction are possible between physical quantities of the same dimension.

  • Multiplication and division are possible between any physical quantities and result in new dimensions.

  • Exponentiation is possible with integers and floats

  • Floor division and modulo are currently not defined because of the ambiguity created with the other standard behaviours (see: Using Floor and Modulo, below)

[3]:
a = 500 * si.kg
b = 23 * si.kg
c = 300 * si.kg
display(a + b - c)
223.000 kg
[4]:
d = 9.81 * si.m / si.s**2
display(d)
9.810 m⋅s-2

2. Auto-scaling of base units and derived units#

[5]:
display(5000 * si.kg) # Auto-scaling to Mg
5.000 Mg
[6]:
display(4_000_000_000 / si.s) # Auto-scaling to GHz
4.000 GHz
[7]:
display(0.000000112 * si.m) # Auto-scaling to nm
112.000 nm

Undefined products of base units are not scaled

[8]:
display(40000 * si.A * si.kg) # No auto-scaling because kg*A is not in the environment
40000.000 kg⋅A

Instances of defined units (i.e. .factor attribute != 1) are not scaled.

[9]:
si.environment('us_customary')
display(40000 * si.lbf)
40000.000 lbf

3. Three-decimals precision#

Due to auto-scaling, Physical instances only show three units of precision. However, this can be changed at the instance-level by using the built-in round() function and at the module-level by setting the precision in the environment (see Environments).

[10]:
si.environment('default')
[11]:
a = 4230.2349329 * si.kg
display(a)
b = round(a, 6)
display(b)
4.230 Mg
4.230235 Mg

If an instance is not eligible for auto-scaling (see above) then the three decimal places of precision can lead to unexpected apparent results.

[12]:
c = 1 / (4000000 * si.kg * si.A)
display(c) # 0.000???
display(round(c, 9))
display("{:.3e}".format(c))
0.000 kg-1⋅A-1
0.000000250 kg-1⋅A-1
'2.500e-07 kg⁻¹·A⁻¹'

4. Unit cancellation#

When an instance has its units “cancelled” out by multiplication or division, then the resulting value is a float.

[13]:
length = 4.2 * si.m
spacing = 0.25 * si.m
num_of_spaces = length / spacing
display(num_of_spaces)
16.8
[14]:
a = 40 * si.Hz
b = 3 * si.s
cycles = a * b
display(cycles)
120.0

5. Conversion to float and int#

Due to auto-scaling the display value is often different than the underlying .value attribute, which represents the instance’s value in SI base units.

Using float() will return the value of the instance in its scaled value.

Similarily, when using an environment where US customary units are defined, using float() will return the factored value.

In other words, the intention of the behaviour of float() is WYSIWYG (what you see is what you get). This enables other customized behaviours which may be desirable even if they are mathematically inconsistent (see Dimensionsally-inconsistent Calculations).

To get the unscaled and unfactored value, use .value instead.

[15]:
a = 5523400 * si.N
display(a)
display(a.value)
5.523 MN
5523400
[16]:
b = float(a) # The value will retain its scaling in MN
display(b)

c = int(b)
display(c)
5.5234
5

6. Using math functions#

The functions in the built-in math module, generally, accept a float and return a float. When Physical instances are passed to the math functions, float() is first called on the Physical which, naturally, converts it to a float (see above). This obliterates any unit information about the physical instance.

[17]:
import math
[18]:
length = 4200 * si.m # Auto-scales to 4.2 km
display(math.sqrt(length)) # Returns sqrt of 4.2
2.04939015319192

To add unit information back to the resulting quantity, use the .split() method to perform the calculation in a mathematically consistent way.

[19]:
value, unit = length.split()
display(math.sqrt(value) * unit)
64.807 m

See Dimensionally-inconsistent Calculations for additional information on managing similar situations.

7. Using Floor and Modulo#

Currently, // and % are not implemented on Physical objects because a decision has not been made on how they should behave. i.e. Should the // and % operators work on the underlying SI .value attribute or on the displayed and scaled unit?

If you have an opinion on this, please voice it on a GitHub issue.