We can use conventional mathematical functions as a programming language. This section was inspired by a YouTube video covering Willian's Formula (a function that calculates the n-th prime), as well as another video covering the basics of mathematical programming.
Most mathematical programming is not practical, but it's interesting to see how programs can be designed using pure mathematical syntax. In mathematical programming, design choices are made to control the flow of data, which reminds me of digital circuits and gating in neural network architectures. Simple perceptron networks that attempt to simulate logical operators require mathematical expressions.
Useful Identities and Functions
Sign function:
sgn(x)=x∣x∣,x=0
Differentiable Sign Function (Approximation). We approximate the non-differentiable sign function using a sigmoid:
sgn(x)≈2σ(kx)−1,where σ(x)=1+e−x1.
As k→∞, this approximation converges pointwise to sgn(x).
Maximum of two numbers:
max(a,b)=2a+b+2∣a−b∣
Minimum of two numbers:
min(a,b)=2a+b−2∣a−b∣
Integer indicator function (returns 1 if n is an integer, 0 otherwise):
1Z(n)=⌊cos2(πn)⌋
Logical operations (assume f and g are functions that return 1 if true and 0 if false):
Negation (NOT):
¬f=1−f
Logical OR:
f∨g=sgn(f+g)
Logical AND:
f∧g=f⋅g
Logical XOR:
f⊕g=∣f−g∣
DeMorgan's Law:
1−sgn(j=1∑n1j)=j=1∏n(1−1j)
Converting Conditional Functions into Mathematical Expressions
Consider a function defined piecewise by a condition:
f(x)=⎩⎨⎧a,b,c,x>10,5≤x<10,x<5.
We can express this as a single-line mathematical expression using indicator functions:
f(x)=a1{x>10}+b1{5≤x<10}+c1{x<5}.
This works because the conditions in a piecewise function are mutually exclusive (or "one-hot"): exactly one condition is satisfied at a time. For example, x cannot simultaneously satisfy both x>10 and 5≤x<10. The indicator function 1{⋅} equals 1 when the condition is true and 0 otherwise, allowing us to sum all terms while only the active condition contributes to the function's value.
In practice, you will need to construct your own indicator functions using a concoction of conventional mathematical functions. The following exercises will ask you to construct basic indicator functions e.g. equality, less than, etc.
Exercises
I recommend using Desmos to construct your functions, which makes it easier to check your work. Also, the solution to these exercises look complicated not because they are complicated, but because math can be a horrible programming language!
Write a mathematical function that checks equality between two numbers (returns 1 if true, 0 otherwise).
Write mathematical functions for strict and non-strict inequalities (< and ≤), returning 1 if true and 0 otherwise.
Write functions that compute the maximum and minimum of two numbers a and b.
Write a function that calculates the number of factors of a given integer n.
Write a function that computes nmodk without using the modulus operator.
Write a function that counts the number of 1s in the binary representation of an integer n.
Write a function that rounds an integer n down to the nearest power of 2.
Write a function that computes the XOR of two integers a and b.
Write a function that computes the number of ways the integers 1, 2, 5 can sum to integer n (inspired by LeetCode: Coin Change II).
One annoying problem in mathematical programming is that math does not have a concept of mutable variable in the sense that as we pass a value through a function, we do not have a variable that updates through computation. As a result, our programs were verbose and hard to understand to the reader.
On the other hand, programming offers mutable variables. Consider the following program:
foo=0foriinrange(100):foo=foo+1
In this example, the variable foo is a mutable variable, since its value changes as the program is executed. In addition, we are able to reference the new value after assignment from a previous iteration. This would have been extremely useful in the solutions (for example, the question that asks to round an integer down to the nearest power of 2), since the condition used in the solution repeatedly checks if the first j bits contain a 1, when an array can reduce this check into a lookup.