C: избегать переполнения при работе с большими числами

Я реализовал некоторые алгоритмы сортировки (для сортировки целых чисел) на C, тщательно используя uint64_t, чтобы хранить все, что связано с размером данных (таким образом, также счетчики и прочее), поскольку алгоритмы также должны быть протестированы с наборами данных из нескольких гигов целых чисел.

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

Тесты с наборами данных до 4 гигабайт (таким образом, 16 ГБ данных) работают нормально (сортировка 4Gint заняла 2228 секунд, ~ 37 минут), но когда мы выходим выше этого (например, 8 Gints), алгоритм не кажется (он работает около 16 часов).

Я боюсь, что проблема может быть вызвана переполнением целочисленного числа, возможно, счетчик в цикле хранится в 32-битной переменной, или, может быть, мы вызываем некоторые функции, которые работают с 32-битными целыми числами.
Что еще может быть?

Есть ли простой способ проверить, происходит ли переполнение целых чисел во время выполнения?

Ответ 1

Это зависит от компилятора, но если вы используете gcc, вы можете скомпилировать с -ftrapv для выпуска SIGABRT, когда происходит интегральное переполнение.

Например:

/* compile with gcc -ftrapv <filename> */
#include <signal.h>
#include <stdio.h>
#include <limits.h>

void signalHandler(int sig) {
  printf("Overflow detected\n");
}

int main() {
  signal(SIGABRT, &signalHandler);

  int largeInt = INT_MAX;
  int normalInt = 42;
  int overflowInt = largeInt + normalInt;  /* should cause overflow */

  /* if compiling with -ftrapv, we shouldn't get here */
  return 0;
}

Когда я запускаю этот код локально, вывод

Overflow detected
Aborted

Ответ 2

Посмотрите -ftrapv и -fwrapv:

-ftrapv

Эта опция генерирует ловушки для подписанного переполнения для операций сложения, вычитания, умножения.

-fwrapv

Этот параметр указывает компилятору предположить, что подписанное арифметическое переполнение сложения, вычитания и умножения обертывается с использованием представления двухкомпонента. Этот флаг допускает некоторые оптимизации и отключает другие. Эта опция включена по умолчанию для интерфейса Java, как того требует спецификация языка Java.

См. также Переполнение целых чисел в стандартах и ​​компиляторах C: и Полезные флаги GCC для C.

Ответ 3

Если вы используете компилятор Microsoft, есть опции для генерации кода, который запускает исключение SEH, когда целочисленное преобразование отсекает ненулевые биты. В тех местах, где это действительно необходимо, используйте побитовое И, чтобы удалить верхние биты перед выполнением преобразования.

Ответ 4

clang теперь поддерживает проверку динамического переполнения как для целых чисел, так и без знака. См. - fsanitize = integer. На данный момент это только один компилятор С++ с полностью поддерживаемой проверкой динамического переполнения для целей отладки.

Ответ 5

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

Что касается вашей конкретной проблемы, имейте в виду, что сортировка общего случая - это O (nlogn), поэтому причина, по которой алгоритм занимает гораздо больше времени, может быть вызвана тем, что увеличение времени не является линейным по отношению к данным комплект размер. Поскольку также не упоминалось, сколько физической памяти находится в ящике и сколько ее используется для вашего алгоритма, возможно, возникла ошибка страницы на диске с большим набором данных, что потенциально замедлит сканирование.