[c] 제곱 숫자를 합산 할 때 음수 또는 0을 명시 적으로 처리해야합니까?

나는 최근에 수업에서 시험을 보았습니다. 문제 중 하나는 다음과 같습니다.

숫자 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 또는이어야한다 . 그래서 (댓글을 읽음으로써) 내 버전이 어떤 식 으로든 정답을 얻을 것이라고 생각합니다.



답변

주석에서 언급 된 토론 요약 :

  • 미리 테스트 할만한 이유가 없습니다 n == 0. while(n)테스트는 완벽하게이 사건을 처리합니다.
  • %음의 피연산자 결과 가 다르게 정의 되었을 때 선생님은 여전히 ​​초기에 익숙했을 것입니다 . 일부 오래된 시스템 (특히 Dennis Ritchie가 C를 개발 한 PDP-11의 초기 Unix 포함)에서 결과 a % b항상 범위 내에 [0 .. b-1]있었습니다. 즉, -123 % 10은 7이었습니다. 이러한 시스템에서 테스트 사전에 n < 0필요합니다.

그러나 두 번째 글 머리 기호는 이전에만 적용됩니다. C 및 C ++ 표준의 현재 버전에서 정수 나누기는 0쪽으로 잘 리도록 정의되므로 n % 10음수 일 n때도 마지막 음 자리수를 얻을 수 있습니다 n.

따라서 “무엇을 의미 while(n)하는가?” 라는 질문에 대한 답변 이다 “정확하게 같은 while(n != 0) , 그리고에 대한 대답 “윌 부정적인 제대로 코드가 작동뿐만 아니라 긍정적으로 n?” 이다 “그래, 현대의 아래, 컴파일러 표준이-준수.” 질문에 대한 대답은 “그러면 왜 강사가 그것을 표시 했습니까?” 아마도 그들은 1999 년 C와 2010 년 C ++에서 일어난 중대한 언어 재정의를 알지 못했을 것입니다.


답변

귀하의 코드는 완벽합니다

당신은 절대적으로 정확하고 선생님이 잘못되었습니다. 결과에 전혀 영향을 미치지 않기 때문에 추가 복잡성을 더할 이유가 전혀 없습니다. 버그도 소개합니다. (아래 참조)

첫째, n0 이면 별도의 검사 는 분명히 완전히 불필요하며 이는 매우 쉽게 실현됩니다. 솔직히 말해서, 나는 선생님이 이것에 대해 이의가 있다면 실제로 선생님의 능력에 의문을 제기합니다. 그러나 나는 모든 사람들이 때때로 뇌 방귀를 가질 수 있다고 생각합니다. 그러나 추가 라인 비용을 들이지 않고도 약간의 선명도를 추가 while(n)하기 while(n != 0)때문에 변경해야 한다고 생각합니다 . 그래도 사소한 일입니다.

두 번째는 좀 더 이해하기 쉽지만 여전히 잘못되었습니다.

이것이 C11 표준 6.5.5.p6의 내용입니다.

몫 a / b가 표현 가능한 경우, 식 (a / b) * b + a % b는 a와 같아야합니다. 그렇지 않으면 a / b 및 a % b의 동작이 정의되지 않습니다.

각주는 이렇게 말합니다.

이것을 종종 “0으로 자르기”라고합니다.

0으로 잘림은의 절대 값 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_MINAND 이고 아키텍처가 2의 보수이고 부호 비트가 1이고 모든 값 비트가 0 인 비트 패턴이 트랩 값이 아닌 경우 (트랩 값없이 2의 보수를 사용하는 것이 매우 일반적 임) 교사의 코드는 정의되지 않은 동작을 생성합니다. 줄에 n = n * (-1). 당신의 코드는 그의 코드 보다 약간 더 좋습니다 . 코드를 불필요하게 만들고 작은 제로 값을 얻음으로써 작은 버그를 도입하는 것을 고려할 때 코드가 훨씬 우수하다고 말합니다.

즉, INT_MIN = -32768 인 컴파일에서 (결과 함수가 <-32768 또는> 32767 인 입력을 수신 할 수 없지만) -32768 의 유효한 입력은-(-32768i16)의 결과로 인해 정의되지 않은 동작을 유발합니다. 16 비트 정수로 표현할 수 없습니다. (-32768i16)은 일반적으로 -32768i16으로 평가되고 프로그램은 음수를 올바르게 처리하기 때문에 실제로 -32768은 잘못된 결과를 발생시키지 않습니다. (SHRT_MIN은 컴파일러에 따라 -32768 또는 -32767 일 수 있습니다.)

