Нужно ли явно обрабатывать отрицательные числа или ноль при суммировании квадратов?

1115 Недавно у меня был тест в моем классе. Одной из проблем было следующее:

Учитывая число n , напишите функцию на C / C ++, которая возвращает сумму цифр числа в квадрате . (Важно следующее) Диапазон из n равен [- (10 ^ 7), 10 ^ 7]. Пример: если n = 123, ваша функция должна вернуть 14 (1 ^ 2 + 2 ^ 2 + 3 ^ 2 = 14).

Это функция, которую я написал:

int sum_of_digits_squared(int n) 
{
    int s = 0, c;

    while (n) {
        c = n % 10;
        s += (c * c);
        n /= 10;
    }

    return s;
}

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

int sum_of_digits_squared(int n) 
 {
    int s = 0, c;

    if (n == 0) {      //
        return 0;      //
    }                  //
                       // THIS APPARENTLY SHOULD'VE 
    if (n < 0) {       // BEEN IN THE FUNCTION FOR IT
        n = n * (-1);  // TO BE CORRECT
    }                  //

    while (n) {
        c = n % 10;
        s += (c * c);
        n /= 10;
    }

    return s;
}

Аргументом для этого является то, что число n находится в диапазоне [ - (10 ^ 7), 10 ^ 7], так что это может быть отрицательное число. Но я не вижу, где моя собственная версия функции терпит неудачу. Если я правильно понимаю, значение while(n) - это while(n != 0), , а не while (n > 0), поэтому в моей версии функции число n не может не войти в петля. Это будет работать точно так же.

Затем я попробовал обе версии функции на своем компьютере дома, и я получил точно такие же ответы для всех примеров, которые я пробовал. Итак, sum_of_digits_squared(-123) равно sum_of_digits_squared(123) (что опять-таки равно 14) (даже без подробностей, которые я, очевидно, должен был добавить). Действительно, если я попытаюсь напечатать на экране цифры числа (от наименьшего до наибольшего значения), в случае 123 я получу 3 2 1, а в случае -123 я получу -3 -2 -1 (что на самом деле вроде интересно). Но в этой проблеме это не имеет значения, так как мы возводим цифры в квадрат.

Итак, кто не прав?

РЕДАКТИРОВАТЬ : Мое плохое, я забыл указать и не знал, что это важно. Версия C, используемая в нашем классе и тестах, должна быть C99 или новее . Таким образом, я предполагаю (прочитав комментарии), что моя версия получит правильный ответ любым способом.

213
задан Lundin 23 October 2019 в 05:44
поделиться

8 ответов

Суммирование обсуждения это проникало в комментариях:

  • нет никакого серьезного основания протестировать заранее на n == 0. Эти while(n) тест обработает тот случай отлично.
  • вероятно, что Ваш учитель все еще привык к более ранним временам, когда результат % с отрицательными операндами был по-другому определен. В некоторых старых системах (включая, особенно, ранний Unix на PDP-11, где Dennis Ritchie первоначально разработал C), результат a % b был всегда в диапазоне [0 .. b-1], означая, что-123% 10 равнялись 7. В такой системе тест заранее для n < 0 был бы необходим.

, Но второй маркер применяется только к более ранним временам. В текущих версиях и C и стандартов C++, целочисленное деление определяется для усечения к 0, таким образом, оказывается, что n % 10, как гарантируют, даст Вам (возможно отрицательный) последняя цифра n, даже когда n отрицательно.

Так ответ на вопрос , "Каково значение while(n)?" "Точно то же как [1 110]" , и ответ на [1 115], "Это кодирует работу правильно для отрицательного, а также положительного n?" "да, В соответствии с любым современным, приспосабливающим Стандартам компилятором". ответ на вопрос "Затем, почему преподаватель отмечал его?" , вероятно, что они не знают о значительном переопределении языка, которое произошло с C в 1999 и с C++ в 2010 или около этого.

242
ответ дан 23 November 2019 в 04:27
поделиться

Как другие указали, специальный режим для n == 0 не имеет смысла, с тех пор для каждого серьезного программиста C очевидно, что, "в то время как (n)" делает задание.

поведение для n< 0 не настолько очевидно, вот почему я предпочел бы видеть те 2 строки кода:

if (n < 0) 
    n = -n;

или по крайней мере комментарий:

// don't worry, works for n < 0 as well

Честно, в какое время Вы начинали полагать, что n мог бы быть отрицательным? При написании кода или при чтении комментариев учителя?

7
ответ дан 23 November 2019 в 04:27
поделиться

