The mathematics works the same for floating-point types as it does for integer types: If n is a power of the radix (two for binary), then f modulo n can be computed by zeroing digits representing values n or greater (also known as the high bits or high digits).
So, for a binary integer with bits b15 b14 b13 b12 b11 b10 b9 b8 b7 b6 b5 b4 b3 b2 b1 b0, we can compute the residue modulo four simply by setting b15 to b2 to zero, leaving only b1 b0.
Similarly, if the radix of the floating-point format is two, we can compute the residue modulo four by removing all digits whose value is four or greater. This does not require a division, but it does require examining the bits representing the value. A simple bit mask alone will not suffice.
The C standard characterizes a floating-point type as a sign (±1), a base b, an exponent, and some number of base b digits. Thus, if we know the format a particular C implementation uses to represent a floating-point type (the way that the sign, exponent, and digits are encoded into bits), an algorithm for calculating f modulo n, where n is a power of b, is:
- Let y = f.
- Use the difference between the exponent of y and the exponent of n to decide which digits in y have position values less than n.
- Change those digits to zero.
- Return f − y.
Some notes:
- The algorithm has to handle infinities, NaNs, subnormals, and other special cases.
- The purpose of zeroing low digits in the copy y and subtracting from f rather than zeroing high digits directly in f is to avoid the need to zero the implicit bit in the IEEE 754 format. (In the algorithm as stated, if the implicit bit in y needs to be zeroed, then all of y is zeroed, so it is easy.)
- Although no division is used, the manipulations are not as simplistic as a bit mask and are unlikely to be useful generally. However, there are special situations, often with known values of d and limited values of x, where such bit manipulations of floating-point representations are useful.
Sample code:
// This code assumes double is IEEE 754 basic 64-bit binary floating-point.
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
// Return the bits representing the double x.
static uint64_t Bits(double x)
{ return (union { double d; uint64_t u; }) { x } .u; }
// Return the double represented by bits x.
static double Double(uint64_t x)
{ return (union { uint64_t u; double d; }) { x } .d; }
// Return x modulo 2**E.
static double Mod(double x, int E)
{
uint64_t b = Bits(x);
int e = b >> 52 & 0x7ff;
// If x is a NaN, return it.
if (x != x) return x;
// Is x is infinite, return a NaN.
if (!isfinite(x)) return NAN;
// If x is subnormal, adjust its exponent.
if (e == 0) e = 1;
// Remove the encoding bias from e and get the difference in exponents.
e = (e-1023) - E;
// Calculate number of bits to keep. (Could be consolidated above, kept for illustration.)
e = 52 - e;
if (e <= 0) return 0;
if (53 <= e) return x;
// Remove the low e bits (temporarily).
b = b >> e << e;
/* Convert b to a double and subtract the bits we left in it from the
original number, thus leaving the bits that were removed from b.
*/
return x - Double(b);
}
static void Try(double x, int E)
{
double expected = fmod(x, scalb(1, E));
double observed = Mod(x, E);
if (expected == observed)
printf("Mod(%a, %d) = %a.\n", x, E, observed);
else
{
printf("Error, Mod(%g, %d) = %g, but expected %g.\n",
x, E, observed, expected);
exit(EXIT_FAILURE);
}
}
int main(void)
{
double n = 4;
// Calculate the base-two logarithm of n.
int E;
frexp(n, &E);
E -= 1;
Try(7, E);
Try(0x1p53 + 2, E);
Try(0x1p53 + 6, E);
Try(3.75, E);
Try(-7, E);
Try(0x1p-1049, E);
}
n == 0
... – Sentimentalismfmod(x,y)
. Good compilers, may detect optimizations available for selectx,y
and emit fast code. – Highwayn == 1
the result is obviously 0. – Pippyf
was a general floating-point number rather than an integral floating-point number. – Unfastenn==1
is even possible... – Indopacificf%1 == f
– Almetaf
to be afloat
with values spanning allfloat
(fractions, whole numbers, negatives, INF, NaN, signed 0), or interested in a subset offloat
? – Highwayf%1 == f
what?!f%1 = 0
is a better candidate, yet even that has issues. – Highwayunsigned
is unabiguous. "mod" withint
can be thought of differently depending on which "mod" you want. In C,%
is the remainder, perhaps not the "mod" you want. This applies tofloat
also. Either define or post examples of expected results with variousfloat
"modded" by a constant. – Highwayfmod()
,remainder()
,remquo()
? The difference between the first two is a difference in rounding mode for the quotient. – Inpatient