그러나 선생님은 n[-10 ^ 7; 10 ^ 7]. 16 비트 정수가 너무 작습니다. 최소한 32 비트 정수를 사용해야합니다. 를 사용하면 int코드가 안전 해 보일 수 있지만 int반드시 32 비트 정수는 아닙니다. 16 비트 아키텍처 용으로 컴파일하면 두 코드 조각 모두에 결함이 있습니다. 그러나이 시나리오는 INT_MIN위에서 언급 한 버전으로 버그를 다시 소개하기 때문에 코드가 훨씬 좋습니다 . 이를 피하기 위해 아키텍처 long대신 int32 비트 정수인 대신 쓸 수 있습니다 . A long는 [-2147483647; 2147483647]. C11 표준 5.2.4.2.1 LONG_MIN 은 종종-2147483648그러나 허용되는 최대 값 (예, 최대 값, 음수) LONG_MIN2147483647입니다.

코드를 어떻게 변경 하시겠습니까?

귀하의 코드는 그대로이므로 실제로 불만이 아닙니다. 실제로 코드에 대해 말할 필요가 있다면 조금 더 명확하게 만들 수있는 작은 것들이 있습니다.

  • 변수의 이름은 조금 나을 수 있지만 이해하기 쉬운 짧은 함수이므로 크게 중요하지 않습니다.
  • 조건을에서 (으) n로 변경할 수 있습니다 n!=0. 의미 상 100 % 동일하지만 조금 더 명확합니다.
  • while 루프 내부에서만 선언 된 c(의 이름을 바꾼 digit) 선언을 while 루프 내부로 이동하십시오.
  • long전체 입력 세트를 처리 할 수 ​​있도록 인수 유형을 변경하십시오 .
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가 음수 값을 얻을 수 있지만 숫자 자체는 양수 또는 음수 가 아니기 때문에 약간 오해의 소지가 있습니다. 이 방법에는 몇 가지 방법이 있지만 이것은 정말 nitpicking이므로 작은 세부 사항은 신경 쓰지 않습니다. 특히 마지막 자리에 대한 별도의 기능이 너무 멀리 걸립니다. 아이러니하게도, 이것은 교사 코드가 실제로 해결하는 것 중 하나입니다.

  • 변수를 변경 sum += (digit * digit)하고 완전히 sum += ((n%10)*(n%10))생략하십시오 digit.
  • digit음수이면 부호를 변경하십시오 . 그러나 변수 이름을 이해하기 쉽게 코드를 더 복잡하게 만들지 않도록 강력히 조언합니다. 그것은 매우 강한 코드 냄새입니다.
  • 마지막 숫자를 추출하는 별도의 함수를 만듭니다. int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }다른 곳에서 해당 기능을 사용하려는 경우에 유용합니다.
  • c원래대로 이름을 지정하십시오 . 이 변수 이름은 유용한 정보를 제공하지는 않지만 오해의 소지가 없습니다.

그러나 솔직히 말하면이 시점에서 더 중요한 일로 넘어 가야합니다. 🙂


답변

나는 당신의 버전이나 선생님의 버전을 완전히 좋아하지 않습니다. 선생님의 버전은 당신이 올바르게 지적하는 추가 시험이 불필요합니다. C의 mod 연산자는 올바른 수학적 모드가 아닙니다. 음수 mod 10은 음의 결과를 생성합니다 (적절한 수학적 계수는 항상 음수가 아닙니다). 그러나 어쨌든 그것을 제곱하기 때문에 아무런 차이가 없습니다.

그러나 이것은 분명하지 않으므로 선생님의 수표가 아니라 왜 작동하는지 설명하는 큰 주석을 코드에 추가합니다. 예 :

/ * 참고 : 계수가 제곱되기 때문에 음수 값에 사용할 수 있습니다. * /


답변

참고 : 이 답변을 작성하면서 C를 사용하고 있음을 분명히했습니다. 내 대답의 대부분은 C ++에 관한 것입니다. 그러나 제목에 여전히 C ++이 있고 질문에 여전히 C ++ 태그가 지정되어 있기 때문에 다른 사람들에게 여전히 유용한 경우를 대비하여 답변을 선택했습니다. 특히 지금까지 본 대부분의 답변이 대부분 불만족하기 때문입니다.

현대 C ++에서 (참고 : C가 어디에 서 있는지 실제로 모르겠습니다) 교수님은 두 가지 측면에서 모두 잘못된 것 같습니다.

먼저이 부분이 바로 여기 있습니다 :

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

C ++에서 이것은 기본적으로 다음과 같습니다.

if (!n) {
        return 0;
}

그것은 당신의 시간이 다음과 같은 것을 의미합니다 :

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

즉, 어쨌든 while이 실행되지 않을 때 if에서 종료하기 때문에 루프 후와 if에서 수행하는 작업이 어쨌든 동등하므로 여기에 넣을 이유가 없습니다. 비록 이것이 어떤 이유로 든 다르지만 말해야하는 경우이 필요합니다.

실제로이 if 문은 내가 실수하지 않는 한 특히 유용하지 않습니다.

두 번째 부분은 물건이 털이 나오는 곳입니다.

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

