Math Types in TypeScript

Image from brgfx on Freepik

Introduction

The words ‘math type’ in a programming language such as Javascript or Typescript seem strange, perhaps even a bit insane. Javascript has a Number type that allows for arithmetic operations such as addition, subtraction, multiplication, and division. There is a robust coercion facility in the language that stems from the early days of the web in which “errors were not an option.” So, the language is ‘forgiving’ enough to allow operations such as

console.log(1 – “1”);

The operation only makes sense with two numerical operands, so Javascript coerces the string “1” to the numerical value of 1 and then performs the subtraction. So, in some respects, the language allows strings to be used in tandem with math operations. The output of an operation, however, may be non-numerical.

Consider the similar statement,

console.log(1 + “1”);

Javascript loves strings. The operation could be interpreted as in the previous example with a predicted numerical output of 2. Instead, Javascript coerces the number, 1, to a string and performs a string concatenation. This results in the output, “11”.

Such coercions makes the use of strings in simple math operations clumsy at best. Yet, how many times have we worked on or debugged an application where a numerical value was stored in a variable of string type or provided as a string from a service request?

Other Number Types

Javascript numbers are 64-bit floats. This is fine (up to a point) for handling representations of real numbers. Articles on numerical analysis and its use in front-end applications will have to wait for a future time 🙂

The language does not have an integer type, so what if we wanted to perform operations on integers and automatically coerce values to the nearest integer? What about whole numbers? Fractions? Complex numbers? Quaternions?

What if we wanted to assign one type to another and perform basic arithmetic operations on differing types?

What if we wanted to consider arbitrary types as operands in an equation with one type specified as the unknown in an equation? What if a ‘math type’ such as a fraction had its numerator or denominator specified as an unknown quantity in an equation?

Practical Example

I had to consider these questions in an EdTech application back in my Flash and Flex days. This learning application involved solution of equations of the form

W op X = Y op Z

where the variables W, X, Y, and Z could be real, integer, whole numbers, or fractions. Fractions could be mixed and represented in reduced or non-reduced form.

Operations were limited to addition, subtraction, multiplication, and division.

Any variable in the above equation could be an unknown. It was allowable for the whole part, numerator, or denominator of a fraction to be an unknown quantity.

Equations were generated dynamically using grade-specific templates. Each variable had constraints on the numerical values that could be generated for the equation. These constraints allowed the same template to be used across multiple grade levels.

For example, consider the template

W + X = ?

where the question mark represents the unknown quantity in the equation.

The variables W and X might be constrained to whole numbers in the interval [0, 5] and the unknown value might also be constrained to the same interval. This allows kindergarten and first graders to use finger counting as an interim to mental solution. For example,

0 + 2 = ?

1 + 4 = ?

are valid generations of equations from the above template and kindergarten or first-grade constraints. However,

2 + 5 = ?

is not valid. It might, however, be valid for a higher grade level.

A more complex template might be of the form

W * X = Y + ?

Where W, X, and Y might be constrained to be sampled from a supplied set of integer values.

Problems with fractions may be presented along with other variable types, as illustrated in the following equation.

1 1/4 + ? = 2 1/4

The general from of a future-proof equation is

A1 op A2 op A3 … op ? = 0

Any variable may be designated as the unknown in an equation. In the case of variable with multiple parts, such as a fraction or complex number, any part could be designated as unknown. So,

1 1/? + 2 = 3 1/2

is a possible equation that could be generated for students to solve by entering the unknown denominator value. All equations were generated based on supplied constraints for each variable. It was highly desirable for the system to accommodate complex numbers in a future release without changing the fundamental architecture or equation generation/solution.

Math Types

I like architectures where seemingly disparate quantities are represented with a common model or interface. This consideration gave rise to an entity loosely called a math type.

A BasicMathType is a container for a single value and possibly a number of constituents used in computing that value. For example, the fraction 1 3/4 contains a whole part, numerator, and denominator, each with their own values. The fraction value is represented by the real number 1.75.

