To keep a small database indicating the physical dimensions of numerical values, and how they combine: for instance, allowing us to specify that a length times a length is an area.
- §11. Prior kinds
- §12. Multiplication lists
- §16. Unary operations
- §17. Euclid's algorithm
- §19. Unit sequences
- §28. Performing derivations
- §29. Classifying the units
- §30. Logging
- §31. Arithmetic on kinds
§1. We sort quasinumerical kinds1 into three: fundamental units, derived units with dimensions, and dimensionless units. In the default setup provided by a work of IF generated by Inform, there is one fundamental unit ("time"), there are two dimensionless units ("number" and "real number") and no derived units. Dimension checking does very little in this minimal environment, though it will, for example, forbid an attempt to multiply 10 PM by 9:15 AM, or indeed to multiply kinds which aren't numberical at all, such as a text by a sound effect.2
Further fundamental units are created every time source text like this is read:
Mass is a kind of value. 1kg specifies a mass.
Mass will then be considered fundamental until the source text says otherwise.
It would no doubt be cool to decide what is fundamental and what is derived by
applying Buckingham's
A mass times an acceleration specifies a force.
Inform chooses one of the three units — say, force — and derives that from the others.
1 Basically, any kind on which arithmetic can be done. To test this, call Kinds::Behaviour::is_quasinumerical. ↩
2 Occasionally we have thought about allowing text to be duplicated by multiplication — 2 times "zig" would be "zigzig", and maybe similarly for lists — but it always seemed more likely to be used by mistake than intentionally. ↩
§2. Multiplication rules are stored in a linked list associated with the left
operand; so that the rule
typedef struct dimensional_rules { struct dimensional_rule *multiplications; } dimensional_rules; typedef struct dimensional_rule { struct wording name; struct kind *right; struct kind *outcome; struct dimensional_rule *next; } dimensional_rule;
- The structure dimensional_rules is private to this section.
- The structure dimensional_rule is accessed in 2/tlok and here.
§3. The derivation process can be seen in action by feeding Inform definitions of the SI units (see the test case SIUnits-G) and looking at the output of:
Test dimensions (internal) with --.
(The dash is meaningless — this is a test with no input.) In the output, we see that
Base units: time, length, mass, elapsed time, electric current, temperature, luminosity Derived units: frequency = (elapsed time)-1 force = (length).(mass).(elapsed time)-2 energy = (length)2.(mass).(elapsed time)-2 pressure = (length)-1.(mass).(elapsed time)-2 power = (length)2.(mass).(elapsed time)-3 electric charge = (elapsed time).(electric current) voltage = (length)2.(mass).(elapsed time)-3.(electric current)-1
...and so on. Those expressions on the right hand sides are "derived units",
where the numbers are powers, so that negative numbers mean division.
It's easy to see why we want to give names and notations for some of
these derived units — imagine going into a cycle shop and asking for a
§4. A "dimensionless" quantity is one which is just a number, and is not a
physical measurement as such. In an equation like
Angle is a kind of value. 1 rad specifies an angle. Length times angle specifies a length.
Inform is not quite so careful about distinguishing dimensionless quantities
as some physicists might be. The official SI units distinguish angle, measured
in radians, and solid angle, in steradians, writing them as having units
Solid angle is a kind of value. 1 srad specifies an solid angle. Area times solid angle specifies an area.
then Inform treats angle and solid angle as having the same multiplicative properties — but it still allows variables to have either one as a kind of value, and prints them differently.
§5. In the process of calculations, we often need to create other and nameless
units as partial answers of calculations. Consider the kinetic energy equation
(length)2.(elapsed time)-2
but that's a unit which isn't useful for much, and doesn't have any everyday name. Inform creates what are called "intermediate kinds" like this in order to be able to represent the kinds of intermediate values which turn up in calculation. They use the special CON_INTERMEDIATE construction, they are nameless, and the user isn't allowed to store the results permanently. (They can't be the kind of a global variable, a table column, and so on.) If the user wants to deal with such values on a long-term basis, he must give them a name, like this:
Funkiness is a kind of value. 1 Claude is a funkiness. A velocity times a velocity specifies a funkiness.
§6. Expressions like
Every physically different derived unit has a unique and distinct sequence.
This is only true because a unit sequence is forbidden to contain derived
units. For instance, specific heat capacity looks as if it is written with
two different units in physics:
The case
typedef struct unit_pair { struct kind *fund_unit; and this really must be a fundamental kind int power; a non-zero integer } unit_pair;
- The structure unit_pair is private to this section.
§7. The following is a hard limit, but really not a problematic one. The
entire SI system has only 7 fundamental units, and the only named scientific
unit I've seen which has even 5 terms in its derivation is molar entropy, a
less than everyday chemical measure
(
define MAX_BASE_UNITS_IN_SEQUENCE 16
typedef struct unit_sequence { int no_unit_pairs; in range 0 to MAX_BASE_UNITS_IN_SEQUENCE struct unit_pair unit_pairs[MAX_BASE_UNITS_IN_SEQUENCE]; int scaling_factor; see discussion of scaling below } unit_sequence;
- The structure unit_sequence is private to this section.
§8. Manipulating units like
For instance, given seconds, Watts and Joules,
If the derivations were ever more complex than
§9. But enough abstraction: time for some arithmetic. Inform performs checking whenever values from two different kinds are combined by any of the arithmetic operations, numbered as follows. The numbers must not be changed without amending the definitions of "plus" and so on in the Basic Inform extension.
enum PLUS_OPERATION from 0 addition enum MINUS_OPERATION subtraction enum TIMES_OPERATION multiplication enum DIVIDE_OPERATION division enum REMAINDER_OPERATION remainder after division enum APPROXIMATE_OPERATION "X to the nearest Y" enum ROOT_OPERATION square root — a unary operation enum REALROOT_OPERATION real-valued square root — a unary operation enum CUBEROOT_OPERATION cube root — similarly unary enum EQUALS_OPERATION set equal — used only in equations enum POWER_OPERATION raise to integer power — used only in equations enum NEGATE_OPERATION unary minus — used only in equations
§10. The following is associated with "total...", as in "the total weight of things on the table", but for dimensional purposes we ignore it.
enum TOTAL_OPERATION not really one of the above
§11. Prior kinds. It turns out to be convenient to have a definition ordering of fundamental kinds,
which is completely unlike the
int Kinds::Dimensions::kind_prior(kind *A, kind *B) { if (A == NULL) { if (B == NULL) return FALSE; return TRUE; } if (B == NULL) { if (A == NULL) return FALSE; return FALSE; } if (Kinds::get_construct(A)->allocation_id < Kinds::get_construct(B)->allocation_id) return TRUE; return FALSE; }
§12. Multiplication lists. The linked lists of multiplication rules begin empty for every kind:
void Kinds::Dimensions::dim_initialise(dimensional_rules *dimrs) { dimrs->multiplications = NULL; }
§13. And this adds a new one to the relevant list:
void Kinds::Dimensions::record_multiplication_rule(kind *left, kind *right, kind *outcome) { dimensional_rules *dimrs = Kinds::Behaviour::get_dim_rules(left); dimensional_rule *dimr; for (dimr = dimrs->multiplications; dimr; dimr = dimr->next) if (dimr->right == right) { KindsModule::problem_handler(DimensionRedundant_KINDERROR, NULL, NULL, NULL, NULL); return; } dimensional_rule *dimr_new = CREATE(dimensional_rule); dimr_new->right = right; dimr_new->outcome = outcome; if (current_sentence) dimr_new->name = Node::get_text(current_sentence); else dimr_new->name = EMPTY_WORDING; dimr_new->next = dimrs->multiplications; dimrs->multiplications = dimr_new; }
§14. The following loop-header macro iterates through the possible triples
define LOOP_OVER_MULTIPLICATIONS(left_operand, right_operand, outcome_type, wn) dimensional_rules *dimrs; dimensional_rule *dimr; LOOP_OVER_BASE_KINDS(left_operand) for (dimrs = Kinds::Behaviour::get_dim_rules(left_operand), dimr = (dimrs)?(dimrs->multiplications):NULL, wn = (dimr)?(Wordings::first_wn(dimr->name)):-1, right_operand = (dimr)?(dimr->right):0, outcome_type = (dimr)?(dimr->outcome):0; dimr; dimr = dimr->next, wn = (dimr)?(Wordings::first_wn(dimr->name)):-1, right_operand = (dimr)?(dimr->right):0, outcome_type = (dimr)?(dimr->outcome):0)
§15. And this is where the user asks for a multiplication to come out in a particular way:
void Kinds::Dimensions::dim_set_multiplication(kind *left, kind *right, kind *outcome) { if ((Kinds::is_proper_constructor(left)) || (Kinds::is_proper_constructor(right)) || (Kinds::is_proper_constructor(outcome))) { KindsModule::problem_handler(DimensionNotBaseKOV_KINDERROR, NULL, NULL, NULL, NULL); return; } if ((Kinds::Behaviour::is_quasinumerical(left) == FALSE) || (Kinds::Behaviour::is_quasinumerical(right) == FALSE) || (Kinds::Behaviour::is_quasinumerical(outcome) == FALSE)) { KindsModule::problem_handler(NonDimensional_KINDERROR, NULL, NULL, NULL, NULL); return; } Kinds::Dimensions::record_multiplication_rule(left, right, outcome); if ((Kinds::eq(left, outcome)) && (Kinds::eq(right, K_number))) return; if ((Kinds::eq(right, outcome)) && (Kinds::eq(left, K_number))) return; Kinds::Dimensions::make_unit_derivation(left, right, outcome); } void Kinds::Dimensions::dim_set_subtraction(kind *left, kind *right, kind *outcome) { if ((Kinds::is_proper_constructor(left)) || (Kinds::is_proper_constructor(right)) || (Kinds::is_proper_constructor(outcome))) { KindsModule::problem_handler(DimensionNotBaseKOV_KINDERROR, NULL, NULL, NULL, NULL); return; } if ((Kinds::Behaviour::is_quasinumerical(left) == FALSE) || (Kinds::Behaviour::is_quasinumerical(right) == FALSE) || (Kinds::Behaviour::is_quasinumerical(outcome) == FALSE)) { KindsModule::problem_handler(NonDimensional_KINDERROR, NULL, NULL, NULL, NULL); return; } if (Kinds::ne(left, right)) KindsModule::problem_handler(ImproperSubtraction_KINDERROR, NULL, NULL, NULL, NULL); if (Kinds::Dimensions::dimensionless(left) == FALSE) left->construct->dimensionless = TRUE; left->construct->relative_kind = outcome->construct; }
§16. Unary operations. All we need to know is which ones are unary, in fact, and:
int Kinds::Dimensions::arithmetic_op_is_unary(int op) { switch (op) { case CUBEROOT_OPERATION: case ROOT_OPERATION: case REALROOT_OPERATION: case NEGATE_OPERATION: return TRUE; } return FALSE; }
§17. Euclid's algorithm. In my entire life, I believe this is the only time I have ever actually
used Euclid's algorithm for the GCD of two natural numbers. I've never
quite understood why textbooks take this as somehow the typical algorithm.
My maths students always find it a little oblique, despite the almost
trivial proof that it works. It typically takes a shade under
int Kinds::Dimensions::gcd(int m, int n) { if ((m<1) || (n<1)) internal_error("applied gcd outside natural numbers"); while (TRUE) { int rem = m%n; if (rem == 0) return n; m = n; n = rem; } }
§18. The sequence of operation here is to reduce the risk of integer overflows when multiplying m by n.
int Kinds::Dimensions::lcm(int m, int n) { return (m/Kinds::Dimensions::gcd(m, n))*n; }
§19. Unit sequences. Given a fundamental type
unit_sequence Kinds::Dimensions::fundamental_unit_sequence(kind *B) { unit_sequence us; if (B == NULL) { us.no_unit_pairs = 0; us.unit_pairs[0].fund_unit = NULL; us.unit_pairs[0].power = 0; redundant, but appeases compilers } else { us.no_unit_pairs = 1; us.unit_pairs[0].fund_unit = B; us.unit_pairs[0].power = 1; } return us; }
§20. As noted above, two units represent dimensionally equivalent physical quantities if and only if they are identical, which makes comparison easy:
int Kinds::Dimensions::compare_unit_sequences(unit_sequence *ik1, unit_sequence *ik2) { int i; if (ik1 == ik2) return TRUE; if ((ik1 == NULL) || (ik2 == NULL)) return FALSE; if (ik1->no_unit_pairs != ik2->no_unit_pairs) return FALSE; for (i=0; i<ik1->no_unit_pairs; i++) if ((Kinds::eq(ik1->unit_pairs[i].fund_unit, ik2->unit_pairs[i].fund_unit) == FALSE) || (ik1->unit_pairs[i].power != ik2->unit_pairs[i].power)) return FALSE; return TRUE; }
§21. We now have three fundamental operations we can perform on unit sequences.
First, we can multiply them: that is, we store in result the unit
sequence representing
So the case
Our method relies on noting that
On each iteration of the loop the variables i1 and i2 are our current read positions in each sequence, while we are currently looking at the unit pairs (t1, m1) and (t2, m2). The following symmetrical algorithm holds on to each pair until the one from the other sequence has had a chance to catch up with it, because we always deal with the pair with the numerically lower t first. This also proves that the results sequence comes out in numerical order.
void Kinds::Dimensions::multiply_unit_sequences(unit_sequence *us1, int s1, unit_sequence *us2, int s2, unit_sequence *result) { if ((result == us1) || (result == us2)) internal_error("result must be different structure"); result->no_unit_pairs = 0; int i1 = 0, i2 = 0; read position in sequences 1, 2 kind *t1 = NULL; int p1 = 0; start with no current term from sequence 1 kind *t2 = NULL; int p2 = 0; start with no current term from sequence 2 while (TRUE) { If we have no current term from sequence 1, and it hasn't run out, fetch a new one21.1; If we have no current term from sequence 2, and it hasn't run out, fetch a new one21.2; if (Kinds::eq(t1, t2)) { if (t1 == NULL) break; both sequences have now run out Both terms refer to the same fundamental unit, so combine these into the result21.3; } else { Different fundamental units, so copy the numerically lower one into the result21.4; } } LOGIF(KIND_CREATIONS, "Multiplication: $Q * $Q = $Q\n", us1, us2, result); }
§21.1. If we have no current term from sequence 1, and it hasn't run out, fetch a new one21.1 =
if ((t1 == NULL) && (us1) && (i1 < us1->no_unit_pairs)) { t1 = us1->unit_pairs[i1].fund_unit; p1 = us1->unit_pairs[i1].power; i1++; }
- This code is used in §21.
§21.2. If we have no current term from sequence 2, and it hasn't run out, fetch a new one21.2 =
if ((t2 == NULL) && (us2) && (i2 < us2->no_unit_pairs)) { t2 = us2->unit_pairs[i2].fund_unit; p2 = us2->unit_pairs[i2].power; i2++; }
- This code is used in §21.
§21.3. So here the head of one sequence is
Both terms refer to the same fundamental unit, so combine these into the result21.3 =
int p = p1*s1 + p2*s2; combined power of t1 \(=\) t2 if (p != 0) { if (result->no_unit_pairs == MAX_BASE_UNITS_IN_SEQUENCE) Trip a unit sequence overflow21.3.1; result->unit_pairs[result->no_unit_pairs].fund_unit = t1; result->unit_pairs[result->no_unit_pairs++].power = p; } t1 = NULL; t2 = NULL; dispose of both terms as dealt with
- This code is used in §21.
§21.4. Otherwise we copy. By copying the numerically lower term, we can be sure that it will never occur again in either sequence. So we can copy it straight into the results.
The code is slightly warped by the fact that UNKNOWN_NT, representing the end of the sequence, happens to be numerically lower than all the valid kinds. We don't want to make use of facts like that, so we write code to deal with UNKNOWN_NT explicitly.
Different fundamental units, so copy the numerically lower one into the result21.4 =
if ((t2 == NULL) || ((t1 != NULL) && (Kinds::Dimensions::kind_prior(t1, t2)))) { if (result->no_unit_pairs == MAX_BASE_UNITS_IN_SEQUENCE) Trip a unit sequence overflow21.3.1; result->unit_pairs[result->no_unit_pairs].fund_unit = t1; result->unit_pairs[result->no_unit_pairs++].power = p1*s1; t1 = NULL; dispose of the head of sequence 1 as dealt with } else if ((t1 == NULL) || ((t2 != NULL) && (Kinds::Dimensions::kind_prior(t2, t1)))) { if (result->no_unit_pairs == MAX_BASE_UNITS_IN_SEQUENCE) Trip a unit sequence overflow21.3.1; result->unit_pairs[result->no_unit_pairs].fund_unit = t2; result->unit_pairs[result->no_unit_pairs++].power = p2*s2; t2 = NULL; dispose of the head of sequence 1 as dealt with } else internal_error("unit pairs disarrayed");
- This code is used in §21.
§21.3.1. For reasons explained above, this is really never going to happen by accident, but we'll be careful:
Trip a unit sequence overflow21.3.1 =
KindsModule::problem_handler(UnitSequenceOverflow_KINDERROR, NULL, NULL, NULL, NULL); return;
§22. The second operation is taking roots.
Surprisingly, perhaps, it's much easier to compute
int Kinds::Dimensions::root_unit_sequence(unit_sequence *us, int pow, unit_sequence *result) { if (us == NULL) return FALSE; *result = *us; for (int i=0; i<result->no_unit_pairs; i++) { if ((result->unit_pairs[i].power) % pow != 0) return FALSE; result->unit_pairs[i].power = (result->unit_pairs[i].power)/pow; } return TRUE; }
§23. More generally, we can raise a unit sequence to the rational power
kind *Kinds::Dimensions::to_rational_power(kind *F, int n, int m) { if ((n < 1) || (m < 1)) internal_error("bad rational power"); if (Kinds::Dimensions::dimensionless(F)) return F; kind *K = K_number; int op = TIMES_OPERATION; if (n < 0) { n = -n; op = DIVIDE_OPERATION; } while (n > 0) { K = Kinds::Dimensions::arithmetic_on_kinds(K, F, op); n--; } if (m == 1) return K; unit_sequence result; unit_sequence *operand = Kinds::Behaviour::get_dimensional_form(K); if (Kinds::Dimensions::root_unit_sequence(operand, m, &result) == FALSE) return NULL; Identify the result as a known kind, if possible23.1; return NULL; }
§24. The final operation on unit sequences is substitution. Given a fundamental type
We simply search for
void Kinds::Dimensions::dim_substitute(unit_sequence *existing, kind *fundamental, unit_sequence *derived) { int i, j, p = 0, found = FALSE; if (existing == NULL) return; for (i=0; i<existing->no_unit_pairs; i++) if (Kinds::eq(existing->unit_pairs[i].fund_unit, fundamental)) { p = existing->unit_pairs[i].power; found = TRUE; Remove the B term from the existing sequence24.1; } if (found) Multiply the existing sequence by a suitable power of B's derivation24.2; }
§24.1. We shuffle the remaining terms in the sequence down by one, overwriting B:
Remove the B term from the existing sequence24.1 =
for (j=i; j<existing->no_unit_pairs-1; j++) existing->unit_pairs[j] = existing->unit_pairs[j+1]; existing->no_unit_pairs--;
- This code is used in §24.
Multiply the existing sequence by a suitable power of B's derivation24.2 =
unit_sequence result; Kinds::Dimensions::multiply_unit_sequences(existing, 1, derived, p, &result); *existing = result;
- This code is used in §24.
§25. For reasons which will be explained in Scaled Arithmetic Values, a unit sequence also has a scale factor associated with it:
int Kinds::Dimensions::us_get_scaling_factor(unit_sequence *us) { if (us == NULL) return 1; return us->scaling_factor; }
§26. That just leaves, as usual, indexing...
void Kinds::Dimensions::index_unit_sequence(OUTPUT_STREAM, unit_sequence *deriv, int briefly) { if (deriv == NULL) return; if (deriv->no_unit_pairs == 0) { WRITE("dimensionless"); return; } for (int j=0; j<deriv->no_unit_pairs; j++) { kind *fundamental = deriv->unit_pairs[j].fund_unit; int power = deriv->unit_pairs[j].power; if (briefly) { if (j>0) WRITE("."); WRITE("("); #ifdef PIPELINE_MODULE Kinds::Textual::write_as_HTML(OUT, fundamental); #else Kinds::Textual::write(OUT, fundamental); #endif WRITE(")"); if (power != 1) WRITE("<sup>%d</sup>", power); } else { if (j>0) WRITE(" times "); if (power < 0) { power = -power; WRITE("reciprocal of "); } wording W = Kinds::Behaviour::get_name(fundamental, FALSE); WRITE("%W", W); switch (power) { case 1: break; case 2: WRITE(" squared"); break; case 3: WRITE(" cubed"); break; default: WRITE(" to the power %d", power); break; } } } }
void Kinds::Dimensions::logger(OUTPUT_STREAM, void *vUS) { unit_sequence *deriv = (unit_sequence *) vUS; if (deriv == NULL) { WRITE("<null-us>"); return; } if (deriv->no_unit_pairs == 0) { WRITE("dimensionless"); return; } for (int j=0; j<deriv->no_unit_pairs; j++) { if (j>0) WRITE("."); WRITE("(%u)", deriv->unit_pairs[j].fund_unit); if (deriv->unit_pairs[j].power != 1) WRITE("%d", deriv->unit_pairs[j].power); } }
§28. Performing derivations. The following is called when the user specifies that
If two or more are fundamental units, we have a choice. That is, suppose we have created three kinds already: mass, acceleration, force. Then we read:
Mass times acceleration specifies a force.
We could make this true in any of three ways: keep M and A as fundamental units and derive F from them, keep A and F as fundamental units and derive M from those, or keep M and F while deriving A. Inform always chooses the most recently created unit as the one to derive, on the grounds that the source text has probably set things out with what the user thinks are the most fundamental units first.
void Kinds::Dimensions::make_unit_derivation(kind *left, kind *right, kind *outcome) { kind *terms[3]; terms[0] = left; terms[1] = right; terms[2] = outcome; int newest_term = -1; Find which (if any) of the three units is the newest-made fundamental unit28.1; if (newest_term >= 0) { unit_sequence *derivation = NULL; Derive the newest one by rearranging the equation in terms of the other two28.2; Substitute this new derivation to eliminate this fundamental unit from other sequences28.3; } else Check this derivation to make sure it is redundant, not contradictory28.4; }
§28.1. Data type IDs are allocated in creation order, so "newest" means largest ID.
Find which (if any) of the three units is the newest-made fundamental unit28.1 =
int i; kind *max = NULL; for (i=0; i<3; i++) if ((Kinds::Dimensions::kind_prior(max, terms[i])) && (Kinds::Behaviour::test_if_derived(terms[i]) == FALSE)) { newest_term = i; max = terms[i]; }
- This code is used in §28.
§28.2. We need to ensure that the user's multiplication rule is henceforth true, and we do that by fixing the newest unit to make it so.
Derive the newest one by rearranging the equation in terms of the other two28.2 =
unit_sequence *kx = NULL, *ky = NULL; int sx = 0, sy = 0; switch (newest_term) { case 0: here L is newest and we derive L = O/R kx = Kinds::Behaviour::get_dimensional_form(terms[1]); sx = -1; ky = Kinds::Behaviour::get_dimensional_form(terms[2]); sy = 1; break; case 1: here R is newest and we derive R = O/L kx = Kinds::Behaviour::get_dimensional_form(terms[0]); sx = -1; ky = Kinds::Behaviour::get_dimensional_form(terms[2]); sy = 1; break; case 2: here O is newest and we derive O = LR kx = Kinds::Behaviour::get_dimensional_form(terms[0]); sx = 1; ky = Kinds::Behaviour::get_dimensional_form(terms[1]); sy = 1; break; } derivation = Kinds::Behaviour::get_dimensional_form(terms[newest_term]); unit_sequence result; Kinds::Dimensions::multiply_unit_sequences(kx, sx, ky, sy, &result); *derivation = result; Kinds::Behaviour::now_derived(terms[newest_term]);
- This code is used in §28.
§28.3. Later in Inform's run, when we start compiling code, many more unit sequences will exist on a temporary basis — as part of the kinds for intermediate results in calculations — but early on, when we're here, the only unit sequences made are the derivations of the units. So it is easy to cover all of them.
Substitute this new derivation to eliminate this fundamental unit from other sequences28.3 =
kind *R; LOOP_OVER_BASE_KINDS(R) if (Kinds::Behaviour::is_quasinumerical(R)) { unit_sequence *existing = Kinds::Behaviour::get_dimensional_form(R); Kinds::Dimensions::dim_substitute(existing, terms[newest_term], derivation); }
- This code is used in §28.
§28.4. If we have
Check this derivation to make sure it is redundant, not contradictory28.4 =
unit_sequence product; Kinds::Dimensions::multiply_unit_sequences( Kinds::Behaviour::get_dimensional_form(terms[0]), 1, Kinds::Behaviour::get_dimensional_form(terms[1]), 1, &product); if (Kinds::Dimensions::compare_unit_sequences(&product, Kinds::Behaviour::get_dimensional_form(terms[2])) == FALSE) KindsModule::problem_handler(DimensionsInconsistent_KINDERROR, NULL, NULL, NULL, NULL);
- This code is used in §28.
§29. Classifying the units. Some of the derived units are dimensionless, others not. number and real number are always dimensionless, and any unit whose derivation is the empty unit sequence must be dimensionless.
int Kinds::Dimensions::dimensionless(kind *K) { if (K == NULL) return FALSE; if (KindConstructors::is_dimensionless(K->construct)) return TRUE; if (Kinds::Behaviour::is_quasinumerical(K) == FALSE) return FALSE; return Kinds::Dimensions::us_dimensionless(Kinds::Behaviour::get_dimensional_form(K)); } int Kinds::Dimensions::us_dimensionless(unit_sequence *us) { if ((us) && (us->no_unit_pairs == 0)) return TRUE; return FALSE; } int Kinds::Dimensions::kind_is_derived(kind *K) { if (Kinds::is_intermediate(K)) return TRUE; if ((Kinds::Behaviour::is_quasinumerical(K)) && (Kinds::Behaviour::test_if_derived(K) == TRUE) && (Kinds::Dimensions::dimensionless(K) == FALSE)) return TRUE; return FALSE; }
§30. Logging. This is used by the internal "dimensions" test of Inform:
void Kinds::Dimensions::log_unit_analysis(void) { LOG("Dimensionless: "); int c = 0; kind *R; LOOP_OVER_BASE_KINDS(R) if (Kinds::Dimensions::dimensionless(R)) { if (c++ > 0) LOG(", "); LOG("%u", R); } LOG("\nBase units: "); c = 0; LOOP_OVER_BASE_KINDS(R) if ((Kinds::Dimensions::dimensionless(R) == FALSE) && (Kinds::Dimensions::kind_is_derived(R) == FALSE) && (Kinds::Behaviour::is_quasinumerical(R))) { if (c++ > 0) LOG(", "); LOG("%u", R); } LOG("\nDerived units:\n"); LOOP_OVER_BASE_KINDS(R) if ((Kinds::Dimensions::kind_is_derived(R)) && (Kinds::is_intermediate(R) == FALSE)) { unit_sequence *deriv = Kinds::Behaviour::get_dimensional_form(R); LOG("%u = $Q\n", R, deriv); } }
§31. Arithmetic on kinds. We are finally able to provide our central routine, the one providing a service for the rest of Inform. Given K1 and K2, we return the kind resulting from applying arithmetic operation op, or NULL if the operation cannot meaningfully be applied. In the case where op is a unary operation, K2 has no significance and should be NULL.
kind *Kinds::Dimensions::arithmetic_on_kinds(kind *K1, kind *K2, int op) { if (K1 == NULL) return NULL; if ((Kinds::Dimensions::arithmetic_op_is_unary(op) == FALSE) && (K2 == NULL)) return NULL; Handle calculations entirely between dimensionless units more delicately31.2; unit_sequence *operand1 = Kinds::Behaviour::get_dimensional_form(K1); if (operand1 == NULL) return NULL; unit_sequence *operand2 = Kinds::Behaviour::get_dimensional_form(K2); if ((Kinds::Dimensions::arithmetic_op_is_unary(op) == FALSE) && (operand2 == NULL)) return NULL; unit_sequence result; Calculate the result unit sequence, or return null if this is impossible31.1; Promote dimensionless numbers to real if necessary31.3; Identify the result as a known kind, if possible23.1; And otherwise create a kind as the intermediate result of a calculation31.4; }
§31.1. Some operations — like addition — cannot be performed on mixed dimensions, and roots can only be taken where fractional powers are avoided, so we sometimes have to give up here and return NULL. Otherwise, though, the functions above make it possible to work out the correct unit sequence.
It's an interesting question what the result of a remainder should be, in
dimensional terms. Clearly the remainder after dividing 90kg by 20 is 10kg.
Inform says the remainder after dividing 90kg by 20kg is also 10kg. There's
an argument that it ought to be 10, but if
Calculate the result unit sequence, or return null if this is impossible31.1 =
switch (op) { case PLUS_OPERATION: case MINUS_OPERATION: case EQUALS_OPERATION: case APPROXIMATE_OPERATION: if (Kinds::Dimensions::compare_unit_sequences(operand1, operand2)) { result = *operand1; break; } return NULL; case REMAINDER_OPERATION: case NEGATE_OPERATION: result = *operand1; break; case ROOT_OPERATION: if (Kinds::Dimensions::root_unit_sequence(operand1, 2, &result) == FALSE) return NULL; break; case REALROOT_OPERATION: if (Kinds::Dimensions::root_unit_sequence(operand1, 2, &result) == FALSE) return NULL; break; case CUBEROOT_OPERATION: if (Kinds::Dimensions::root_unit_sequence(operand1, 3, &result) == FALSE) return NULL; break; case TIMES_OPERATION: Kinds::Dimensions::multiply_unit_sequences(operand1, 1, operand2, 1, &result); break; case DIVIDE_OPERATION: Kinds::Dimensions::multiply_unit_sequences(operand1, 1, operand2, -1, &result); break; default: return NULL; }
- This code is used in §31.
§31.2. If result is the empty unit sequence, we'll identify it as a number, because number is the lowest type ID representing a dimensionless unit. Usually that's good: for instance, it says that a frequency times a time is a number, and not some more exotic dimensionless quantity like an angle.
But it's not so good when the calculation is not really physical at all, but
purely mathematical, and all we are doing is working on dimensionless units.
For instance, if take an angle
Handle calculations entirely between dimensionless units more delicately31.2 =
if ((K1->construct->relative_kind) && (K2) && (K2->construct == K1->construct->relative_kind) && ((op == PLUS_OPERATION) || (op == MINUS_OPERATION) || (op == APPROXIMATE_OPERATION))) return K1; if ((K1->construct->relative_kind) && (Kinds::eq(K1, K2)) && (op == MINUS_OPERATION)) return Kinds::base_construction(K1->construct->relative_kind); if (K1->construct->relative_kind) return NULL; if ((K2) && (K2->construct->relative_kind)) return NULL; if (Kinds::Dimensions::arithmetic_op_is_unary(op)) { if ((op == REALROOT_OPERATION) && (Kinds::eq(K1, K_number))) return K_real_number; if (Kinds::Dimensions::dimensionless(K1)) return K1; } else { if ((Kinds::Dimensions::dimensionless(K1)) && (Kinds::Dimensions::dimensionless(K2))) { if (op == TIMES_OPERATION) { dimensional_rules *dimrs = Kinds::Behaviour::get_dim_rules(K1); for (dimensional_rule *dimr = (dimrs)?(dimrs->multiplications):NULL; dimr; dimr = dimr->next) if (Kinds::eq(K2, dimr->right)) return dimr->outcome; } if (Kinds::eq(K2, K_number)) return K1; if (Kinds::eq(K1, K_number)) return K2; if (Kinds::eq(K1, K2)) return K1; } }
- This code is used in §31.
§31.3. It's also possible to get a dimensionless result by, for example, dividing a mass by another mass, and we need to be careful to keep track of whether we're using real or integer arithmetic: 1500.0m divided by 10.0m must be 150.0, not 150.
Promote dimensionless numbers to real if necessary31.3 =
if (Kinds::Dimensions::us_dimensionless(&result)) { if (Kinds::Dimensions::arithmetic_op_is_unary(op)) { if (Kinds::FloatingPoint::uses_floating_point(K1)) return K_real_number; return K_number; } else { if ((Kinds::FloatingPoint::uses_floating_point(K1)) || (Kinds::FloatingPoint::uses_floating_point(K2))) return K_real_number; return K_number; } }
- This code is used in §31.
§23.1. If we've produced the right combination of fundamental units to make one of the
named units, then we return that as an atomic kind. For instance, maybe we
divided a velocity by a time, and now we find that we have
Identify the result as a known kind, if possible23.1 =
kind *R; LOOP_OVER_BASE_KINDS(R) if (Kinds::Dimensions::compare_unit_sequences(&result, Kinds::Behaviour::get_dimensional_form(R))) return R;
§31.4. Otherwise the result is a unit sequence which doesn't have a name, so we store it as an intermediate kind, representing a temporary value living only for the duration of a calculation.
A last little wrinkle is: how we should scale this? For results like an
acceleration, something defined in the source text, we know how accurate the
author wants us to be. But these intermediate kinds are not defined, and we
don't know for sure what the author would want. It seems wise to set
The same unit sequence can have different scalings each time it appears as
an intermediate calculation. We could get to
And otherwise create a kind as the intermediate result of a calculation31.4 =
result.scaling_factor = Kinds::Dimensions::lcm(Kinds::Behaviour::scale_factor(K1), Kinds::Behaviour::scale_factor(K2)); return Kinds::intermediate_construction(&result);
- This code is used in §31.
§32. And this is needed for typechecking:
kind *Kinds::Dimensions::relative_kind(kind *K1) { if (K1->construct->relative_kind) return Kinds::base_construction(K1->construct->relative_kind); return K1; }