문제의 핵심은 음수 계수의 출력이 출력하는 것입니다.

현대 C ++에서 이것은 대부분 잘 정의 된 것처럼 보입니다 .

이항 / 연산자는 몫을 산출하고, 이진 % 연산자는 첫 번째 표현식을 두 번째로 나눈 나머지를 산출합니다. / 또는 %의 두 번째 피연산자가 0이면 동작이 정의되지 않습니다. 정수 피연산자의 경우 / 연산자는 분수 부분을 버린 대수 지수를 산출합니다. 몫 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와 같아야합니다

그리고 다른 장소 :

정수가 나눠지고 나눗셈이 정확하지 않은 경우, 두 피연산자가 모두 양수이면 / 연산자의 결과는 대수 지수보다 작은 가장 큰 정수이고 % 연산자의 결과는 양수입니다. 피연산자가 음수 인 경우, / 연산자의 결과가 대수 지수보다 작은 최대 정수인지 대수 지수보다 큰 최소 정수인지 여부는 % 연산자 결과의 부호와 같이 구현 정의됩니다. 몫 a / b가 표현 가능한 경우, 식 (a / b) * b + a % b는 a와 같아야합니다.

ANSI C 또는 ISO C 중 어느 것이 -5 % 10이어야하는지 지정합니까?

네 C99에서도이 것은 당신에게 영향을 미치지 않는 것 같습니다. 방정식은 동일합니다.


답변

다른 사람들이 지적했듯이, 모든 심각한 C 프로그래머에 대해 “while (n)”이 작업을 수행한다는 것이 명백하기 때문에 n == 0에 대한 특수 처리는 넌센스입니다.

n <0의 동작은 그다지 명확하지 않으므로 이러한 두 줄의 코드를 보는 것이 좋습니다.

if (n < 0)
    n = -n;

또는 적어도 코멘트 :

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

솔직히, n이 음수 일 것이라고 생각한 시점은 언제입니까? 코드를 작성할 때 또는 선생님의 말을 읽을 때?


답변

이것은 내가 실패한 과제를 상기시킵니다.

90 년대로 돌아가 강사는 루프에 대해 싹 트고 있었고, 우리의 임무는 주어진 정수> 0에 대한 자릿수를 반환하는 함수를 작성하는 것이 었습니다.

예를 들어, 자릿수는 321입니다 3.

과제는 단순히 자릿수를 반환하는 함수를 작성한다고 말했지만 강의에서 다룰 때까지 10으로 나누는 루프를 사용할 것이라고 기대 했습니다 .

그러나 루프 사용은 명시 적으로 언급되지 않았으므로 I : took the log, stripped away the decimals, added 1전체 클래스 앞에서 램버트되었습니다.

요점은 과제의 목적은 강의 중에 배운 내용에 대한 이해를 테스트하는 것이 었습니다 . 제가받은 강의에서 컴퓨터 교사가 약간 바보라는 것을 알았습니다 (그러나 계획이있는 바보입니까?)


당신의 상황에서 :

C / C ++에서 숫자의 자릿수 합계를 반환하는 함수를 작성하십시오.

나는 분명히 두 가지 답변을 제공했을 것입니다 :

  • 정답 (숫자를 먼저 제곱) 및
  • 예를 유지하면서 틀린 대답, 단지 그를 행복하게 유지하기 위해 😉

답변

일반적으로 과제에서 코드가 작동하기 때문에 모든 마크가 제공되는 것은 아닙니다. 또한 솔루션을 읽기 쉽고 효율적이며 우아하게 만드는 마크를 얻었습니다. 이러한 것들이 항상 상호 배타적이지는 않습니다.

충분히 strees 할 수없는 것은 “의미있는 변수 이름 사용” 입니다.

귀하의 예에서는 큰 차이가 없지만 코드 라인이 수백만 줄인 프로젝트에서 작업하는 경우 매우 중요합니다.

C 코드로 보려는 또 다른 것은 영리 해 보이려고 노력하는 사람들입니다. while (n! = 0)을 사용 하는 대신 , 동일한 것을 의미하기 때문에 while (n) 을 쓰면 얼마나 영리한지 모든 사람에게 보여줄 것입니다. 글쎄, 그것은 당신이 가진 컴파일러에서하지만 당신이 제안한 것처럼 이전 버전은 같은 방식으로 구현하지 않았습니다.

일반적인 예는 배열에서 인덱스를 동시에 참조하면서 인덱스를 참조하는 것입니다. 숫자 [i ++] = iPrime;

이제 코드에서 작업하는 다음 프로그래머는 할당 전후에 증가하는지 알 수 있어야하므로 누군가가 과시 할 수 있습니다.

1MB의 디스크 공간은 화장지 롤보다 공간을 절약하기 위해 명확성을 추구하는 것보다 저렴합니다. 동료 프로그래머는 더 행복 할 것입니다.