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


220

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

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


120
n = n * (-1)우스운 방법이다 n = -n; 학계 만이 생각할 것입니다. 중복 괄호를 추가하지 마십시오.
user207421

32
주어진 구현이 사양에 맞는지 확인하기 위해 일련의 단위 테스트를 작성하십시오. 코드에 (기능적) 문제가있는 경우 특정 입력에 대해 잘못된 결과를 보여주는 테스트를 작성할 수 있어야합니다.
Carl

94
"숫자 제곱의 합"이 완전히 다른 세 가지 방식으로 해석 될 수 있다는 것이 흥미 롭습니다. (숫자가 123 인 경우, 해석 가능한 14, 18, 수율, 36)
안드레아스 Rejbrand

23
@ilkkachu : "숫자의 자릿수의 합". 음, "제곱수"는 123 ^ 2 = 15129이므로 분명히 "제곱수의 자릿수 합계"는 "15129의 자릿수 합계"이며, 이는 분명히 1 + 5 + 1 + 2 + 9입니다. = 18.
Andreas Rejbrand

15
n = n * (-1)? 응 ??? 당신의 교수가 찾고있는 것은 이것입니다 :`n = -n '. C 언어에는 단항 빼기 연산자가 있습니다.
Kaz

답변:


245

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

  • 미리 테스트 할만한 이유가 없습니다 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 ++에서 일어난 중대한 언어 재정의를 알지 못했을 것입니다.


39
"n == 0에 대해 미리 테스트 할 이유가 없습니다" -기술적으로는 맞습니다. 그러나 우리가 교수 환경에서 교수에 대해 이야기하고 있다는 것을 감안할 때, 그들은 우리보다 훨씬 더 간결함에 대한 명확성을 높이 평가할 것입니다. n == 0적어도 추가 테스트를 추가하면 그 경우 어떤 일이 발생했는지 모든 독자에게 즉시 명확하게 알 수 있습니다. 그것이 없으면 독자는 루프가 실제로 건너 뛴다는 것을 스스로 만족시켜야하며 s반환 된 기본값 은 올바른 것입니다.
ilkkachu

22
또한 교수는 학생이 알고 있고, 입력이 0으로 작동하는 이유와 방법에 대해 생각했음을 알고 싶어 할 수 있습니다 (즉, 실수로 올바른 값 반환하지 않음 ). 학생들은 그 경우에 일어날 일을 모르고 루프가 0 번 실행될 수 있다는 것을 모르는 학생들을 만났을 것입니다. 교수 환경을 다룰 때는 모든 가정과 모퉁이 사건에 대해 더 명확하게하는 것이 가장 좋습니다. ..
ilkkachu '10

36
@ilkkachu이 경우 교사는 테스트가 올바르게 수행되어야하는 작업을 전달해야합니다.
klutt

38
@ilkkachu 글쎄, 나는 일반적인 경우에 당신의 요점을 취한다. 왜냐하면 나는 단순히 교육 학적 배경이 아니라 간결함에 대한 명확성, 그리고 거의 항상 명확성을 높이 평가하기 때문이다. 그러나 그렇게 말하면 간결함 명확 하고 간혹 특수 사례로 코드 (및 적용 범위 분석)를 어지럽히 지 않고 일반 코드 엣지 케이스를 모두 처리하도록 메인 코드를 정렬 할 수 있다면 , 그것은 아름다운 것입니다! 초보자 수준에서도 높이 평가할만한 가치가 있다고 생각합니다.
Steve Summit

49
@ilkkachu이 인수에 의해 반드시 n = 1 등에 대한 테스트를 추가해야합니다. 특별한 것은 없습니다 n=0. 불필요한 브랜치와 합병증을 도입한다고해서 코드가 더 쉬워지는 것은 아닙니다. 이제는 일반적인 알고리즘이 정확하다는 것을 보여줄뿐만 아니라 모든 특수 사례에 대해 별도로 생각해야합니다.
Voo

107

귀하의 코드는 완벽합니다

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

첫째, 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원래대로 이름을 지정하십시오 . 이 변수 이름은 유용한 정보를 제공하지는 않지만 오해의 소지가 없습니다.

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


19
입력이 INT_MIN있고 아키텍처가 2의 보수 (매우 일반적 임)를 사용하는 경우 교사 코드는 정의되지 않은 동작을 생성합니다. 아야. 마크가 남을 것입니다. ;-)
Andrew Henle

5
뿐만 아니라 언급해야한다 (a/b)*b + a%b ≡ a, 영업 이익의 코드는 그 사실에 달려 /라운드 0에 가까워, 그 (-c)*(-c) ≡ c*c. 충분히 비 명백하기 때문에, 추가 검사가 모든 것을 보장하는 표준에도 불구하고 정당화한다고 주장한다. (물론 관련 표준 섹션을 연결하는 주석이 있어야하지만 스타일 가이드 라인이 다양하다는 주장도 마찬가지로 똑같이 주장 될 수 있습니다.)
leftaroundabout

7
@MartinRosenau 당신은 "아마"라고 말합니다. 이것이 실제로 일어나고 있는지 또는 표준이나 다른 것에 의해 허용되는지 아니면 방금 추측하고 있습니까?
klutt

6
@MartinRosenau : 좋습니다. 그러나이 스위치를 사용하면 더 이상 C가되지 않습니다. GCC / clang에는 내가 알고있는 ISA에서 정수 나누기를 깨는 스위치가 없습니다. 부호 비트를 무시해도 상수 제수의 경우 일반 곱셈 역수를 사용하여 속도를 높일 수 있습니다. (그러나 하드웨어 분할 명령어를 사용하는 친숙한 모든 ISA는 C99 방식으로 구현하여 0으로 잘 리므로 C %/연산자는 idivx86 또는 sdivARM 등으로 컴파일 할 수 있습니다 . 빠른 코드 세대 컴파일 시간 상수의 약수에 대한)
피터 코르

5
@TonyK AFIK, 그것이 일반적으로 해결되는 방법이지만 표준에 따르면 UB입니다.
klutt

20

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

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

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


9
C %는 부호있는 유형조차도 있기 때문에 나머지 라고하는 것이 가장 좋습니다 .
Peter Cordes

15
제곱은 중요하지만, 그것이 분명한 부분이라고 생각합니다. 지적해야 할 것은 (예를 들어) -7 % 10 실제로-7 3이
아니라는

5
"적절한 수학 계수"는 아무 의미가 없습니다. 올바른 용어는 "Euclidean modulo"(접미사를 명심하십시오!)이며 실제로 C의 %연산자가 아닙니다.
Jan Hudec

나는이 답변이 모듈로를 해석하는 여러 가지 방법의 문제를 해결하기 때문에 좋아합니다. 우연히 해석하거나 해석 할 수있는 물건을 두지 마십시오. 이것은 코드 골프가 아닙니다.
하퍼-복원 모니카

1
"적절한 수학 계수는 항상 음이 아닙니다" -실제로는 아닙니다. 모듈로 연산의 결과는 동등 클래스 이지만 결과를 해당 클래스에 속하는 가장 작은 음수가 아닌 숫자로 처리하는 것이 일반적입니다.
klutt

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에서도이 것은 당신에게 영향을 미치지 않는 것 같습니다. 방정식은 동일합니다.


1
인용 한 부분은이 답변을 지원하지 않습니다. "나머지의 부호가 구현 정의"의미하지 않는다 (-1)%10생산할 수 -1또는 1; 그것은 생산할 수있는 의미 -19, 그리고 후자의 경우에 (-1)/10생성됩니다 -1및 영업 이익의 코드는 종료하지 않습니다.
stewbasic

이에 대한 출처를 말씀해 주시겠습니까? 나는 (-1) / 10이 -1이라고 믿는 데 어려움을 겪고 있습니다. 또한 0이어야합니다. 또한 (-1) % 10 = 9이 지배 방정식을 위반하는 것 같습니다.
Chipster

1
@ Chiphip으로 시작한 (a/b)*b + a%b == a다음을 a=-1; b=10주고 (-1/10)*10 + (-1)%10 == -1. 지금, 만약 -1/10실제로 (-inf으로) 둥근 아래를 얻을, 우리는이 (-1/10)*10 == -10, 그리고 당신이 필요 (-1)%10 == 9일치에 대한 첫 번째 방정식을 위해. 다른 답변 상태 와 마찬가지로 이것이 현재 표준 내에서 작동하는 방식이 아니라 작동하는 방식입니다. 그것은 실제로 나머지의 부호에 관한 것이 아니라 나눗셈이 어떻게 돌아가고 나머지 방정식을 만족시켜야 하는지에 관한 것 입니다.
ilkkachu

1
@Chipster 소스는 인용 한 스 니펫입니다. 그주의 (-1)*10+9=-1선택이되도록 (-1)/10=-1하고 (-1)%10=9지배 방정식을 위반하지 않습니다. 반면에, 선택 (-1)%10=1은 어떻게 (-1)/10선택 되든 지배 방정식을 만족시킬 수 없다 . 정수 전혀 없다 q같은 그 q*10+1=-1.
stewbasic

8

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

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

if (n < 0) 
    n = -n;

또는 적어도 코멘트 :

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

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


1
N이 음수인지 여부에 관계없이 N 제곱은 양수입니다. 그렇다면 왜 사인을 먼저 제거해야합니까? -3 * -3 = 9; 3 * 3 = 9. 아니면 이것을 배운지 30 년 만에 수학이 바뀌 었습니까?
Merovex

2
@CB 공평하게도 테스트를 작성하는 동안 n이 음수라는 것을 알지 못했지만 다시 돌아 왔을 때 숫자가 음수 인 경우에도 while 루프를 건너 뛸 수 없다는 느낌이 들었습니다. 나는 컴퓨터에서 몇 가지 테스트를했고 회의론을 확인했다. 그 후이 질문을 게시했습니다. 그래서 아니요, 코드를 작성하는 동안 그렇게 깊이 생각하지 않았습니다.
user010517720

5

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

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

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

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

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

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


당신의 상황에서 :

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

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

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

5
그리고 세 번째 숫자의 합을 제곱?
kriss

@kriss-그래, 난 똑똑하지 않아 :-(
SlowLearner

1
또한 학생 시간에 너무 모호한 의견을 공유했습니다. 교사는 약간의 조작 라이브러리를 원했지만 내 코드에 놀랐고 작동하지 않는다고 말했습니다. 나는 그의 과제에서 엔디안을 정의하지 않았으며 다른 선택을하는 동안 더 낮은 비트를 비트 0으로 선택했다고 그에게 지적해야했습니다. 유일한 성가신 부분은 내가 말하지 않고 그 차이가 어디에 있는지 알아낼 수 있었어야한다는 것입니다.
kriss

1

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

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

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

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

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

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

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


2
나는 C에서 몇 번만 프로그래밍했으며 ++i평가 전에 i++증가하고 나중에 증가 한다는 것을 알고 있습니다. while(n)또한 일반적인 언어 기능입니다. 이와 같은 논리를 기반으로 같은 코드를 많이 보았습니다 if (foo == TRUE). 나는 동의합니다 : 변수 이름.
alan ocallaghan

3
일반적으로 그것은 나쁜 조언은 아니지만 방어 프로그래밍의 목적을 위해 기본 언어 기능 (사람들이 필연적으로 나올 것입니다)을 피하는 것은 과도합니다. 어쨌든 짧고 명확한 코드는 더 읽기 쉽습니다. 우리는 여기서 미친 펄에 대해 이야기하지 않고 하나의 라이너를 강타하는 것이 아니라 실제로 기본적인 언어 기능을 제공합니다.
alan ocallaghan

1
확실하지 않습니다.이 답변에 대한 downvotes가 무엇을 주 었는지. 나에게 여기에 언급 된 모든 것은 진실하고 중요하며 모든 개발자에게 좋은 조언입니다. 특히 스마트 엉덩이 프로그래밍에도 불구하고, 일부는 while(n)(I "좋아요"그것에 대해 최악의 예가 아니다 if(strcmp(one, two))이상)
카이 Huppmann

1
게다가, 당신은 정말 사이의 차이를 모르는 사람이 못하게해야 i++하고 ++i생산에 사용되어야한다 C 코드를 수정합니다.
klutt

2
@PaulMcCarthy 우리는 코드를 쉽게 읽을 수 있습니다. 그러나 우리는 그 의미에 동의하지 않습니다. 또한 객관적이지 않습니다. 한 사람이 읽기 쉬운 것은 다른 사람에게는 어려울 수 있습니다. 성격과 배경 지식이 이것에 큰 영향을 미칩니다. 내 주요 요점은 맹목적으로 일부 규칙을 따르면 가독성을 극대화하지 못한다는 것입니다.
klutt

0

나는 '%'의 원래 정의 나 현대적인 정의가 더 나은지에 대해서는 논쟁하지 않을 것입니다. 그러나 짧은 함수에 두 개의 return 문을 쓰는 사람은 C 프로그래밍을 전혀 가르쳐서는 안됩니다. 추가 반환은 goto 문이며 C에서는 goto를 사용하지 않습니다. 또한 제로 검사가없는 코드는 동일한 결과를 가져 오므로 추가 반환으로 인해 읽기가 더 어려워졌습니다.


4
"추가 반품은 goto 명세서이며 C에서는 goto를 사용하지 않습니다." -그것은 매우 광범위한 일반화와 매우 넓은 범위의 조합입니다.
SS Anne

1
"우리"는 C에서 goto를 사용합니다. 아무 문제가 없습니다.
klutt

1
또한, 기능 등으로 아무것도 잘못있다int findChar(char *str, char c) { if(!str) return -1; int i=0; while(str[i]) { if(str[i] == c) return i; i++; } return -1; }
klutt

1
@PeterKrassoi 나는 읽기 어려운 코드를 옹호하지는 않지만 간단한 goto 또는 여분의 return 문을 피하기 위해 코드가 완전한 혼란처럼 보이는 수많은 예를 보았습니다. 다음은 goto를 적절히 사용하는 코드입니다. 코드를 쉽게 읽고
다루기

1
@PeterKrassoi이 버전도 보여주세요 : pastebin.com/qBmR78KA
klutt

0

문제 설명은 혼란 스럽지만 숫자 예제 는 숫자의 자릿수 합계의 의미를 명확하게합니다 . 개선 된 버전은 다음과 같습니다.

정수를 취 C 및 C ++의 공통 서브셋의 함수 작성 n범위 [-10 7 10 7 ] 경우 다음과베이스 (10)의 복귀의 표현의 숫자의 제곱의 합이 실시 예 n이고 123, 함수를 14(1 2 + 2 2 + 3 2 = 14)를 반환해야합니다 .

작성한 기능은 두 가지 세부 사항을 제외하고는 괜찮습니다.

  • 인수는 C 표준에 의해 long형식 long이 최소 31 개의 값 비트를 갖도록 보장 되므로 지정된 범위의 모든 값을 수용 할 수있는 형식 을 가져야하므로 [-10 7 , 10 7 ]의 모든 값을 표시하기에 충분한 범위 입니다. (유형 int은 최대 값이 반환 유형 인 경우 충분합니다 568.)
  • %음의 피연산자 의 동작은 직관적이지 않으며 C99 Standard 및 이전 버전에 따라 사양이 다양합니다. 부정적인 입력에도 접근 방식이 유효한 이유를 문서화해야합니다.

다음은 수정 된 버전입니다.

int sum_of_digits_squared(long n) {
    int s = 0;

    while (n != 0) {
        /* Since integer division is defined to truncate toward 0 in C99 and C++98 and later,
           the remainder of this division is positive for positive `n`
           and negative for negative `n`, and its absolute value is the last digit
           of the representation of `n` in base 10.
           Squaring this value yields the expected result for both positive and negative `c`.
           dividing `n` by 10 effectively drops the last digit in both cases.
           The loop will not be entered for `n == 0`, producing the correct result `s = 0`.
         */
        int c = n % 10;
        s += c * c;
        n /= 10;
    }
    return s;
}

교사의 답변에는 여러 가지 결함이 있습니다.

  • 유형 int의 값 범위가 충분하지 않을 수 있습니다.
  • 특별한 값을 요구할 필요는 없습니다 0.
  • 음수 값을 부정하면 필요하지 않으며에 대해 정의되지 않은 동작이있을 수 있습니다 n = INT_MIN.

문제 설명 (C99 및의 값 범위)에 추가 제약 조건이 주어지면 n첫 번째 결함 만 문제입니다. 추가 코드는 여전히 올바른 답변을 생성합니다.

이 시험에서 좋은 점수를 받아야하지만, 음의 문제에 대한 이해를 나타 내기 위해 필기 시험에서 설명이 필요합니다 n. 구술 시험에서, 당신은 질문을 받았을 것이고 당신의 대답은 그것을 못 박았을 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.