Когда я запускаю вашу программу в отладчике с n = 40
и r = 20
в 32-разрядном двоичном файле, скомпилированном с Microsoft Visual Studio, я не получаю ошибку сегментации, но я получаю ошибку деления на ноль в следующая строка:
return factorial(l)/(factorial(l-m)*factorial(m));
factorial(l-m)
и factorial(m)
оба оценивают как factorial(20)
, что составляет 2,192,834,560
.
Предполагая, что sizeof(int) == 4
(32-разрядный), это число не может быть представлено знаком int
. Поэтому переполнение int
, которое, согласно официальному стандарту C, вызывает неопределенное поведение .
Однако, даже если поведение не определено, я могу разумно предположить, что происходит следующее:
Из-за переполнения число 2,192,834,560
станет -2,102,132,736
. Это связано с тем, что второе число соответствует первому числу в двоичном представлении двоичного дополнения .
Поскольку это число умножается на себя в вашем коде (при условии n = 40
и r = 20
), тогда результат умножения будет 4,418,962,039,762,845,696
. Это число, конечно, не вписывается в int
со знаком, так что переполнение происходит снова.
Шестнадцатеричное представление этого числа 0x3D534E9000000000
.
, так как это большое число не подходит в 32-разрядное целое число все лишние биты удаляются, что эквивалентно обработке результата по модулю UINT_MAX + 1
(4,294,967,296
). Результат этой операции по модулю 0
.
Следовательно, выражение
factorial(l-m)*factorial(m)
оценивается как 0
.
Это означает что строка
return factorial(l)/(factorial(l-m)*factorial(m));
приведет к исключению деления на ноль.
Одним из способов решения проблемы обработки больших чисел является использование чисел с плавающей запятой вместо целые числа. Они могут обрабатывать очень большие числа без переполнения, но вы можете потерять точность. Если вы используете double
вместо float
, вы не так легко потеряете точность, и, даже если вы это сделаете, потеря точности будет меньше.