Problems displaying values with units; math with values that have units generally

I have another units-related problem which I’ve half-solved, and I’m looking for a better solution.

The simulation I’m working on has, as you might imagine, a number of values with units (temperature, volume, etc), all floating-point, and sometimes their mantissas get quite long. For the sake of players, I would like to shorten these numbers when displayed, but it appears that the many of Inform’s functions for handling numbers only know how to handle what Inform calls real numbers and not values with units.

E.g.

"Printing Values With Units Test" by Kevin Riggle

[Temperature - adapted from Metric Units by Graham Nelson]
Temperature is a kind of value.

The specification of temperature is "Used to measure how hot or cold
something is. (Note that Inform writes '1 C' for one coulomb, and '1C' for
one degree centigrade.)"

1.0C (in US units, in C) or 1 degree centigrade (in degrees centigrade,
singular) or 2 degrees centigrade (in degrees centigrade, plural) or 1 degree
Celsius (in degrees Celsius, singular) or 2 degrees Celsius (in degrees
Celsius, plural) specifies a temperature.

When play begins:
	let T be 8.575 degrees Celsius;
	say "The temperature of T is [T][line break]";
	say "The temperature of T to the nearest whole number is [T to the nearest whole number][line break]".

Example Location is a room.

This throws a compile-time error, You wrote 'say "The temperature of T to the neares [...] nearest whole number][line break]"' , which I tried to match against several possible phrase definitions. None of them worked. where it expects a real number instead.

The hackish workaround is to remove the units, so e.g. T divided by 1 degree Centigrade to the nearest whole number works, albeit then requiring me to add the appropriate units back to the displayed output by hand. But for more complicated units this gets increasingly lengthy and seems like it shouldn’t be necessary. (The same problem afflicts mathematical functions like the floor function which really should work, and adding and removing units in the middle of a complex equation gets ugly.)

Is there a better answer here?

The definition of the phrase in question is:

To decide which arithmetic value is (X - arithmetic value) to the nearest (Y - arithmetic value)

So you need to specify exactly what you want Inform to round to: T to the nearest 1C works, and produces:

The temperature of T is 8.575C
The temperature of T to the nearest whole number is 9.0C

You can even write: T to the nearest 0.5C, which yields 8.5C for your example.

1 Like

You could use a custom “To decide” phrase to wrap the functions which are defined as real-to-real in the Standard Rules in order to use them for temperatures.

One way to do that would be “quasi-functional-programming style” by parameterizing the phrase with the function (which is a phrase itself), like this:

To decide which temperature is the result of (F - a phrase real number -> real number) of (T - a temperature):
	let temp-as-real be T divided by 1 degree centigrade;
	let r be F applied to temp-as-real;
	let result be r multiplied by 1 degree centigrade;
	decide on result.

Then you could write elsewhere:

When play begins:
	let temp be 8.575 degrees Celsius;
	let minus-temp be -20 degrees Celsius;
	say "temp is [temp].";
	say "The floor of temp is [result of floor function of temp].";
	say "The ceiling of temp is [result of ceiling function of temp].";
	say "The square of temp is [result of rsqr function of temp].";
	say "The square root of temp is [result of root function of temp].";
	say "The absolute of minus-temp is [result of abs function of minus-temp].".

The names of the functions are defined in the Standard Rules, “Section SR5/2/4a - Values - Real Arithmetic (for Glulx only)”, in the lines like “(this is the ... function)”.

See chapter 22.3 “Phrases as values” for the general idea.

It would of course be better if we could also express this in a type-generic way, not just for each kind of value like temperature separately.
Something like: “To decide which K is the result of (F - a phrase real number -> real number) of (original - arithmetic value of kind K):”, as is described in chapter 22.7 “Kind variables”.

But I haven’t found an elegant solution to do the latter in conjunction with the unit conversion, because instead of doing “let temp-as-real be T divided by 1 degree centigrade;” like we did above, we would of course now need to use the correct unit for the kind of value K, and I didn’t see an obvious way to do this. But I might easily have overlooked something, of course.

Basically, instead of the “default value of K” (which is 0 or 0.0C etc. for the numerical kinds of values), we would need the multiplicative identity or neutral element of K (in other words, the equivalent of 1 expressed in K).

Like this? (The phrase “unit value of [arithmetic kind K]” does what you mean, I think.)

Easy Unit Values.i7x (12.2 KB)

3 Likes

Oooh, nice! Exactly, thanks for that!

If we include your extension, we can write a type-generic abstraction like this (modified from above):

To decide which K is the result of (F - a phrase real number -> real number) of (original - arithmetic value of kind K):
	let neutral-element be the unit value of K;
	let original-as-real be original divided by neutral-element;
	let r be F applied to original-as-real;
	let result be r multiplied by neutral-element;
	decide on result.

Which lets us apply the real-to-real functions to other kinds of values, like mass, for example:

let m1 be 5.678kg;
say "The floor of m1 is [result of floor function of m1].";
say "The ceiling of m1 is [result of ceiling function of m1].";
[... etc.]

(If we have defined mass before, of course: “Mass is a kind of value. 1.0kg (in metric units, in kg) or 1 kilogram (in kilograms, singular) or 2 kilograms (in kilograms, plural) specifies a mass.”)

Quite possibly there are simpler ways to do all this, but it was fun to tinker with it. :slight_smile:

2 Likes

Oh this is great. Thank you all so much. A bit of a follow-up question: is there a way to get Inform to say only “1C” or “1 kW” rather than “1.0C” or “1.0 kW”? It’s important that Inform understand the internal representation that kilowatts are a real number, but once I’ve rounded it for display it’s misleading to players to have that .0 kicking around.

I’d say, generally-speaking, there are two strategies for this:

Strategy 1

Either you’d need to declare the units with names for the parts, and declare parts of them optional, as in:
A monetary value is a kind of value. $1.99 specifies a monetary value with parts dollars and cents (optional, preamble optional).”

Then “$3” “will be the preferred form when Inform prints out a monetary value which is an exact number of dollars”, see chapter 15.15. The parts of a number specification.

But this doesn’t work so well in a scientific context / in our use case, because Inform would then expect that such numbers exactly conform to the specification regarding the number of digits. For example, if we defined “1.99C specifies a temperature with parts degrees and subdegrees (optional, preamble optional)”, Inform would later throw the error “you use the notation '8.575C' to write a constant value. But the notation was specified as '1.99C', which means that the second numerical part should range between 0 and 99”.

Strategy 2

So, I think we are left with the second strategy, which is to print the rounded value and append the unit name explicitly, as you already mentioned in your original post. We could do this, for example, in these different ways:

2.1 For each kind of value:
To say rounded (T - a temperature):
	let temp-as-num be T divided by 1 degree Centigrade to the nearest whole number;
	say "[temp-as-num]C".

To say rounded (M - a mass):
	let mass-as-num be M divided by 1kg to the nearest whole number;
	say "[mass-as-num]kg".

And then:

When play begins:
    let temp be 8.575 degrees Celsius;
    say "temp is [rounded temp].";
    let m1 be 5.678kg;
    say "m1 is [rounded m1].";
2.2 Alternatively, in a type-generic version:
To say rounded (original - an arithmetic value of kind K):
	let neutral-element be the unit value of K;
	let original-as-real be original divided by neutral-element;
	let rounded-number be original-as-real to the nearest whole number;
	say "[rounded-number]".

But here, since I don’t know whether there’s a way to find out and print just the unit name of any given kind, we don’t say the unit name in our type-generic function. So we need to append it in the calling code:

When play begins:
    let temp be 8.575 degrees Celsius;
    say "temp is [rounded temp]C.";
    let m1 be 5.678kg;
    say "m1 is [rounded m1]kg.";
1 Like