Division without division and other computational tricks
What is this about? 🔗
You want to divide two numbers with your computer.
No problem: Just let your computer do it!
But in rare cases, your computer may not know how to divide.
This could be the case if said computer is a mere µcontroller. Even more so if you do your own arithmetic, e.g., BCD or fixed point.
So this post assumes your computer knows how to do addition , subtraction , multiplication , and comparison with , but it does not yet know division . It offers a software solution that teaches your computer division.
I understand one normal expert advice for teaching computers division is to use the CORDIC algorithm family (which can even be used to teach your computer multiplication, in case it has not learned that yet, either). But that CORDIC stuff is quite a beast to wrap your head around (which I, honestly, have not really tried yet). Apparently, it also needs a bit of ROM space for lookup tables. Finally, from my present partial understanding, CORDIC seems to be intended for binary numeric representation; the approach offered here can be used for BCD numbers unchanged.
So this toot dares to suggest a quite simple home-grown approach to the division problem.
That approach has a reasonably decent runtime performance, though it may not be optimal. It results in short program code which is easy to get right. And it doesn’t need lookup tables.
And, of course: If you just want to read this for the fun of it, without any particular application in mind, you are warmly invited to do so.
Full disclosure: I wrote this for the fun of it, without any particular application in mind.
In a nutshell 🔗
You are given a number and desire to calculate .
(That is actually all that is missing to do general division: Instead of dividing something by , multiply it with .)
To do so, start with any value that is a reasonable approximation of .
Here, the requirements for “reasonable” are lax. Any with will work. (But the better your initial approximation is, the faster the calculation completes.)
From your start value , define a sequence of better and better approximations for via the simple formula
Here is a numerical example with , using as an initial approximation for .
| | value |
|---|---|
| | 0.500000000000000000 |
| | 0.250000000000000000 |
| | 0.312500000000000000 |
| | 0.332031250000000000 |
| | 0.333328247070312500 |
| | 0.333333333255723119 |
| | 0.333333333333333315 |
| | 0.333333333333333315 |
| | 0.333333333333333315 |
The sequence stabilizes with all subsequent values being identically to . That value incidentally is the best that can be achieved, as the next higher value that can be represented by the floating point number scheme used is 0.333333333333333370, which is less precise.
In all numerical calculations presented here, I used whatever floating point number scheme my PC’s libraries offer. I believed this to be the ubiquitous IEEE 754 binary64, but I did nothing to verify this.
Once has found the first correct digit, each of the few next subsequent values has twice as many correct digits as its predecessor. This is typical of the quadratic convergence afforded by the method presented here.
When to stop? 🔗
One might be tempted to stop when . This will work most of the time, but not always. In rare cases, the sequence will oscillate.
One example found by brute-force search is with start value :
| | value |
|---|---|
| | 0.0020000000000000000 |
| | 0.0026479999999999997 |
| | 0.0029259764480000002 |
| | 0.0029582205931032645 |
| | 0.0029585798380249708 |
| | 0.0029585798816568042 |
| | 0.0029585798816568051 |
| | 0.0029585798816568042 |
| | 0.0029585798816568051 |
The value returned by the build-in division in this case prints as 0.0029585798816568047.
It is conceivable that in some cases, the sequence might end up in a merry-go-round of three or even more numbers. With a brief brute-force search, I did not find any occurrences of that, but it should not be ruled out.
In the realm of pure mathematics and precise real numbers, the algorithm employed guarantees an increasing sequence . (This statement purposefully omits the possibly too-high start-value .)
In the practical realm of computers with finite-precision arithmetic, I suggest as a straightforward termination criterion . When that triggers, a good finite-precision approximation of the desired result is given by .
For a posh twist, you can use that only if , and if not. This results in a marginal increase in typical precision, at the expense of a marginal increase in runtime.
Choice of start value 🔗
One can try to find a good start value by analyzing the given number to be inverted.
Where floating point numbers are used, analysis of the representation of yields information about the magnitude of , from which a rough approximation of its inverse can be obtained that is good enough to serve as .
A more pedestrian approach starts with the value and calculates the product . If that product is close enough to , then can be used as is. If not, can be either increased or decreased as pertinent, and calculated anew.
This pedestrian approach is used in the following sample implementation:
Sample implementation 🔗
Here is a sample implementation for this algorithm in Python:
def sample_implementation(a): assert 0 < a # Find decent start value x # between 0.17/a and 1.8/a: x = 1 xa = x * a while 1.8 < xa: x *= 0.1 xa = x * a while xa < 0.17: x *= 10 xa = x * a # The multiplication factors # of 0.1 and 10 in the above # have been pulled # out of thin air. # They'll work, but better # choices may be possible. # If representation is binary, # using the values 0.125 and 8 # will most likely # speed up calculations. # Omit the possibly too high # start value x, proceed to the # next value in the sequence # right away: x = x * (2 - xa) while True: x_new = x * (2 - x * a) if x_new <= x: return x_new x = x_new
To verify, the results of this implementation where compared with that of the system’s floating point division for 1,000,000 values in the range 1e-300 through 1e+300.
The results from this implementation where either the same as that
computed by the system’s 1/a, or it was the next lower floating
point value that can be represented, or the next higher. In no case
was the result from this implementation further off the system’s, it
was always one of those three.
In percentages:
| what | frequency |
|---|---|
| same value | 67.8 % |
| next lower value | 23.2 % |
| next larger value | 9.0 % |
For a further experiment, the posh implementation of calculating the return value was tried:
if x_new <= x: if x_new == x: return x_new else: return 0.5 * ( x_new + x_new * (2 - x_new * a) )
This was only slightly better:
| what | frequency |
|---|---|
| same value | 68.1 % |
| next lower value | 22.9 % |
| next larger value | 9.0 % |
Suggested exercises 🔗
- Implement this algorithm in a programming language of your choice and play around with it.
- Extend the algorithm so it can deal with negative and also does whatever the appropriate error handling for your platform is when .
The math behind this: a theorem. 🔗
Theorem’s assumptions 🔗
Assume a real-valued function is defined over the open non-empty interval of real numbers between two numbers and and is twice continuously differentiable over that interval.
Assume a point exists in that interval, that is, , which is a fixpoint of , that is, .
Assume further , and also for all such that , and finally, assume that no fixpoint of exists larger than and smaller than .
Theorem’s claims 🔗
- Then, any such that fulfills .
Assuming that first claim to be true, given any such that , we can define a sequence recursively via for all indices (natural numbers) .
This sequence has the following properties:
- will hold for any index . (This helps the sequence definition to make sense.)
- This is an increasing sequence, that is, for any index .
- This sequence converges to .
- Finally, it does so in quadratic fashion, that is, there exist some number and some constant such that for any index such that .
Application 🔗
Assuming for a moment the theorem given were true. It can be applied to our inversion problem as follows:
- , .
- is a quadratic parable with zeros at and , opening downwards.
- is clearly a fixpoint of .
- A straightforward calculation shows that the only other fixpoint of is .
- The maximal value of is in the middle between its zeros, so at .
- The first derivative of at is , as has its maximum there.
- The first derivative of has its only zero at .
- As opens downwards, the derivative has to be positive before and negative thereafter.
- The theorem gives us that our sequence is increasing and we have quadratic convergence, for any start value such that .
- Now is symmetrical around , so we also have quadratic convergence for any start value such that .
Suggested exercise 🔗
Convince yourself (in a “pedestrian” way) that for any with the assertion holds in our particular situation.
Suggested exercise 🔗
Convince yourself that the theorem allows the use of the function to calculate .
(There are much more convenient ways to calculate . But this way is at least legitimate, as the function can be calculated via its power series without prior knowledge of .)
Suggested exercise 🔗
In music, the “equal temperament” demands that the quotient of the frequencies of two adjacent (half-)tones is .
- Convince yourself that, for any real , this number is a fixpoint of .
- Find a value that might be suitable to make the theorem applicable.
- Verify that the theorem indeed is applicable.
- Calculate via .
Proof of the theorem 🔗
We first need to prove: Given such that , it is true that .
For : According to the mean value theorem, there must exist some with such that . According to our assumptions, . As , we see .
For , consider the function . This function is zero precisely where has a fixpoint. In particular, . Now has no fixpoint between and (exclusively), so becomes zero nowhere in that interval. Being a continuous function, cannot change sign over an interval without assuming a zero value in that interval. So cannot change sign between and (exclusively), in other words, must either be always negative or always positive there. Now is continuously differentiable. Its derivative evaluates to at . As the derivative is continuous, it must be negative for some interval of points slightly smaller than . By another application of the mean value theorem, must be positive on that same interval. We established does not change sign in the interval strictly between and , and we have found points in that interval where is positive, so we now know it must be positive everywhere between and (exclusively), which in particular proves . Now is equivalent to .
These two together prove that the sequence increases and is bounded from above by .
Any increasing bounded sequence of real numbers converges. (How to prove this fact, which is both profound and basic, depends on your definition of real numbers.)
Let us call the limit . As is continuous:
So is a fixpoint of , which forces . So the sequence converges with limit .
The only claim not yet proven is that this convergence is quadratic.
This is a direct consequence of Taylor’s theorem. It allows us to rewrite
In our case, , so this reduces to or or . The Lagrange form of the remainder boils down to for some suitable value with . Now is continuous, hence bounded over any closed interval containing . If we have an upper bound of over some such interval and let become large enough so all subsequent are contained in that interval, then indeed , which completes the proof.
Suggested exercise 🔗
Formulate the mirror-image theorem, where convergence ends up not being from below, but from above, and prove it.
Use your theorem to prove that Heron’s method works, which uses the recursion formula to calculate .
Use your theorem to prove that recursion via the function converges to for any start value with .
Suggested exercise 🔗
Above, there is a definition of “quadratic convergence” that loosely means that after a certain number of digits of the final result have been calculated correctly, the next iteration step tends to produce twice as many correct digits. There are approximation sequences that exhibit cubic convergence: The next iteration step tends to produce three times as many correct digits as the previous one has established.
- Give a formal definition of cubic convergence.
- Show that, given any start value with , the sequence defined by exhibits cubic convergence behaviour, converging towards .
- Show that, given any and any start value with , the sequence defined by exhibits cubic convergence behaviour, converging to . – When all subterms that do not involve are computed only once beforehand, four multiplications and two additions/subtractions need to be done in each iteration of the loop. It depends on circumstances whether this is faster or slower, compared with the ancient Heron’s method as mentioned above. 🔗
Author’s remark 🔗
This was also an experiment regarding typing math in via raw mathml. Result: Can be done, but is not entirely convenient.
I did not bother installing web fonts, but rely on your browser’s support of mathml. Depending what browser you use, your mileage may vary.
Discussion opportunity 🔗
If you want to comment or discuss this piece and have a Fediverse account, feel invited to answer my pertinent toot.