我最近在课堂上做了一个测试。问题之一是:
给定一个数字n,用C / C ++编写一个函数,该函数返回数字平方的数字总和。(以下内容很重要)。的范围的Ñ为[ - (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)
测试将完美地处理该情况。
当%
负数操作数的结果定义不同时,您的老师可能仍旧习惯了。在某些老的系统(包括,特别是,早期的Unix上的PDP-11,其中丹尼斯里奇最初开发C),结果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发生的重大语言重新定义。
你是绝对正确的,你的老师是错误的。绝对没有理由增加这种额外的复杂性,因为它根本不影响结果。它甚至引入了一个错误。(见下文)
首先,n
显然完全不需要单独检查if 是否为零,这很容易实现。老实说,如果您的老师对此有异议,我实际上会质疑他的能力。但是我想每个人都会时不时放屁。但是,我确实while(n)
应该将其更改为,while(n != 0)
因为它增加了一点额外的清晰度,甚至不需要花费额外的时间。不过这是小事。
第二个更容易理解,但他仍然错了。
这就是C11标准6.5.5.p6所说的:
如果商a / b是可表示的,则表达式(a / b)* b + a%b等于a;否则,a / b和a%b的行为均未定义。
脚注说:
这通常称为“向零截断”。
截断为零意味着for的绝对值a/b
等于(-a)/b
all a
和的绝对值b
,这又意味着您的代码非常正确。但是,您的老师确实有一点要注意,因为在这里对结果求平方实际上是至关重要的。a%b
根据上述定义进行计算很容易,但是这可能违反您的直觉。对于乘法和除法,如果操作数具有相等的符号,则结果为正。但是,对于模,结果与第一个操作数的符号相同。例如,7%3==1
但是(-7)%(-3)==(-1)
。这是一个演示它的片段:
$ cat > main.c
#include
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的输入),-32768 的有效输入也会导致未定义的行为,因为-(-32768i16)的结果不能表示为16位整数。(实际上,-32768可能不会导致错误的结果,因为-(-32768i16)通常计算结果为-32768i16,并且您的程序正确处理了负数。)(取决于编译器,SHRT_MIN可以是-32768或-32767。)
但是您的老师明确指出n
可以在[-10 ^ 7; 10 ^ 7]。16位整数太小;您将必须使用[至少] 32位整数。使用int
似乎可以使他的代码安全,除非那int
不一定是32位整数。如果针对16位体系结构进行编译,则您的两个代码段都存在缺陷。但是您的代码仍然要好得多,因为这种情况重新引入了上述错误INT_MIN
及其版本。为了避免这种情况,您可以编写,long
而不是int
,这是任一体系结构上的32位整数。long
保证A 可以保存[-2147483647; 2147483647]。C11标准5.2.4.2.1 LONG_MIN
通常是-2147483648
但允许的最大值(是,最大值,它是一个负数)LONG_MIN
为2147483647
。
您的代码就可以了,所以这些并不是真正的抱怨。如果我真的真的需要对您的代码说些什么,那就更像是,有些小事情可能会使它变得更加清晰。
变量的名称可能会更好一些,但是它是一个易于理解的简短函数,因此没什么大不了的。
您可以将条件从更改n
为n!=0
。从语义上讲,它是100%等效的,但它使它更加清晰。
将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
可以获取负值,但是数字本身从不为正或为负。有几种解决方法,但这确实很挑剔,我不会在意这么小的细节。尤其是最后一位数字的单独功能过于复杂。具有讽刺意味的是,这是您的老师的代码真正解决的问题之一。
更改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:负数mod 10会产生负结果(正确的数学模数始终为非负数)。但是既然您要对它进行平方运算,就没有区别。
但这远非显而易见,因此我将在您的代码中添加的不是老师的检查,而是一个大注释,说明了它为什么起作用。例如:
/ *注意:这适用于负值,因为模数平方* /
注意:在我编写此答案时,您确实已经说明您正在使用C。我的答案大部分是关于C ++的。但是,由于您的标题仍然是C ++,并且该问题仍被标记为C ++,所以我还是选择了答案,以防其他人仍然有用,尤其是因为到目前为止我所看到的大多数答案都不尽人意。
在现代C ++中(注意:我真的不知道C在这个方面的立场),您的教授在这两个方面似乎都是错误的。
首先是这部分在这里:
if (n == 0) { return 0; }
在C ++中,这基本上与以下内容相同:
if (!n) { return 0; }
这意味着您的while相当于这样的事情:
while(n != 0) { // some implementation }
这意味着由于您只是退出了if而无论如何都不会执行,因此确实没有理由将if放在这里,因为循环后的操作和if的操作都是等效的。尽管我应该说这是出于某些原因,但它们与众不同,但是如果需要,您需要使用它。
所以说真的,除非我弄错了,否则这个if语句并不是特别有用。
第二部分是毛茸茸的地方:
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
还有另一个地方:
当整数被除法并且除法不精确时,如果两个操作数都为正,则/运算符的结果为小于代数商的最大整数,并且%运算符的结果为正。如果任一操作数为负,则/运算符的结果是小于代数商的最大整数,还是大于代数商的最小整数,由%定义。如果商a / b是可表示的,则表达式(a / b)* b + a%b等于a。
ANSI C或ISO C是否指定了-5%10?
是的。即使在C99中,这似乎也不会影响您。等式是相同的。
正如其他人指出的那样,对n == 0的特殊处理是无稽之谈,因为对于每个认真的C程序员来说,显然“ while(n)”可以胜任。
n <0的行为不是那么明显,这就是为什么我更愿意看到这两行代码:
if (n <0) n = -n;
或至少一个评论:
// don't worry, works for n <0 as well
老实说,您什么时候开始考虑n可能为负?编写代码或阅读老师的言论时?