Implementing the modulus operator for floating-point numbers
Table of Contents
1. Motivation
This article was very remotely inspired by Jacob Smith’s article about color
palettes. I started writing some Scheme code for generating color palettes, and
I decided to first write some functions for converting between RGB and HSV
formats. Then, I realized that Scheme (unlike Emacs Lisp, for example) doesn’t
support floating-point inputs in its modulus function.
I decided to write my own fmod function, starting with a simple version that
only supported positive values, and eventually adding support for negative
values, even though this was not needed when converting between RGB and HSV. I
started writing the process in my scratch repository, but I think it has become
interesting enough to deserve its own place in this blog.
2. Simple version for positive values
The first function I wrote simply keeps subtracting the divisor to the dividend until the dividend is smaller than the divisor.
;; Calculate remainder of X by Y. Supports floating-point, as long as they are ;; positive. (define (basic-fmod x y) (if (< x y) x (basic-fmod (- x y) y)))
I also wrote an iterative C version.
/* Calculate remainder of X and Y, as long as they are positive. */ double basic_fmod(double x, double y) { while (x >= y) x -= y; return x; }
3. Supporting negative values
There are two main approaches for handling negative values: the one used by the
fmod function in the math.h C header and the one used by the mod function in
Emacs Lisp. Let’s have a look at the outputs of both functions, and how they can
be implemented.
3.1. C’s fmod
Let’s have a look at some outputs from the fmod function in the math header.
| X | Y | Result |
|---|---|---|
| 9.5 | 2.5 | 2.00 |
| 9.5 | -2.5 | 2.00 |
| -9.5 | 2.5 | -2.00 |
| -9.5 | -2.5 | -2.00 |
As you can see, the result is always the modulus of the absolute values, while
keeping the original sign of x. We can easily implement this by adding some
calls to abs and wrapping our previous function with a sign conditional.
;; Scheme equivalent of C's `fmod'. (define (c-fmod x y) (define (basic-fmod x y) (if (< x y) x (basic-fmod (- x y) y))) (let ((result (basic-fmod (abs x) (abs y)))) (if (negative? x) (- result) result)))
This is the iterative C version. I decided to use an int instead of a bool for
the bNegativeResult variable because I didn’t want to include stdbool.h just for
this. I used hungarian notation to emphasize that it acts as a boolean.
#include <math.h> /* fabs() */ /* Equivalent of `fmod' from math.h */ double my_fmod(double x, double y) { int bNegativeResult = x < 0; x = fabs(x); y = fabs(y); while (x >= y) x -= y; return bNegativeResult ? -x : x; }
3.2. Emacs’ mod
Now let’s compare the previous results with the ones from Emacs.
| X | Y | Result |
|---|---|---|
| 9.5 | 2.5 | 2.00 |
| 9.5 | -2.5 | -0.50 |
| -9.5 | 2.5 | 0.50 |
| -9.5 | -2.5 | -2.00 |
These results are better in my opinion, since they satisfy the following formula.
Where \lfloor a / b \rfloor indicates the integer division of a and b.
Before explaining my approach, I want to show how Emacs’ mod actually works. The
actual C function is called fmod_float and is located in the src/floatfns.c
file. Omitting the emacs-specific parts, we get the following function.
#include <math.h> /* fmod() */ double my_emacs_fmod(double x, double y) { x = fmod(x, y); /* If the "remainder" comes out with the wrong sign, fix it. */ if (y < 0 ? x > 0 : x < 0) x += y; return x; }
I want to note that, although Emacs’ obviously uses the real fmod from math.h,
the previous my_fmod function can be used here as well.
As you can see, the only part that differences the Emacs modulus from the C modulus is the conditional in the middle. We could simply implement this behavior in Scheme by adding the missing conditional, but I think it’s better if we adapt our previous function.
3.3. My approach
If we look again at the outputs from Emacs’ mod, we can see that the changes in
the output match the following table.
| X | Y | Result |
|---|---|---|
| Positive | Positive | AbsMod(x, y) |
| Positive | Negative | y + AbsMod(x, y) |
| Negative | Positive | y - AbsMod(x, y) |
| Negative | Negative | -AbsMod(x, y) |
Where AbsMod represents the modulus of |x| and |y|:
The table can also be expressed as a conditional formula, if you are into that.
With this, we can make a final fmod version.
;; Calculate remainder of X by Y, supporting floating point and negative values. (define (fmod x y) (define (basic-fmod x y) (if (< x y) x (basic-fmod (- x y) y))) (let ((abs-result (fmod-positive (abs x) (abs y)))) (cond ((and (positive? x) (positive? y)) abs-result) ((and (positive? x) (negative? y)) (+ y abs-result)) ((and (negative? x) (positive? y)) (- y abs-result)) ((and (negative? x) (negative? y)) (- abs-result)))))
There are some unnecessary calls to positive? and negative?, but I think it’s
clearer this way. This issue does not happen in the following C version.
#include <math.h> /* fabs() */ double emacs_fmod(double x, double y) { const double abs_x = fabs(x); const double abs_y = fabs(y); /* Calculate fmod(fabs(x), fabs(y)) */ double abs_result = abs_x; while (abs_result >= abs_y) abs_result -= abs_y; return (x >= 0) ? ((y >= 0) ? abs_result : y + abs_result) : ((y >= 0) ? y - abs_result : -abs_result); }
4. Final note
Most of these functions were made just by observing the output of already existing functions, so I don’t know if this is the most optimal or mathematical approach. If you have any suggestions or improvements, feel free to contribute to this page.