The BasicMathType interface is shown below.

https://medium.com/media/d2404373ded6aded506f8794d9e6d0a1/href

This interface provides the contract between any BasicMathType and its consumer.

Math types can be arbitrarily used as variables (and even unknown quantities) in the general equation from the previous section. Concrete values may be provided to generate equations for which a student must input a solution in the application. The general equation can be solved symbolically. Specific values for all variables and the unknown quantity may then be used to validate a student’s answer.

Fractions represent a unique math type with additional capability.

https://medium.com/media/c934514e7c98edffb178e84424cdaa9e/href

Examples

The value of this system comes from generality and consistency in automatic type conversions. In the event that an input quantity such as the string, “a” or Javascript NaN is assigned to a math type, the invalid input is always coerced to a default for the type. This is a number system and only numbers are allowed to play 🙂

Nothing is ever promoted to a Javascript string. No results are presented as Javascript null, undefined, or NaN. Numerical answers are always provided to arithmetic operations on math types, even if an answer is not uniquely defined.

Math types also respect the domain and range of the type. It is allowable to perform arithmetic operations on whole numbers (represented by the WholeModel class). Whole numbers are integers greater than or equal to zero.

Consider the simple arithmetic operation, 1–5.

The result is negative four, which is not a whole number. The math type system for whole numbers recognizes this as a problem. There are two possible solutions. Note that all arithmetic operations on math types return at least a BasicMathType, which could be upcast.

One solution is to ensure that the result of any arithmetic operation on whole numbers is returned as a whole number model. In this case, the result is coerced to the nearest whole number, which is zero.

The alternative (which is used in the current math type system) is to compute the correct result and return an instance of the IntegerModel class. IntegerModel is a superclass of BasicMathType.

Similar issues exist with dividing two integers, say 3 and 5. The result is a real number, not an integer. The math type system computes the correct result and returns an instance of the RealModel class. Note that ‘models’ in this system are concrete implementations of abstract ‘types.’

Generality and coercion are best illustrated by several examples.

1 — Set a fraction value using a string. Automatically set mixed property to a value of true.

frac.value = ” 1 7/ 8 “;
expect(frac.whole).toEqual(1);
expect(frac.numerator).toEqual(7);
expect(frac.denominator).toEqual(8);
expect(frac.mixed).toBe(true);

2 — Add fraction types and set reduced property to a value of true on the result.

const frac: FractionModel = new FractionModel();
frac.setFraction(0, 1, 3);

const frac2: FractionModel = new FractionModel();
frac2.setFraction(0, 1, 6);

const result: FractionType = frac.add(frac2) as FractionType;
result.reduced = true;

expect(result.numerator).toEqual(1);
expect(result.denominator).toEqual(2);

3 — Subtract a number from a fraction type.

const frac: FractionModel = new FractionModel();
frac.setFraction(2, 3, 4);

const result: FractionType = frac.subtract(0.5) as FractionType;
result.reduced = true;

expect(result.whole).toEqual(2);
expect(result.numerator).toEqual(1);
expect(result.denominator).toEqual(4);

4 — Multiply a fraction type by a string.

const frac: FractionModel = new FractionModel();
frac.setFraction(2, 3, 4);

const result: FractionType = frac.multiply(“1 1/2”) as FractionType;

expect(result.whole).toEqual(4);
expect(result.numerator).toEqual(1);
expect(result.denominator).toEqual(8);

5 — Default fraction type is 0 0/1. Dividing by a zero fraction returns a default fraction.

const frac: FractionModel = new FractionModel();
frac.setFraction(0, 1, 3);

const frac2: FractionModel = new FractionModel();

const result: FractionType = frac.divide(frac2) as FractionType;

expect(result.whole).toEqual(0);
expect(result.numerator).toEqual(0);
expect(result.denominator).toEqual(1);

6 — Divide fraction type by a string.

const frac: FractionModel = new FractionModel();
frac.setFraction(2, 2, 5);

