热门标签 | HotTags
当前位置:  开发笔记 > 开发工具 > 正文

求平方和时,是否需要显式处理负数或零?

如何解决《求平方和时,是否需要显式处理负数或零?》经验,为你挑选了5个好方法。

我最近在课堂上做了一个测试。问题之一是:

给定一个数字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或更高版本。因此,我猜(通过阅读注释)我的版本将以任何方式获得正确的答案。



1> Steve Summit..:

总结评论中一直渗入的讨论:

没有充分的理由提前进行测试n == 0。该while(n)测试将完美地处理该情况。

%负数操作数的结果定义不同时,您的老师可能仍旧习惯了。在某些老的系统(包括,特别是,早期的Unix上的PDP-11,其中丹尼斯里奇最初开发C),结果a % b的范围内[0 .. b-1],这意味着-123%10为7。在这样的系统中,测试事先n <0需要。

但是第二个符号仅适用于较早的时间。在C和C ++标准的当前版本中,整数除法被定义为向0截断,因此事实证明即使在为负数的情况下,n % 10也可以保证给您(可能为负数)的最后一位数字。nn

因此,“……的含义是while(n)什么?”这个问题的答案是什么“完全一样while(n != 0),而回答“愿意为负此代码正常工作,以及积极的n?” “在任何符合标准的现代编译器下都是。” 问题的答案:“那么,老师为什么要把它记下来?” 可能是他们没有意识到1999年C和2010年左右C发生的重大语言重新定义。


@ilkkachu通过该参数,您当然还应该添加n = 1的测试,依此类推。n = 0没什么特别的。引入不必要的分支和复杂性并不会使代码变得更容易,反而使其变得更困难,因为现在您不仅必须证明通用算法是正确的,而且还必须分别考虑所有特殊情况。
_“没有充分的理由提前测试n == 0” _-从技术上讲,这是正确的。但是考虑到我们是在教学环境中谈论一位教授,他们可能比我们更欣赏简洁而不是简洁。为“ n == 0”添加额外的测试至少会使所有读者立即并完全明白在这种情况下会发生什么。没有它,读者必须使自己满意,确实确实跳过了循环,并且返回的默认值s是正确的。
@ilkkachu好吧,在一般情况下,我同意你的观点,因为我非常欣赏简洁,而不是为了简洁起见,而且几乎一直都在这样做,而不必只是出于教学目的。但是话虽如此,有时还是简洁明了,如果您可以安排主代码同时处理一般情况和边缘情况,而又不会因特殊情况而使代码(和覆盖率分析)混乱对于边缘情况,那是一件美丽的事!我认为即使在初学者的水平上也值得赞赏。
@ilkkachu如果是这样,那么老师应该分发一项需要这种测试才能正常工作的任务。
同样,教授可能想知道学生已经知道并且已经考虑了为什么以及在输入为零的情况下函数的行为(即,它没有返回_by意外_的正确值)。他们可能遇到了一些学生,这些学生不知道在那种情况下会发生什么,循环可以运行零次,等等。当您在处理教学设置时,最好对所有假设和极端情况更加清楚。 ..
老师要求学生分发单元测试例程和他们编写的代码怎么样?里面会有一些“ assert(sum_of_digits_squared(0)== 0)”和“ assert(sum_of_digits_squared(-3)== 9)”,这样老师可以看到学生已将案件考虑在内,并且生产代码将是免费的,只要像专业人士一样简短即可。UT例程在不符合标准的实现上将失败。学生们会养成良好的习惯,恕我直言。
因此,我认为(希望)如果OP在其代码中添加注释,他将获得更多的荣誉,这清楚地表明他已经考虑了所有可能的输入及其后果。例如:`while(n){//处理n = 0的情况,因为循环将被跳过并且s已经设置为0`。这样的注释听起来很愚蠢,但是它们向老师展示了学生的想法,即使对于导致代码减少的深层思想也可以给出一些要点。
@Frodyne是的,您必须证明自己知道代码为什么正确。但是正确的方法是通过单元测试或评论或类似的方式。并非通过编写使其他编码人员想知道您是否甚至不知道0为假而其他所有都为真的代码。
@SteveSummit我基本上同意你的看法。是的,OP的代码更小,同样清晰,没有不必要的检查,甚至(如@klutt所指出的)可以正确处理极端情况,因此OP的代码比他的教授更好。然而!在教学环境中,仅仅获得正确答案是不够的,您还必须证明自己知道为什么它是正确答案。OP代码的反面非常简单,以至于知识较少的学生可以编写相同的东西,甚至不考虑“ n = 0”或“ n <0”。继续...

