あなたのコードは完璧です
あなたは絶対に正しく、先生は間違っています。結果にまったく影響を与えないため、このような複雑さを追加する理由はまったくありません。それもバグを紹介します。(下記参照)
まず、n
ゼロかどうかの個別のチェックは明らかに完全に不要であり、これは簡単に実現できます。正直に言って、先生がこれに異議を唱えているかどうか、先生の能力に実際に質問します。しかし、誰もが時々脳をおならすことができると思います。ただし、追加の行を追加することなく、少し見やすくするためにwhile(n)
変更する必要があると思いますwhile(n != 0)
。それはささいなことですが。
2つ目はもう少しわかりやすいですが、彼はまだ間違っています。
これは、C11標準6.5.5.p6で述べられていることです。
商a / bが表現可能な場合、式(a / b)* b + a%bはaに等しくなります。それ以外の場合、a / bとa%bの両方の動作は未定義です。
脚注はこう言っています:
これは、しばしば「ゼロへの切り捨て」と呼ばれます。
ゼロへの切り捨ては、の絶対値a/b
が(-a)/b
すべてのa
およびの絶対値と等しいことをb
意味します。これは、コードが完全に問題ないことを意味します。
Moduloは簡単な数学ですが、直感に反する場合があります
ただし、結果を二乗しているという事実はここでは実際に重要であるため、教師は注意する必要があるという点を持っています。a%b
上記の定義に従って計算することは簡単な数学ですが、直感に反する可能性があります。乗算と除算では、オペランドに等号がある場合、結果は正になります。しかし、それがモジュロになると、結果は最初のオペランドと同じ符号を持ちます。2番目のオペランドは符号にまったく影響しません。例えば、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
ANDであり、アーキテクチャが2の補数であり、かつ符号ビットが1ですべての値ビットが0であるビットパターンがトラップ値ではない場合(トラップ値なしで2の補数を使用することは非常に一般的です)、教師のコードは未定義の動作を生成しますラインでn = n * (-1)
。あなたのコードは-たとえ少しでも- 彼よりも優れています。そして、コードを不必要に複雑にし、絶対にゼロの値を取得することによって小さなバグを導入することを考えると、あなたのコードははるかに優れていると思います。
つまり、INT_MIN = -32768のコンパイルでは(結果の関数が<-32768または> 32767の入力を受信できない場合でも)、-(-32768i16)の結果が原因で、-32768 の有効な入力により未定義の動作が発生します。 16ビット整数として表現することはできません。(実際には、-(-32768i16)は通常-32768i16と評価され、プログラムは負の数を正しく処理するため、-32768はおそらく誤った結果を引き起こさないでしょう。
ただし、教師はn
[-10 ^ 7;の範囲である可能性があることを明確に述べています。10 ^ 7]。16ビット整数は小さすぎます。[少なくとも] 32ビット整数を使用する必要があります。を使用int
すると、int
必ずしも32ビット整数であるとは限らないことを除いて、彼のコードは安全に思えるかもしれません。16ビットアーキテクチャ用にコンパイルすると、両方のコードスニペットに欠陥があります。しかし、このシナリオINT_MIN
では彼のバージョンで上記のバグが再導入されるため、コードはさらに優れています。これを回避するには、どちらのアーキテクチャでも32ビット整数であるのlong
代わりに記述できますint
。A long
は、[-2147483647;の範囲内の任意の値を保持できることが保証されています。2147483647]。C11標準5.2.4.2.1 LONG_MIN
は-2147483648
しかし、許可される最大値(はい、最大値、負の数です)LONG_MIN
は2147483647
です。
コードにどのような変更を加えますか?
あなたのコードはそのままで問題ないので、これらは実際には不満ではありません。私が本当にあなたのコードについて何かを言う必要があるなら、それをほんの少し明確にすることができるいくつかの小さなことがあるようなものです。
- 変数の名前は少し良いかもしれませんが、理解しやすい短い関数なので、大したことではありません。
- 状態をから
n
に変更できますn!=0
。意味的には100%相当ですが、少し明確になります。
c
(名前をに変更しましたdigit
)の宣言をwhileループ内でのみ使用するため、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
が負の値を取得する可能性があるためですが、数字自体が正または負になることはありません。これを回避する方法はいくつかありますが、これは本当につまらないものであり、そのような細かい点は気にしません。特に、最後の桁の個別の関数は、それを取りすぎています。皮肉なことに、これは教師のコードが実際に解決することの1つです。
- 変数に変更
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
最初と同じように名前を付けます。その変数名は有用な情報を提供しませんが、一方で、誤解を招くものでもありません。
しかし、正直なところ、この時点で、より重要な作業に移る必要があります。:)
n = n * (-1)
とんでもない方法n = -n
です。学者だけがそれを考えさえするでしょう。冗長な括弧を追加することは言うまでもありません。