const result: FractionType = frac.divide(“3/7”) as FractionType;
result.reduced = true;

expect(result.whole).toEqual(5);
expect(result.numerator).toEqual(3);
expect(result.denominator).toEqual(5);

7 — Divide by zero into an existing fraction type results in no change to that fraction.

const frac: FractionModel = new FractionModel();
frac.setFraction(0, 1, 3);

const frac2: FractionModel = new FractionModel();

frac.divideAndReplace(frac2);

expect(frac.whole).toEqual(0);
expect(frac.numerator).toEqual(1);
expect(frac.denominator).toEqual(3);

8 — Real model value can be assigned from another math type such as a fraction.

const real: RealModel = new RealModel();
const frac: FractionModel = new FractionModel();

frac.setFraction(1, 3, 4);

real.value = frac;

expect(real.value).toEqual(1.75);

9 — Subtract string from real model.

const real: RealModel = new RealModel();

real.value = 1.25;

const result: RealModel = real.subtract(“1.0”) as RealModel;

expect(result.value).toEqual(0.25);

10 — Multiply real model by a string.

const real: RealModel = new RealModel();

real.value = 1.25;

const result: RealModel = real.multiply(“2.0”) as RealModel;

expect(result.value).toEqual(2.5);

11 — Assign fraction to an integer model’s value.

const int: IntegerModel = new IntegerModel();
const frac: FractionModel = new FractionModel();

frac.setFraction(1, 3,4);

int.value = frac;
expect(int.value).toEqual(2);

12 — Add string to integer model.

const int: IntegerModel = new IntegerModel();

int.value = 1.25;

const result: IntegerModel = int.add(“1”) as IntegerModel;

expect(result.value).toEqual(2);

13 — Multiply integer model by a number.

const int: IntegerModel = new IntegerModel();

int.value = 1.25;

const result: IntegerModel = int.multiply(2) as IntegerModel;

expect(result.value).toEqual(2);

14 — Assign fraction model to a whole model’s value.

const whole: WholeModel = new WholeModel();
const frac: FractionModel = new FractionModel();

frac.setFraction(1, 3,4);

whole.value = frac;
expect(whole.value).toEqual(2);

frac.setFraction(-1, 2,3);

whole.value = frac;
expect(whole.value).toEqual(0);

15 — Subtract number from whole model and return integer model.

const whole: WholeModel = new WholeModel();

whole.value = 1.25;

const result: BasicMathType = whole.subtract(3);
const resultType: MathTypeEnum = result.type;

expect(resultType).toEqual(MathTypeEnum.INTEGER);
expect(result.value).toEqual(-2);

Summary

The above examples provide a brief glimpse into the math type system inside the AMYR library. While EdTech is the obvious application area for such capability, I used the Flash ActionScript version of the math type system in several business applications. One simple example required changing the formatting of financial results from rounded floating-point values to a fractional representation in different parts of an application. The values were input into a RealModel and that model was assigned to a FractionModel as needed.

Based on interest, support for the definition of equations (with a single unknown variable or unknown part within an unknown variable) is the most likely next addition. This includes the ability to solve the equation and compare an input answer to the correct solution. New math types are most likely to be complex numbers and quaternions.

I hope you find some future value in this system or at least have fun deconstructing the internals. Check out the AMYR library on GitHub and get started!

GitHub – theAlgorithmist/AMYR: AMYR is a crowdfunded Typescript library that will eventually contain everything from data structures to semi-numerical to numerical algorithms, libraries for applied math, geometry, AI, drawing, business analytics, and more!

Join us at ng-conf 2023!

ng-conf | June 14–15, 2023
Workshops | June 12–13, 2023
Location | Salt Lake City, UT

ng-conf 2023 is a two-day single track conference focused on building the Angular community. Come be a part of this amazing event, meet the Angular Team and the biggest names in the community. Learn the latest in Angular, build your network, and grow your skills.

Math Types in TypeScript was originally published in ngconf on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Comment

Your email address will not be published. Required fields are marked *