Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Например, вот такое. Сразу скажу — я не автор и не проверял его работу. Просто взял вот тут в качестве примера
PD>Допустим, мне надо использовать этот код. Верен ли он алгоритмически (то есть делает ли то, что ему положено) — не обсуждаем. Меня интересуют лишь два вопроса
PD>Может ли произойти переполнение с плавающей точкой в этом коде ?
PD>Не возникнет ли тут деление на 0 ?
PD>Ну вот я Вам и не соврал. Все изложил, что знаю. Жду ответа
Да, оба риска в этом коде реальны, но зависят от входных данных (вашей матрицы). Разберём по пунктам.
1) Может ли быть переполнение double?
В вашем коде потенциально “опасные” места такие:
(а) Суммирование в Mul
y[i] = y[i] + (A[i][j] * x[j]);
Переполнение возможно, если либо A[i][j] очень большое, либо x[j] становится очень большим, либо сумма большого количества членов разгоняет y[i].
Но с вашей структурой матрицы (почти все элементы 0, небольшие числа около диагонали) само по себе Mul обычно не переполняет. Однако важно:
Метод Якоби может “разогнать” X, если матрица плохо обусловлена, не диагонально преобладающая, или есть маленькие диагональные элементы.
Тогда x[j] может стать огромным, и уже A[i][j] * x[j] / суммы могут уйти в inf.
(б) Внутри Якоби
TempX[i] = F[i] - Σ_{g≠i} A[i][g]*X[g];
TempX[i] /= A[i][i];
Если A[i][i] маленькое по модулю, то деление сделает TempX[i] очень большим, и дальше это может быстро привести к inf.
А при Леннард–Джонсе?
Если вы уже записываете в матрицу готовые “небольшие” числа и для дальних пар ставите 0 — тогда переполнение именно из-за R^-12 в этом коде не случится, потому что здесь R не вычисляется.
Но если в другом месте вы считаете
U = E0*[(R0/R)12-(R0/R)6]
то переполнение возможно при очень маленьком R (почти совпадающие координаты, ошибка данных): (R0/R)12 может стать гигантским. Это уже не про Якоби, а про подготовку данных.
Итог по переполнению:
В этом коде переполнение double возможно если:
— Якоби расходится (что бывает часто, если нет диагонального преобладания/хороших свойств матрицы),
— или на диагонали очень маленькие числа → X улетает,
— или в матрице/векторе всё-таки встречаются большие значения.
2) Будет ли деление на 0?
Да, может. Единственное деление тут:
TempX[i] /= A[i][i];
Если хоть один диагональный элемент A[i][i] == 0, будет деление на ноль → получите inf или nan (в зависимости от платформы/режима FPU), а итерации дальше почти гарантированно “сломаются”.
Ещё важнее: даже если A[i][i] не ровно 0, но очень мал (например, 1e-300), то это практически то же самое по эффекту: TempX[i] станет огромным, и дальше возможны inf/NaN.
Что стоит сделать минимально (практично)
Перед итерациями (или внутри) проверять диагональ:
if (A[i][i] == 0.0) { /* ошибка */ }
лучше так:
if (fabs(A[i][i]) < 1e-15) { /* слишком близко к нулю */ }
Проверять появление nan/inf:
if (!isfinite(TempX[i])) { /* аварийно остановиться */ }
(Не по вашему вопросу, но критично) освобождение памяти у вас неправильное:
delete x; delete y;
для массивов нужно:
delete[] x;
delete[] y;
И для матрицы тоже нужно освобождение по строкам + массив указателей, иначе утечка.
PD>А пока что могу сказать, что было у меня. Просто убрал это стандартное корректирующее действие и стал ждать, что из этого выйдет. Примеры запускал (считайте, что тесты) Когда переполнение наконец произошло, нашел его место и поставил проверку. Вот и все. Место такое нашлось одно за все время, что я с программой работал. Нашлось бы еще одно — поставил бы проверку и там. И это при (будем так условно считать) полном непонимании этого алгоритма — я и впрямь в деталях той реализации разбираться не стал.
Как видно, запускать и ждать теперь необязательно
PD>Нет, не придется мне коммитить код в Ваш проект. Да и в любой другой тоже. Моя программистская карьера завершена. Но в ней я такое (да и не такое!) не раз коммитил, и как-то все потом благополучно работало и никто не жаловался.
Так же говорят тетеньки, которые пирожками на вокзале торгуют
PD>В теории Вы правы. Если мой код действительно в библиотеке, да еще рассчитанной на то, что ее будет использовать потом широкий круг пользователей, то такое недопустимо.
PD>Но где я сказал, что этот код для библиотеки ? Я такого нигде не говорил.
PD>Где я сказал, что этот код может быть, когда-то будет использоваться кем-то для иной цели ? Я такого тоже не говорил.
Это зависит не только от вас.
PD>Или в моем примере с Якоби — меня абсолютно не интересует, что будет, если какой-то другой пользователь начнет использовать этот код для каких-то матриц, в которых иные данные (ну скажем, много больших элементов далеко от главной диагонали). Это его проблемы. В той задаче, что я сейчас решаю, данные именно такие и другими быть не могут — объяснение выше.
Полностью с вами согласен. То, что вы описываете, называется "инженерная культура". Точнее — её отсутствие. Типа, давайте не будем клеить обои за шкафом. Если кто-то когда-то решит передвинуть шкаф — это будут его проблемы.
PD>И , наконец, мне надо, чтобы программа как можно скорее заработала, а не провести теоретическое изучение поведения этого алгоритма при произвольных данных.
Да, совершенно верно. Именно так пишут школьники: главное, чтобы программа как можно скорее заработала. Скомпилировалась — полдела сделано. Запустилась и не вылетела — всё, несём сдавать.
Если преподаватель не проверил, что она ещё и делает то, что нужно — это его проблема. После получения зачёта эта программа всё равно пойдёт в корзину.
Но поскольку мир разработки не исчерпывается одноразовыми студенческими программами, мы своих студентов стараемся учить, как делать
правильно. Наш лозунг: "Делайте всё как следует. Стрёмно получится само".
PD>Не слишком ли мы заботится о том, что код, вовсе не предназначенный для массового использования , не будет совершенно корректно работать для случаев, которые мне при его написании для данной задачи совершенно неинтересны ?
Для начала стоит научиться писать код так, чтобы он хотя бы для интересных в данной задаче случаев работал корректно. Как видим, это не всегда тривиально.
PD>Я Вам дал на естественном языке информацию об ожидаемом диапазоне данных во входной матрице. Другой информации у меня нет.
Для традиционных формальных средств этого недостаточно. А для ИИ — вполне.
PD>Те, кто отводил под это 2 цифры в 1960 или 1970 году — вполне были правы. Не дожили их машины и и их программы до 2000 года. И не могли дожить.
Прекрасно дожили. То, что программы, разработанные в СССР, выбрасывали вместе с машинами — это не стандартная ситуация, а глобальная катастрофа.
В развитом мире код, написанный для IBM360, прекрасно работал в 2010 (и наверняка сейчас всё ещё работает).
PD>А если какой-то их код и дожил года до так 1980, то его потом все равно переносили, там и должны были поправить. Там скорее всего многое пришлось бы править — ну хотя бы из-за разных размеров данных на разных машинах.
Про вашу точку зрения ещё в 1981 году сняли анимационный художественный фильм.
https://www.kinopoisk.ru/film/257212/