long-time listener, first-time caller. I am relatively new to programming and was looking back at some of the code I wrote for an old lab. Is there an easier way to tell if a double is evenly divisible by an integer?
double num (//whatever);
int divisor (//an integer);
bool bananas;
if(floor(num)!= num || static_cast<int>(num)%divisor != 0) {
bananas=false;
}
if(bananas==true)
//do stuff;
}
The question is strange, and the checks are as well. The problem is that it makes little sense to speak about divisibility of a floating point number because floating point number are represented imprecisely in binary, and divisibility is about exactitude.
I encourage you to read this article, by David Goldberg: What Every Computer Scientist Should Know About Floating Point Arithmetic. It is a bit long-winded, so you may appreciate this website, instead: The Floating-Point Guide.
The truth is that
floor(num) == numis a strange piece of code.numis adoublefloor(num)returns andouble, close to anintThe trouble is that this does not check what you really wanted. For example, suppose (for the sake of example) that
5cannot be represented exactly as a double, therefore, instead of storing5, the computer will store4.999999999999.In general exact comparisons are meaningless for floating point numbers, because of rounding errors.
If you insist on using
floor, I suggest to usefloor(num + 0.5)which is better, though slightly biased. A better rounding method is the Banker’s rounding because it is unbiased, and the article references others if you wish. Note that the Banker’s rounding is the baked in inround…As for your question, first you need a
doubleaware modulo:fmod, then you need to remember the avoid exact comparisons bit.A first (naive) attempt:
Unfortunately it fails one important test: the magnitude of
moddepends on the magnitude ofdivisor, thus ifdivisoris smaller thanepsilonto begin with, it will always be true.A second attempt:
Better, but not quite there:
modandepsilonare signed! Yes, it’s a bizarre modulo, th sign of mod is the sign of numA third attempt:
Much better.
Should work fairly well too if
divisorcomes from an integer, as there won’t be precision issues… or at least not too much.EDIT: fourth attempt, by @ybungalobill
The previous attempt does not deal well with situations where
num/divisorerrors on the wrong side. Like1.999/1.000–>0.999, it’s nearlydivisorso we should indicate equality, yet it failed.Looks like a never ending task eh ?
There is still cause for troubles though.
doublehas a limited precision, that is a limited number of digits that is representable (16 I think ?). This precision might be insufficient to represent an integer:This truncation means it is impossible to map it back to its original value. This should not cause any issue with
doubleandint, for example on my platformdoubleis 8 bytes andintis 4 bytes, so it would work, but changingdoubletofloatorinttolongcould violate this assumption, oh hell!Are you sure you really need floating point, by the way ?