Мне не полностью нравится или Ваша версия или Ваш учитель. Версия Вашего учителя делает дополнительные тесты, на которые Вы правильно указываете, являются ненужными. Оператор Mod C не является надлежащей математической модификацией: модификация отрицательного числа 10 приведет к отрицательному результату (надлежащий математический модуль является всегда неотрицательным). Но так как Вы придаете ему квадратную форму так или иначе, никакое различие.

, Но это совсем не очевидно, таким образом, я добавил бы к Вашему коду не проверки Вашего учителя, но большой комментарий, который объясняет, почему он работает. Например:

ПРИМЕЧАНИЕ/*: Это работает на отрицательные величины, потому что модуль становится в квадрате * /

19
ответ дан 23 November 2019 в 04:27
поделиться

Это напоминает мне о присвоении, что я перестал работать

Путь назад в 90-х. Лектор вырастал приблизительно на циклах, и, короче говоря, наше присвоение состояло в том, чтобы записать функцию, которая возвратит количество цифр для любого данного целого числа> 0.

Так, например, количество цифр в 321 было бы 3.

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

, Но циклы использования не был явно указан так я: took the log, stripped away the decimals, added 1 и впоследствии критиковался перед целым классом.

Точка, цель присвоения состояла в том, чтобы протестировать наше понимание того, что мы изучили во время лекций . От лекции, которую я получил, я узнал, что компьютерный учитель был немного придурком (но возможно толчок с планом?)

<час>

В Вашей ситуации:

пишут функцию в C/C++, который возвращается, сумма цифр числа придала квадратную форму

, я определенно предоставлю два ответа:

  • корректный ответ (возводящий в квадрат число сначала), и
  • неправильный ответ в соответствии с примером, только для угождения ему ;-)
4
ответ дан 23 November 2019 в 04:27
поделиться

ПРИМЕЧАНИЕ: AS я писал этот ответ, Вы действительно разъясняли использование C. Большинство моего ответа о C++. Однако, так как Ваш заголовок все еще имеет C++, и вопросом является все еще отмеченный C++, я принял решение ответить так или иначе в случае, если это все еще полезно для других людей, тем более, что большинство ответов, которые я видел до настоящего времени, является главным образом неудовлетворительным.

В современном C++ (Примечание: Я действительно не знаю, где C стоит на этом), Ваш преподаватель, кажется, неправ в обоих случаях.

Первый эта часть прямо здесь:

if (n == 0) {
        return 0;
}

В C++, это является в основном тем же самым как :

if (!n) {
        return 0;
}

, Который означает Ваш, в то время как эквивалентно чему-то вроде этого:

while(n != 0) {
    // some implementation
}

, Который означает, так как Вы просто выходите в Вашем если, когда, в то время как не выполнился бы так или иначе, действительно нет причины поместить это, если здесь, начиная с того, что Вы делаете после цикла и в, если эквивалентны так или иначе. Хотя я должен сказать, что это по некоторым причинам, они отличались, у Вас должно будет быть это если.

Поэтому действительно, это, если оператор не особенно полезен, если я не ошибаюсь.

вторая часть - то, где вещи становятся волосатыми:

if (n < 0) {
    n = n * (-1);
}  

основа проблемы, что вывод модуля отрицательного числа выводы.

В современном C++, это, кажется, главным образом четко определено :

двоичный файл / оператор приводит к частному, и двоичный оператор % приводит к остатку от подразделения первого выражения вторым. Если второй операнд / или % является нулем, поведение не определено. Для интегральных операндов / оператор приводит к алгебраическому частному с любой отброшенной дробной частью; если частное a/b является представимым в типе результата, (a/b) *b + a%b равен a.

И позже:

, Если оба операнда являются неотрицательными затем, остаток является неотрицательным; в противном случае знак остатка определяется реализацией.

, Поскольку плакат заключенного в кавычки ответа правильно указывает, важная часть этого уравнения прямо здесь:

(a/b) *b + a%b

, Берущий пример Вашего случая, Вы получили бы что-то вроде этого:

-13/ 10 = -1 (integer truncation)
-1 * 10 = -10
-13 - (-10) = -13 + 10 = -3 

единственная выгода то, что последняя строка:

, Если оба операнда являются неотрицательными затем, остаток является неотрицательным; в противном случае знак остатка определяется реализацией.

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

Тем не менее имеют в виду, что это не обязательно относится к более ранним версиям C++ или C99. Если, именно это использует Ваш преподаватель, который мог быть почему.

<час>

РЕДАКТИРОВАНИЕ: Нет, я неправ. Это кажется иметь место для C99 или позже также :

C99 требует этого, когда a/b является представимым:

(a/b) * b + a%b должен равняться

И другое место :

, Когда целые числа разделены и подразделение неточно, если оба операнда положительны результат / оператор является самым большим целым числом меньше, чем алгебраическое частное и результат оператора % положительны. Если или операнд отрицателен, является ли результат / оператор самым большим целым числом меньше, чем алгебраическое частное или самое маленькое целое число, больше, чем алгебраическое частное определяется реализацией, как знак результата оператора %. Если частное a/b будет представимым, то выражение (a/b) *b + a%b должно равняться a.

Делает или ANSI C или ISO C, указывают, каковы-5% 10 должны быть?

Так, да. Даже в C99, это, кажется, не влияет на Вас. Уравнение является тем же.

10
ответ дан 23 November 2019 в 04:27
поделиться

Ваш код превосходен

, Вы абсолютно корректны, и Ваш учитель неправ. Нет абсолютно никакой причины вообще, чтобы добавить, что дополнительная сложность, так как она не влияет на результат вообще. Это даже представляет ошибку. (См. ниже)

Первый, отдельная проверка, если n нуль, является очевидно абсолютно ненужной, и это очень легко понять. Честно говоря, я на самом деле подвергаю сомнению Вашу компетентность учителей, если у него есть возражения об этом. Но я предполагаю, что у всех может время от времени быть заскок. Однако я ДЕЙСТВИТЕЛЬНО думаю, что while(n) должен быть изменен на while(n != 0), потому что это добавляет немного дополнительную ясность, даже не стоя дополнительной строки. Это - незначительная вещь все же.

второй немного более понятен, но он все еще неправ.

Это - то, что говорит стандарт C11 6.5.5.p6 :

, Если частное a/b является представимым, выражение (a/b) *b + a%b должно равняться a; иначе поведение и a/b и a%b не определено.

в сноске говорится это:

Это часто называют "усечением к нулю".

Усечение к нулевым средствам, что абсолютное значение для a/b равно абсолютному значению для (-a)/b для всего a и b, который в свою очередь означает, что Ваш код прекрасно подходит. Однако у Вашего учителя действительно есть точка, что необходимо быть осторожными, потому что то, что Вы придаете результату квадратную форму, на самом деле крайне важно здесь. Вычисление a%b согласно вышеупомянутому определению является легкой математикой, но это могло бы идти вразрез с Вашей интуицией. Для умножения и разделения, результат положителен, если операнды имеют знак "равно". Но когда дело доходит до модуля, результат имеет тот же знак как первый операнд. Например, 7%3==1, но (-7)%(-3)==(-1). Вот отрывок, демонстрирующий это:

$ cat > main.c 
#include <stdio.h>

void f(int a, int b) 
{
    printf("a: %2d b: %2d a/b: %2d a\%b: %2d (a%b)^2: %2d (a/b)*b+a%b==a: %5s\n",
           a, b ,a/b, a%b, (a%b)*(a%b), (a/b)*b+a%b == a ? "true" : "false");
}

int main(void)
{
    int a=7, b=3;
    f(a,b);
    f(-a,b);
    f(a,-b);
    f(-a,-b);
}

$ gcc main.c -Wall -Wextra -pedantic -std=c99

$ ./a.out
a:  7 b:  3 a/b:  2 a%b:  1 (a%b)^2:  1 (a/b)*b+a%b==a:  true
a: -7 b:  3 a/b: -2 a%b: -1 (a%b)^2:  1 (a/b)*b+a%b==a:  true
a:  7 b: -3 a/b: -2 a%b:  1 (a%b)^2:  1 (a/b)*b+a%b==a:  true
a: -7 b: -3 a/b:  2 a%b: -1 (a%b)^2:  1 (a/b)*b+a%b==a:  true

Так, иронически, Ваш учитель подтвердил свою точку зрения, будучи неправым.

код Вашего учителя испорчен

Да, это на самом деле. Если вход INT_MIN, И архитектура является дополнением two И комбинацией двоичных разрядов, где знаковый бит равняется 1, и все биты значения 0, не значение прерывания (использующий дополнение two без значений прерывания, очень распространено), затем Ваш , код учителя приведет к неопределенному поведению на строке n = n * (-1). Ваш код - если очень немного - лучше , чем его. И рассматривая представление небольшой ошибки путем создания кода ненужным комплексом и получения абсолютно нулевого значения, я сказал бы, что код НАМНОГО лучше.

, Другими словами, в компиляциях, где INT_MIN =-32768 (даже при том, что получающаяся функция не может получить вход, который является <-32768 или> 32767), допустимый вход-32768 причин неопределенное поведение, потому что результата - (-32768i16) нельзя выразить как 16-разрядное целое число. (На самом деле,-32768, вероятно, не вызвал бы неправильный результат, потому что - (-32768i16) обычно оценивает к-32768i16, и Ваша программа обрабатывает отрицательные числа правильно.) (SHRT_MIN мог быть-32768 или-32767, в зависимости от компилятора.)

, Но Ваш учитель явно указал, что n может быть в диапазоне [-10^7; 10^7]. 16-разрядное целое число является слишком маленьким; необходимо было бы использовать [по крайней мере], 32-разрядное целое число. Используя [1 115], могло бы казаться, сделал бы его код безопасным, за исключением того, что int не обязательно 32-разрядное целое число. Если Вы компилируете для 16-разрядной архитектуры, оба из Ваших фрагментов кода испорчены. Но Ваш код еще намного лучше, потому что этот сценарий повторно вводит ошибку с [1 117] упомянутый выше с его версией. Для предотвращения этого можно записать long вместо [1 119], который является 32-разрядным целым числом на любой архитектуре. long, как гарантируют, сможет содержать любое значение в диапазоне [-2147483647; 2147483647]. Стандарт C11 5.2.4.2.1 LONG_MIN часто -2147483648, но максимум (да, максимум, это - отрицательное число), позволенный значение для [1 123], 2147483647.

, Какие изменения я внес бы в Ваш код?

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

  • названия переменных могли быть немного лучше, но это - короткая функция, которую легко понять, так это не имеет большого значения.
  • Вы могли изменить условие с [1 125] до [1 126]. Семантически, это - 100%-й эквивалент, но это делает это немного более ясным.
  • объявление Перемещения [1 127] (который я переименовал к [1 128]) к внутренней части цикл с условием продолжения, так как это только используется там.
  • тип аргумента Изменения к [1 129] для обеспечения его может обработать целый входной набор.
int sum_of_digits_squared(long n) 
{
    long sum = 0;

    while (n != 0) {
        int digit = n % 10;
        sum += (digit * digit);
        n /= 10;
    }

    return sum;
}

На самом деле, это может быть немного вводящим в заблуждение, потому что - как упомянуто выше - переменная digit может получить отрицательную величину, но цифра никогда не сам по себе или положительна или отрицательна. Существует несколько путей вокруг этого, но это ДЕЙСТВИТЕЛЬНО придирается к мелочам, и я не заботился бы о таких маленьких деталях. Особенно отдельная функция для последней цифры берет его слишком далеко. Как ни странно, это - одна из вещей, которые кодируют Ваши учителя, на самом деле решает.

  • Изменение sum += (digit * digit) к [1 132] и пропуск переменная digit полностью.
  • Изменяют знак [1 134], если отрицательный.
  • Создают отдельную функцию, которая извлекает последнюю цифру. int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }
  • Просто имя это c, поскольку Вы первоначально делаете. То имя переменной не дает полезной информации, но с другой стороны, это не вводит в заблуждение также.

, Но честно говоря, в этой точке необходимо идти дальше к более важной работе. :)

105
ответ дан 23 November 2019 в 04:27
поделиться

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

Один я не могу strees достаточно быть , "используют значимые имена переменной" .

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

Другой вещью, которую я склонен видеть с кодом C, являются люди, пытающиеся выглядеть умной. Вместо того, чтобы использовать в то время как (n! = 0) я покажу всем, насколько умный я путем записи , в то время как (n) , потому что это означает то же самое. Хорошо это делает в компиляторе, который Вы имеете, но поскольку Вы предложили Вас, более старая версия учителя не реализовала его тот же путь.

типичный пример А ссылается на индекс в массиве при постепенном увеличении его одновременно; Числа [я ++] = iPrime;

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

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

1
ответ дан 23 November 2019 в 04:27
поделиться

Я не спорил бы о том, лучше ли оригинал или современное определение '%', но кто-либо, кто пишет, что два оператора возврата в такую короткую функцию не должны преподавать C, программирующий вообще. Дополнительный возврат является оператором перехода, и мы не используем goto в C. Кроме того, код без нулевой проверки имел бы тот же результат, дополнительный возврат мешал читать.

0
ответ дан 23 November 2019 в 04:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: