I have a reason for this, but I’m not sure how good/awful my code is. But my basic idea is: I need to find a way to subtract and add fractions with denominators up to 6 in z-code. I don’t need more than that.

The LCM of these numbers is 60, so what I did was to multiply prospective numbers by 60 and then do arithmetic on the integers. This feels … adequate, but also like cheating, in a way. Is there better? Am I missing an obvious way to make things less ugly?

my-lcm is a number that varies. my-lcm is 60.
to decide which number is lcmdiv of (x - a number) and (y - a number):
decide on (my-lcm * x) / y;
to say floatrep of (x - a number):
let Q be x / my-lcm;
if Q is 0 and x < 0, say "-";
say "[x / my-lcm]";
if the remainder after dividing x by my-lcm is 0, continue the action;
say ".[run paragraph on]";
let decimal be remainder after dividing (x * 1000) / my-lcm by 1000;
if decimal < 0, now decimal is 0 - decimal;
if decimal < 10, say "0";
if decimal < 100, say "0";
say "[decimal]";
when play begins: [this simply tests tricky cases]
let n1 be lcmdiv of 7 and 3;
let n2 be lcmdiv of 11 and 4;
say "[n1] [n2].";
say "[floatrep of 13].";
say "[floatrep of -13].";
say "[floatrep of 73].";
say "[floatrep of -73].";
say "[floatrep of (n1 - n2)]."

If you know the LCM is always 60, this seems like the best solution.

In general, you can either find the LCM and convert both fractions to that form, or just “cross-multiply” as long-ago math teachers taught it to me:

a/b + c/d =
ad/bd + bc/bd =
(ad+bc)/bd

If all your numbers are less than 60, you don’t have to worry about precision, because 60 × 60 is well below the Z-machine’s limit of 32767.

The only hard part is simplifying the result, which requires calculating the GCD of the two numbers. But this is still fairly straightforward:

To decide what number is the greatest common denominator of (X - a number) and (Y - a number):
while Y is not zero:
let tmp be Y;
let Y be the remainder after dividing X by Y;
let X be tmp;
decide on X.

Then you just need a special kind of value to represent a fraction, and you should be set.

when play begins:
say 1/2 + 1/3;
say 3/2 + 4/3;
say 1/6 + 7/6;
say 11/2 + 1/2;

produces

5/6
17/6
4/3
6/1

Secret sauce:

Multipliers is always { 60, 30, 20, 15, 12, 10 }.
A fraction is a kind of value. 10/6 specifies a fraction with parts dividend and divisor.
To decide what fraction is (f - a fraction) f+ (g - a fraction):
let x be (the numerator of f) * entry (denominator of f) in multipliers;
let y be (the numerator of g) * entry (denominator of g) in multipliers;
let z be x + y;
repeat with i running from 1 to 6 begin;
if the remainder after dividing z by entry i in multipliers is zero, decide on ( z / entry i in multipliers ) over i;
end repeat;
To decide what number is the numerator of (f - a fraction): decide on the dividend part of f.
To decide what number is the denominator of (f - a fraction): decide on the divisor part of f.
To decide what fraction is (m - a number) over (n - a number):
decide on the fraction with dividend part m divisor part n;
to say (f - a fraction) + (g - a fraction): say f f+ g; say line break;

Oh, wow, very nice! While I considered fractions as a kind of value, I didn’t push forward with it, because I didn’t see the way. I saw the example in the Inform cookbook with inches and feet but for some reason I didn’t consider you could actually use a slash.

It’s the sort of thing I’d see in Python, but I’d automatically see more hoops to jump through for Inform.

A foo is a kind of value. #$%^+-_@~`!?* <>'1/|=& specifies a foo.
x is initially #$%^+-_@~`!?* <>'9/|=&

So far as I know, anything besides .,;:"\[](){} is fair game, even whitespace. It’ll cheerfully allow you to create really regrettable ambiguities.

A foo is a kind of value. 1 + 2 specifies a foo.
x is always 1 + 2.
y is always 1.
when play begins: say x; say "."; say y + 2.

outputs

1 + 2.
3

(I mean ambiguous to a human reader. There is a straightforward distinction: literals with numerals are the kind of value; anything with variables is not.)

It may have some surviving bugs, and there is room for improvement.

EDIT: Extension updated to version 1/220828. Now allows parsing of fractions in player commands, rounding to the nearest fraction of a different denominator (much faster than the original version), and corrects an issue on fraction-to-fraction comparisons.

You give me too much credit! Precision is quite constrained based on the word size of the VM, and the base kind is not a real arithmetic value kind, so it’s not quite as easy to use as I would like (but still pretty easy, I think – I would be interested to know if @aschultz can use it for his original problem).

A version v2+ would use a block value kind instead of a word value kind for much better precision, but that’s a project for another day (and/or a different programmer).

Though to be fair, it sounds like the original problem only involves denominators less than 60. So if you give that part at least six bits and promote to full integers when necessary for computations, precision shouldn’t be an issue.