SegFaults McGee
SegFaults McGee

Reputation: 321

C++ Should this be easier?

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;
}

Upvotes: 2

Views: 349

Answers (3)

Matthieu M.
Matthieu M.

Reputation: 299870

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) == num is a strange piece of code.

  • num is a double
  • floor(num) returns an double, close to an int

The trouble is that this does not check what you really wanted. For example, suppose (for the sake of example) that 5 cannot be represented exactly as a double, therefore, instead of storing 5, the computer will store 4.999999999999.

double num = 5; // 4.999999999999999
double floored = floor(num); // 4.0

assert(num != floored);

In general exact comparisons are meaningless for floating point numbers, because of rounding errors.

If you insist on using floor, I suggest to use floor(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 in round...


As for your question, first you need a double aware modulo: fmod, then you need to remember the avoid exact comparisons bit.

A first (naive) attempt:

// divisor is deemed non-zero
// epsilon is a constant

double mod = fmod(num, divisor); // divisor will be converted to a double

if (mod <= epsilon) { }

Unfortunately it fails one important test: the magnitude of mod depends on the magnitude of divisor, thus if divisor is smaller than epsilon to begin with, it will always be true.

A second attempt:

// divisor is deemed non-zero
double const epsilon = divisor / 1000.0;

double mod = fmod(num, divisor);

if (mod <= epsilon) { }

Better, but not quite there: mod and epsilon are signed! Yes, it's a bizarre modulo, th sign of mod is the sign of num

A third attempt:

// divisor is deemed non-zero
double const eps = fabs(divisor / 1000.0);

double mod = fabs(fmod(num, divisor));

if (mod <= eps) { }

Much better.

Should work fairly well too if divisor comes 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/divisor errors on the wrong side. Like 1.999/1.000 --> 0.999, it's nearly divisor so we should indicate equality, yet it failed.

// divisor is deemed non-zero
mod = fabs(fmod(num/divisor, 1));

if (mod <= 0.001 || fabs(1 - mod) <= 0.001) { }

Looks like a never ending task eh ?


There is still cause for troubles though.

double has 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:

Integer n = 12345678901234567890;
double d = n; // 1.234567890123457 * 10^20

This truncation means it is impossible to map it back to its original value. This should not cause any issue with double and int, for example on my platform double is 8 bytes and int is 4 bytes, so it would work, but changing double to float or int to long could violate this assumption, oh hell!

Are you sure you really need floating point, by the way ?

Upvotes: 10

Matt
Matt

Reputation: 5567

Based on the above comments, I believe you can do this...

double num (//whatever);
int divisor (//an integer);
if(fmod(num, divisor) == 0) {
         //do stuff;
}

Upvotes: 3

Tyler Crompton
Tyler Crompton

Reputation: 12652

I haven't checked it but why not do this?

if (floor(num) == num && !(static_cast<int>(num) % divisor)) {
    // do stuff...
}

Upvotes: 0

Related Questions