2> klutt..:

您的代码非常好

你是绝对正确的,你的老师是错误的。绝对没有理由增加这种额外的复杂性,因为它根本不影响结果。它甚至引入了一个错误。(见下文)

首先,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)/ball 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_MINAND且体系结构为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_MIN2147483647

我将对您的代码进行哪些更改?

您的代码就可以了,所以这些并不是真正的抱怨。如果我真的真的需要对您的代码说些什么,那就更像是,有些小事情可能会使它变得更加清晰。

变量的名称可能会更好一些,但是它是一个易于理解的简短函数,因此没什么大不了的。

您可以将条件从更改nn!=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像最初一样命名即可。该变量名不会提供任何有用的信息,但是另一方面,它也不会引起误解。

但老实说,在这一点上,您应该继续进行更重要的工作。:)


*如果输入是“ INT_MIN”,并且架构使用二进制补码(这是很常见的),那么您的教师代码将产生未定义的行为。那会留下痕迹。;-)
@MartinRosenau你说“可能”。您确定这确实在发生,还是标准或某些东西允许这样做,还是您只是在推测?
@MartinRosenau:好的,但是使用这些开关将使其不再是C。GCC / clang没有任何开关可以中断我知道的任何ISA上的整数除法。即使忽略符号位,也可以使用常数除数的常规乘法逆来提高速度。(但是我熟悉的所有具有硬件除法指令的ISA都以C99方式实现它,并向零截断,因此C`%`和`/`运算符可以在x86或`sdiv`上编译为`idiv`。在ARM或其他任何工具上。但这与编译时常数除数的更快代码生成无关。
应该提到的是,除了`(a / b)* b + a%b≡a`外,OP的代码还取决于`/`舍入为零,而`(-c)*(- c)≡c * c`。可以争论的是,尽管标准地保证了所有这些,但是额外的检查是合理的,因为它是足够明显的。(当然也可以说,应该有一条注释链接相关的标准部分,但样式准则会有所不同。)
@TonyK AFIK,通常是这样解决的,但按照标准是UB。
@AndrewHenle我同意。这是理论问题。但是再看一次。我发现教师(和OP:s)使用的数据类型不能保证能够代表输入集中的所有值。:D
@AndrewHenle是的,当我的脑海里涌现出一连串的想法时,我感到非常高兴。我很希望OP可以向他的老师证明这一点。XD
这也是一些非常好的bug发现。我只是希望老师不是惩罚学生证明自己错误的人之一。
@AndrewHenle让我们希望如此。这个示例确实显示了在处理低级语言时预见所有内容可能非常棘手。我不会怪任何人错过这个错误。
是的,但是“没有陷阱值的二进制补码”是什么?今天所有CPU中有99.9999999%?([实际上可能很高,如果不是更高的话)(https://superuser.com/questions/1137182/is-there-any-existing-cpu-implementation-which-uses-ones-complement)... )
另一个好收获。我认为您可以通过销售代码回顾服务来赚钱。;-)但是,从理论上讲,`int32_t`是可选类型-`int_least32_t`和`int_fast32_t`是必需的类型。

3> Lee Daniel C..:

我不完全喜欢您的版本或老师的版本。老师的版本不需要您正确指出的额外测试。C的mod运算符不是正确的数学mod:负数mod 10会产生负结果(正确的数学模数始终为非负数)。但是既然您要对它进行平方运算,就没有区别。

但这远非显而易见,因此我将在您的代码中添加的不是老师的检查,而是一个大注释,说明了它为什么起作用。例如:

/ *注意:这适用于负值,因为模数平方* /


平方很重要,但是我认为这是显而易见的部分。应该指出的是(例如)“-7%10” *实际上是“ -7” *,而不是3。
C的`%`最好称为* remainder *,因为即使是有符号类型,也是如此。
“适当的数学模量”没有任何意义。正确的术语是“欧几里德模”(请注意后缀!),这的确是C的`%`运算符所没有的。

4> Chipster..:

注意:在我编写此答案时,您确实已经说明您正在使用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中,这似乎也不会影响您。等式是相同的。



5> 小智..:

正如其他人指出的那样,对n == 0的特殊处理是无稽之谈,因为对于每个认真的C程序员来说,显然“ while(n)”可以胜任。

n <0的行为不是那么明显,这就是为什么我更愿意看到这两行代码:

if (n <0) 
    n = -n;

或至少一个评论:

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

老实说,您什么时候开始考虑n可能为负?编写代码或阅读老师的言论时?


@CB坦白说,我什至没有注意到在编写测试时n可能为负,但是当返回时,我只是感觉即使循环数为负,也不会跳过while循环。我在计算机上做了一些测试,证实了我的怀疑。之后,我发布了这个问题。所以,不,我在编写代码时没有那么深入地思考。
推荐阅读
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 本教程详细介绍了如何使用 TensorFlow 2.0 构建和训练多层感知机(MLP)网络,涵盖回归和分类任务。通过具体示例和代码实现,帮助初学者快速掌握 TensorFlow 的核心概念和操作。 ... [详细]
  • Coursera ML 机器学习
    2019独角兽企业重金招聘Python工程师标准线性回归算法计算过程CostFunction梯度下降算法多变量回归![选择特征](https:static.oschina.n ... [详细]
  • Redux入门指南
    本文介绍Redux的基本概念和工作原理,帮助初学者理解如何使用Redux管理应用程序的状态。Redux是一个用于JavaScript应用的状态管理库,特别适用于React项目。 ... [详细]
  • 本文总结了优化代码可读性的核心原则与技巧,通过合理的变量命名、函数和对象的结构化组织,以及遵循一致性等方法,帮助开发者编写更易读、维护性更高的代码。 ... [详细]
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • 本文回顾了2017年的转型和2018年的收获,分享了几家知名互联网公司提供的工作机会及面试体验。 ... [详细]
  • Python 工具推荐 | PyHubWeekly 第二十一期:提升命令行体验的五大工具
    本期 PyHubWeekly 为大家精选了 GitHub 上五个优秀的 Python 工具,涵盖金融数据可视化、终端美化、国际化支持、图像增强和远程 Shell 环境配置。欢迎关注并参与项目。 ... [详细]
  • 目录一、salt-job管理#job存放数据目录#缓存时间设置#Others二、returns模块配置job数据入库#配置returns返回值信息#mysql安全设置#创建模块相关 ... [详细]
  • 本文详细介绍了福昕软件公司开发的Foxit PDF SDK ActiveX控件(版本5.20),并提供了关于其在64位Windows 7系统和Visual Studio 2013环境下的使用方法。该控件文件名为FoxitPDFSDKActiveX520_Std_x64.ocx,适用于集成PDF功能到应用程序中。 ... [详细]
  • ZooKeeper集群脑裂问题及其解决方案
    本文深入探讨了ZooKeeper集群中可能出现的脑裂问题,分析其成因,并提供了多种有效的解决方案,确保集群在高可用性环境下的稳定运行。 ... [详细]
  • Java 实现二维极点算法
    本文介绍了一种使用 Java 编程语言实现的二维极点算法。该算法用于从一组二维坐标中筛选出极点,适用于需要处理几何图形和空间数据的应用场景。文章不仅详细解释了算法的工作原理,还提供了完整的代码示例。 ... [详细]
  • 深入解析Serverless架构模式
    本文将详细介绍Serverless架构模式的核心概念、工作原理及其优势。通过对比传统架构,探讨Serverless如何简化应用开发与运维流程,并介绍当前主流的Serverless平台。 ... [详细]
  • 如何使用Ping命令来测试网络连接?当网卡安装和有关参数配置完成后,可以使用ping命令来测试一下网络是否连接成功。以winXP为例1、打开XP下DOS窗口具体操作是点击“开始”菜 ... [详细]
  • 苹果系统频繁弹窗提示无法验证服务器身份?竟是网易邮箱证书过期所致
    近日,众多苹果用户发现iOS、iPadOS和macOS系统频繁弹出无法验证服务器身份的警告。问题根源在于网易邮箱未能及时更新其数字证书,导致原证书过期后无法被信任。 ... [详细]
author-avatar
台艾辉_435
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有