Pull to refresh

Comments 39

Много букв, вся статья сводится к одному, проверка входных данных:
public static int gcd(int a, int b) {
    if (a <= 0 || b <= 0) {
        throw new IllegalArgumentException();
    }
    ...
}
Зачем кидать исключение при нормальных данных?
Они не нормальные. Я не знаю, почему в Википедии написано «целых чисел». В «Математическом энциклопедическом словаре (1988)» у меня значится «Наибольший общий делитель двух или нескольких натуральных чисел».
Но в википедии как минимум написано
Наибольший общий делитель существует и однозначно определён, если хотя бы одно из чисел m или n не ноль.

так что часть нападок ТС являются ошибочными.
Но вы же исправили в Википедии, да?
В Википедии всё верно написано. И преведён источник.

Наибольший общий делитель существует и однозначно определён, если хотя бы одно из чисел m или n не ноль.

Вероятно, для НОД(0, 0) разумно принять значение ∞ или бросать исключение, для остальных пар целых чисел он прекрасно находится.
Ещё можно INT_MAX :) Всё равно пока аргумент int ничего больше просто быть не может. Но я бы бросил исключение либо вернул −1: ±∞ существует только для чисел с плавающей запятой, выпендрёж с INT_MAX вряд ли кому‐то нужен, а −1 gcd возвращать не должен ни при каких условиях. Т.е. специальное значение (здесь — −1, т.к. я последнее время много пишу на C, но это может быть и nan (привет языкам без целых чисел) или Nothing) для языков без исключений (или где их не принято использовать для математических функций) либо исключение.
Лучше кинуть исключение, т.к. результат будет использоваться потом. В случае NaN исключение наверняка вылезет в другом месте, а с -1 вообще может ничего и не быть.
Исключений может просто не быть: тогда и кинуть их не получится. NaN взят из lua, там исключения ловятся весьма заморочным способом с pcall, а math.log(-1) возвращает NaN.
Но когда они есть, лучше их и кидать, благо они для этого и созданы.
А log(-1) = NaN, так это в соответствии с IEEE 754 возвращает сопроцессор, lua здесь абсолютно не причем.
Не сказал бы: Python и Perl кидают исключение, lua тоже не обязан выдавать то, что выдаёт сопроцессор. Высокоуровневые языки на то и высокоуровневые, что абстрагируются от железа.
* То есть, он не был обязан выдавать это при создании. Теперь, если math.log начнёт выдавать исключение, комплексное число или что‐нибудь ещё, будет много недовольных, которые рассчитывают на старое поведение.
Лучше тогда вернуть gcd(Math.Abs(a), Math.Abs(b));
а нулевые входные значения?
Точно, тогда gcd(Math.Max(Math.Abs(a), Math.Abs(b)), Math.Min(Math.Abs(a), Math.Abs(b)))) — с таким изменением будут работать все реализации, кроме третьей, но я ни разу не видел человека, который писал третью
Если уж и кидать исключение, то только если a < 0 || b < 0, в случае с нулем все нормально отработает
UFO just landed and posted this here
Обычно задача начинается с «Дано два натуральных числа...»
На западе часто 0 считается натуральным. Лучше использовать словосочетание positive integer
Я на западе. Где это тут 0 считается натуральным?
Хех, а в геометрии Лобачевского параллельные линии пересекаются.

Но за Пеано спасибо! Отличное чтиво на ночь :)
Ну это и не противоречит, ведь где как. Я просто уточнил, что в Agda, Coq, а также в Homotopy type theory таки с нуля, а это вроде как наиболее современное в программировании и математике.
А я уточнил, что «на западе» есть разные мнения по этому вопросу.
There is no universal agreement about whether to include zero in the set of natural numbers: some define the natural numbers to be the positive integers {1, 2, 3, ...}, while for others the term designates the non-negative integers {0, 1, 2, 3, ...}. The former definition is the traditional one, with the latter definition having first appeared in the 19th century.

Natural Numbers
> НОД существует для любых двух целых чисел.
НОД(0;0)?
0 делится на 42. И 0 делится на 42. Следовательно, 42 — общий делитель 0 и 0. Но 42 > 0, так что 0 не может быть наибольшим общим делителем 0 и 0.
Значит, НОД(0;0) = -42? :)
UFO just landed and posted this here
Вы ведь понимаете, что 0 не может быть каким-либо делителем, правда?
Хм. Что в таком случае тогда можно сказать про НОК?
Например, НОК(0,b)=НОК(a,0)=0, НОК(a,b)=НОК(abs(a),abs(b))*sign(a*b) — чтобы сохранить формулу НОК(a,b)*НОД(a,b)=a*b.
Число 0 в операции умножения играет роль бесконечности, так что не удивительно, что оно оказывается «наибольшим».
Есть очень хорошая лекция А. Степанова (автора C++ STL) про алгоритм Эвклида: video.yandex.ru/users/ya-events/view/129#

Так там он описывает, как к нему на одну из предыдущих лекций пришел Дональд Кнут и сказал в стиле «Но ведь ответ -5 — неправильный. По определению НОД — наибольший общий делитель.» На что Степанов ответил: «Зависит от определения». В лекции по ссылке подробно описано и объяснено.
int ans = gcd(Math.abs(a), Math.abs(b));

У этого решения могут быть проблемы, когда одно из чисел a или b равно int.MinValue (для него Abs не работает). Справится ли второе решение
(if(b==0) return Math.Abs(a)) для всех случаев, кроме (int.MinValue,0) и (int.MinValue, int.MinValue), я не проверял (подозрение вызывает (int.MinValue,-1) — будет ли там считаться без ошибки и исключений остаток от деления?)
Думаю, что безопаснее было бы сразу перевести аргументы в uint (или в ulong, если мы пишем gcd(long,long)), а в конце преобразовать ответ обратно. Как-нибудь так:
long gcd(long x,long y){
  ulong a=x,b=y;
  if(x<0) a=-a;
  if(y<0) b=-b;
  while(b!=0){
      ulong t=a%b;
      a=b;
      b=t;
  }
  return (long)a;
}
А почему -5 не может быть делителем? ведь указанные числа на него делятся, и без остатка.
Может быть, такой ответ и неудобен в некоторых применениях, но чему собственно он противоречит?

Хм… разделить яблоко на -5 частей… а что, звучит!
Читайте статью внимательно. Ответ на ваш вопрос дан в следующем же абзаце:
Но ведь ответ -5 — неправильный. По определению НОД — наибольший общий делитель. А таковым является число 5. Ведь и первое, и второе число делятся без остатка на 5. Значит и первые две реализации не дают верный ответ.
Sign up to leave a comment.

Articles