为什么这个平方根近似不起作用?

该程序基本上找到给定数字的平方根的近似值。 我不明白为什么它不起作用的问题。 该程序正在编译但从未运行。 它无限地计算一些东西。

public static void Main(string[] args) { Console.WriteLine("Enter number to find Square Root"); var num = Convert.ToInt32(Console.ReadLine()); var ans = SqaureRoot(num); Console.WriteLine("Square root of {0} : {1}",num,ans); } 

问题显然必须在这个方法中,在我看来,代码不会从while循环中退出,我只是看不出原因。 必须使用Newton Raphson方法解决此问题,仅接近平方根。 可能是因为牛顿拉夫森方程没有括号吗?

  public static double SqaureRoot(double a) { if (a < 0) throw new Exception("Can not sqrt a negative number"); double error = 0.00001; double x = 1; while (true) { double val = x * x; if (Math.Abs(a) <= error) return x; x = x / 2 + a / (2 * x); } } 

我不明白为什么它不起作用的问题。 …代码没有从while循环中退出,我只是看不出原因。

这是真的,你看不到它。 我一直在冰箱里看着牛奶 ,我看不到牛奶。 你有冰箱盲,但编码。 你正在直视一个明显的缺陷而你却看不到它。

这个问题在初学者和有经验的程序员中非常普遍。

它在人类中也很常见。 你倾向于阅读你认为的东西,即使它不是:

  /\ /I \ /LOVE\ /PARIS \ / IN THE \ /THE SPRING\ -------------- 

他们第一次阅读时,大多数人都读到“ 我在spring爱巴黎 ”,但这并不是它所说的

你正在分析的程序是有效的程序,但该程序只存在于你的头脑中。 你必须分析你实际编写并实际运行的程序! 这实质上是一种确认偏见的forms – 倾向于观察证据,certificate代码是正确的,并且没有看到与之相矛盾的证据。

经验丰富的开发人员每天都使用许多技术来打破代码盲目性并找到缺陷。

你正在使用的技术是让有新眼睛的人看看问题 。 这项技术有效; 我只需要看一下您的代码就可以看到明显的问题,因为我正在阅读您编写的代码 ,并且您正在阅读您认为自己编写的代码

但这是一种糟糕的技术,因为它会浪费别人的时间来解决你可以学会解决的琐碎问题。 今天是学习如何自己解决这些问题的好日子。

第一种技术是学习如何使用调试器

你想要做的是逐步调试调试器中的代码,一次一个语句,并在每个语句上预测该语句将做什么。 您必须在执行语句之前进行预测 。 然后执行声明,看看你的预测是否成真。 最终你会做出一个错误的预测,这就是你对程序的理解是错误的。

这也适用于我们的视错觉。 如果你告诉人们读一个单词,暂停并阅读下一个单词,就很容易看到错误。

下一个技术称为橡皮鸭调试

得到一个橡皮鸭或研究生或其他可以与之交谈的对象,然后大声解释你的程序的每一行 – 最好在调试时 – 并对该行的正确性给出过分详细的解释。 当你到达一条线路时,你无法certificate这一点,要么你不了解你自己的程序,要么线条错误。

你会觉得自己像一个白痴大声地对着橡皮鸭说话,但是让它发挥作用的一部分就是让你的大脑负责说话。 再次,这对我们的错觉起作用:如果你读出每个单词并大声说出句子,而不是阅读整个句子然后说出整个句子,那么错误就变得明显了。

特别是,要非常仔细地解释每个变量的用途和用法 。 在你的情况下,单独会很快找到问题,因为你有一个声明和写入但从未读过的变量。 写入但从未读过的变量是一个巨大的红旗; 这意味着你要么在你的机器中有一个无用的部分,或者更可能的是,你有一个不被误用的关键部分。 就像这里的情况一样。

在您的特定情况下,有一些特殊的技术。

  • 你的直觉给了你两个很大的提示:问题是一个无限循环,“可能是因为牛顿拉夫森方程没有括号吗?” 第一个直觉就是现场。 第二种直觉对我来说毫无意义,我不知道你在谈论什么。 但无论如何,要追平那些预感。

  • 您已将问题正确诊断为无限循环,因此请特别关注循环条件时关注您的注意力。 是否有意义? (提示:不。)再次,大声朗读。 它有助于。

  • 您正在编写标准算法的代码版本。 回到您对算法的描述,并validation算法中的每一步都在代码中的某处找到。 在原始算法中有一个不在你的程序中的操作,这就是bug的地方。

  • 重命名变量,使其名称更清晰地表示其概念。 error是错误的; 那不是错误。 那就是errorTolerancex应该是approximation 。 该误差由currentError = approximation * approximation - a; 如果你查看你的程序并问自己“ errorTolerancecurrentError相比在哪里?你很容易找到你的错误。如果你这样做, 你会写出更少的愚蠢错误

  • 概括。 很难看到你的错误,因为你没有阅读代码,你正在阅读它的意图。 你看一下代码,你看到的是:


 SOLVER { VALIDATE ARGUMENT INITIALIZE APPROXIMATION while (true) { if (WITHIN TOLERANCES) RETURN APPROXIMATION REFINE APPROXIMATION } } 

那段代码是对的 。 你看看if(Abs...并且看到“我正在检查我是否在容忍范围内”,这是正确的做这一步的地方,你甚至没有想到“但我是否正确地实施了它?”

但由于这是您希望代码采用的结构, 您实际上可以这样写

 class Solver { private double a; private double approximation; private Solver(double a) { this.a = a; } public static double Solve(double a) { Solver s = new Solver(a); s.Validate(); s.Initialize(); while (true) { if (s.WithinTolerances()) return s.approximation; s.Refine(); } } private void Validate() { ... } private void Initialize() { ... } private bool WithinTolerances() { ... } private void Refine() { ... } } 

当你有一个方法可以做一件事并且做得非常好时,你就不太可能犯错误 ,如果这个方法可以独立测试,那么你可以获得奖励积分。

请注意,上面我刚刚创建的类可能是一个抽象基类! 我们可以拥有一整套求解器,使用不同的技术解决不同的问题。

  • 你的代码一般都很草率。 SqaureRoot而不是SquareRootException而不是ArgumentException 。 等等。 别邋.. 养成制作每一行代码的习惯,你就要写出清晰和精确的模型。 这将使您更容易找到不可避免的错误,因为您不必躲过大错误的海洋,其中一些可能会掩盖更大的错误。

任何这些技术都可以快速找到您的错误。 立即学习所有这些知识

进一步阅读:

 if (Math.Abs(a) <= error) return x; 

a不会在循环内部发生变化,因此永远不会返回。

如评论中所述,您应该与错误术语Math.Abs(a-val) ,而不是Math.Abs(a) ,即初始值。

正如其他人所指出的那样,你永远不会满足return的条件。 考虑改变这个:

 if (Math.Abs(a) <= error) 

 if (Math.Abs(a - val) <= error)