C ++ Должно ли это быть проще? - PullRequest
2 голосов
/ 31 марта 2011

давний слушатель, впервые звонящий.Я относительно новичок в программировании и оглядывался назад на некоторый код, написанный для старой лаборатории.Есть ли более простой способ определить, делится ли двойное число на целое число?

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

Ответы [ 3 ]

10 голосов
/ 31 марта 2011

Вопрос странный, и проверки тоже. Проблема в том, что не имеет смысла говорить о делимости числа с плавающей запятой, потому что число с плавающей запятой представляется неточно в двоичном виде, а делимость составляет около точность .

Я призываю вас прочитать эту статью Дэвида Голдберга: Что должен знать каждый компьютерный ученый об арифметике с плавающей запятой . Это немного затянуто, поэтому вы можете по достоинству оценить этот веб-сайт: Руководство с плавающей точкой .

Правда в том, что floor(num) == num - странный кусок кода.

  • num является double
  • floor(num) возвращает double, близкое к int

Беда в том, что это не проверяет, что вы действительно хотели. Например, предположим (ради примера), что 5 нельзя представить в точности как двойное число, поэтому вместо хранения 5 компьютер будет хранить 4.999999999999.

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

assert(num != floored);

В общем случае точные сравнения бессмысленны для чисел с плавающей запятой из-за ошибок округления.

Если вы настаиваете на использовании floor, я предлагаю использовать floor(num + 0.5), что лучше, хотя и слегка предвзято. Лучшим методом округления является округление Банкира , потому что оно непредвзято, и статья ссылается на других, если хотите. Обратите внимание, что округление банкира запекается в round ...


Что касается вашего вопроса, сначала вам нужно знать double по модулю: fmod, затем вам нужно запомнить избегать точных сравнений бит.

Первая (наивная) попытка:

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

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

if (mod <= epsilon) { }

К сожалению, он не прошел один важный тест: величина mod зависит от величины divisor, поэтому, если divisor меньше, чем epsilon, с самого начала, это всегда будет истинно.

Вторая попытка:

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

double mod = fmod(num, divisor);

if (mod <= epsilon) { }

Лучше, но не совсем там: mod и epsilon подписаны! Да, это странно по модулю, знак мод - это знак числа

Третья попытка:

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

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

if (mod <= eps) { }

Намного лучше.

Должно работать довольно хорошо, если divisor получено из целого числа, так как не будет проблем с точностью ... или, по крайней мере, не слишком много.

РЕДАКТИРОВАТЬ : четвертая попытка, @ ybungalobill

Предыдущая попытка не очень хорошо справляется с ситуациями, когда num/divisor ошибки на неправильной стороне Как и 1.999/1.000 -> 0.999, это почти divisor, поэтому мы должны указать равенство, но это не удалось.

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

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

Выглядит как бесконечная задача, а?


Хотя есть еще причины для неприятностей.

double имеет ограниченную точность, то есть ограниченное число представляемых цифр (я думаю, что 16). Эта точность может быть недостаточной для представления целого числа:

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

Это усечение означает, что невозможно сопоставить его с первоначальным значением. Это не должно вызывать проблем с double и int, например, на моей платформе double равен 8 байтам и int равен 4 байтам, поэтому он будет работать, но при изменении double на float или int до long может нарушить это предположение, о черт!

Кстати, вы действительно нуждаетесь в плавающей запятой?

3 голосов
/ 31 марта 2011

Исходя из вышеизложенных комментариев, я считаю, что вы можете сделать это ...

double num (//whatever);
int divisor (//an integer);
if(fmod(num, divisor) == 0) {
         //do stuff;
}
0 голосов
/ 31 марта 2011

Я не проверял, но почему бы не сделать это?

if (floor(num) == num && !(static_cast<int>(num) % divisor)) {
    // do stuff...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...