훌륭한 수학자도 실수를 한다

WBOY
풀어 주다: 2024-08-09 18:38:02
원래의
175명이 탐색했습니다.

私たちは数学が精度の科学であることを知っています。インタラクティブな数学学習ソフトウェアである GeoGebra についても同じことが言えますか? PVS-Studioを使ってプロジェクトのソースコードを解析してみましょう!

Even great mathematicians make mistakes

導入

大学でコンピューター サイエンスをどのように学んだか覚えていますか?これらすべての行列とベクトルの乗算、多項式、内挿、外挿...これらの恐ろしい数式を、別の実験レポートではなく実際のプロジェクトで見たらどうなるでしょうか?このようなコードベースの問題を掘り下げてみるとどうなるでしょうか? PVS-Studio を実行して、数学の教科書の埃を払うことをお勧めします。なぜ教科書なのか?見せてあげましょう

数学の課題

このようなプログラムのソース コードを調査する際の主な課題の 1 つは、何が起こっているかを理解することです。アナライザー レポートを確認する際、警告が実際の問題を示しているのかどうかという疑問がありました。

次の断片を見てみましょう:

リーリー

次の PVS-Studio 警告が表示されます:

V6072 2 つの類似したコードの断片が見つかりました。おそらく、これはタイプミスであり、「a」の代わりに「b」変数を使用する必要があります。 AlgoTriangularDF.java 145、AlgoTriangularDF.java 146、AlgoTriangularDF.java 147、AlgoTriangularDF.java 148

本当にタイプミスですか?簡単な調査の結果、正しい公式が見つかったら、すべてが正しく記述されていると言えます。

コード部分は三角分布、つまりこの分布の確率密度関数 (PDF) を評価します。次の式が見つかりました:

Even great mathematicians make mistakes

それでは、コードを見てみましょう。

ここで* fv* は関数変数です。wrapwrapperを返し、その後、必要な数学的演算が実行されます。multiplyメソッドとmultiplyRメソッドの両方があることに注目するのは興味深いことです。 2 番目の方法では、乗算が常に可換であるとは限らないため、Rrightを表し、オペランドを交換します。

したがって、2 番目の式の結果は

branchAToModeに書き込まれ、4 番目の式の結果はbranchModeToBに書き込まれます。また、

branchModeToB

で分子と分母の符号が変更されていることにも気付きました。次の式が得られます:

Even great mathematicians make mistakes式の値は変更されませんでした。

そこで、私たちは受け取った警告のいくつかを理解するために数学的知識を更新しました。コードに実際のエラーがあるかどうかを特定するのは難しくありませんが、代わりにここに何が存在するのかを理解するのは困難です。

エラー

失われたコードセグメント

簡単なことから始めて、次の方法を見てみましょう:

リーリー
誰かが

s[2]

s[3]に置き換えるのを忘れていることがわかります。最後の行の効果はその輝きにあります。これは伝説的で、あまりにも一般的なコピー&ペーストのエラーです。その結果、4 番目の配列項目が欠落しており、null!になります。V6033 同じキー「2」を持つ項目はすでに変更されています。 AlgoPolyhedronNetPrism.java 376、AlgoPolyhedronNetPrism.java 377

価値観についてはどうでしょうか?

次に、次のコード スニペットで問題を特定してください:

リーリー
素晴らしい景色ですね!読むのは楽しいですが、このメソッドは 66 行目で始まり 404 行目で終わるため、これはほんの一部に過ぎません。アナライザーは、V6033 タイプの警告を 50 件発行します。これらの警告の 1 つを簡単に見てみましょう:

V6033 同じキー「〜」を持つ項目が既に追加されています。 MathMLParser.java 229、MathMLParser.java 355

余分な断片を削除して、警告に言及している表現を見てみましょう:

リーリー
でも、面白いですよ。メソッド呼び出し間の間隔はどれくらいですか? 126行あります。そうですね、このようなエラーを手作業で見つけることができれば幸いです!

ほとんどはキーと値が重複しています。ただし、いくつかのケースは上記の例と似ており、開発者が値を別の値で上書きします。どれを使えばいいでしょうか?

円または楕円

リーリー

楕円のメソッドは、楕円と円の両方で呼び出されます。確かに、円は楕円でもあるため、これで問題ないと考えることができます。ただし、このクラスには

updateCircle

メソッドもあります。では、それは何でしょうか?もう少し詳しく見てみましょうすべては

DrawConic3D

クラスで行われます。楕円と円のメソッドは次のとおりです:

protected void updateCircle(PlotterBrush brush) { if (visible == Visible.CENTER_OUTSIDE) { longitude = brush.calcArcLongitudesNeeded(e1, alpha, getView3D().getScale()); brush.arc(m, ev1, ev2, e1, beta - alpha, 2 * alpha, longitude); } else { longitude = brush.calcArcLongitudesNeeded(e1, Math.PI, getView3D().getScale()); brush.circle(m, ev1, ev2, e1, longitude); } } protected void updateEllipse(PlotterBrush brush) { if (visible == Visible.CENTER_OUTSIDE) { brush.arcEllipse(m, ev1, ev2, e1, e2, beta - alpha, 2 * alpha); } else { brush.arcEllipse(m, ev1, ev2, e1, e2, 0, 2 * Math.PI); } }
로그인 후 복사

Well... It doesn't give that much confidence. The method bodies are different, but nothing here indicates that we risk displaying unacceptable geometric objects if the method is called incorrectly.

Could there be other clues? A whole single one! TheupdateCirclemethod is never used in the project. Meanwhile,updateEllipseis used four times: twice in the first fragment and then twice inDrawConicSection3D, the inheritor class of* DrawConic3D*:

@Override protected void updateCircle(PlotterBrush brush) { updateEllipse(brush); } @Override protected void updateEllipse(PlotterBrush brush) { // .... } else { super.updateEllipse(brush); } }
로그인 후 복사

ThisupdateCircleisn't used, either. So,updateEllipseonly has a call in its own override and in the fragment where we first foundupdateForItSelf. In schematic form, the structure looks like as follows:

Even great mathematicians make mistakes

On the one hand, it seems that the developers wanted to use the all-purposeupdateEllipsemethod to draw a circle. On the other hand, it's a bit strange thatDrawConicSection3Dhas theupdateCirclemethod that callsupdateEllipse. However,updateCirclewill never be called.

It's hard to guess what the fixed code may look like if the error is actually in the code. For example, ifupdateCircleneeds to callupdateEllipseinDrawConicSection3D, butDrawConic3Dneeds a more optimized algorithm for the circle, the fixed scheme might look like this:

Even great mathematicians make mistakes

So, it seems that developers once wroteupdateCircleand then lost it, and we may have found its intended "home". Looks like we have discovered the ruins of the refactoring after which the developers forgot about the "homeless" method. In any case, it's worth rewriting this code to make it clearer so that we don't end up with so many questions.

All these questions have arisen because of the PVS-Studio warning. That's the warning, by the way:

V6067 Two or more case-branches perform the same actions. DrawConic3D.java 212, DrawConic3D.java 215

Order of missing object

private void updateOrdering(GeoElement geo, ObjectMovement movement) { .... switch (movement) { .... case FRONT: .... if (index == firstIndex) { if (index != 0) { geo.setOrdering(orderingDepthMidpoint(index)); } else { geo.setOrdering(drawingOrder.get(index - 1).getOrdering() - 1); } } .... } .... }
로그인 후 복사

We get the following warning:

V6025 Index 'index - 1' is out of bounds. LayerManager.java 393

This is curious because, in theelseblock, theindexvariable is guaranteed to get the value 0. So, we pass -1 as an argument to thegetmethod. What's the result? We catch anIndexOutOfBoundsException.

Triangles

@Override protected int getGLType(Type type) { switch (type) { case TRIANGLE_STRIP: return GL.GL_TRIANGLE_STRIP; case TRIANGLE_FAN: return GL.GL_TRIANGLE_STRIP; // <= case TRIANGLES: return GL.GL_TRIANGLES; case LINE_LOOP: return GL.GL_LINE_LOOP; case LINE_STRIP: return GL.GL_LINE_STRIP; } return 0; }
로그인 후 복사

The code is new, but the error is already well-known. It's quite obvious thatGL.GL_TRIANGLE_STRIPshould beGL.GL_TRIANGLE_FANinstead*.* The methods may be similar in some ways, but the results are different. You can read about it under the spoiler.

V6067 Two or more case-branches perform the same actions. RendererImplShadersD.java 243, RendererImplShadersD.java 245

To describe a series of triangles, we need to save the coordinates of the three vertices of each triangle. Thus, given N triangles, we need the saved 3N vertices. If we describe a polyhedral object using a polygon mesh, it's important to know if the triangles are connected. If they are, we can use the Triangle Strip or the Triangle Fan to describe the set of triangles using N + 2 vertices.

We note that the Triangle Fan has been removed in Direct3D 10. In OpenGL 4.6, this primitive still exists.

The Triangle Fan uses one center vertex as common, as well as the last vertex and the new vertex. Look at the following example:

Even great mathematicians make mistakes

To describe it, we'd need the entry (A, B, C, D, E, F, G). There are five triangles and seven vertices in the entry.

The Triangle Strip uses the last two vertices and a new one. For instance, we can create the image below using the same sequence of vertices:

Even great mathematicians make mistakes

Therefore, if we use the wrong primitive, we'll get dramatically different results.

Overwritten values

public static void addToJsObject( JsPropertyMap jsMap, Map map) { for (Entry entry : map.entrySet()) { Object object = entry.getValue(); if (object instanceof Integer) { jsMap.set(entry.getKey(), unbox((Integer) object)); } if (object instanceof String[]) { // <= JsArray clean = JsArray.of(); for (String s: (String[]) object) { clean.push(s); } jsMap.set(entry.getKey(), clean); } else { // <= jsMap.set(entry.getKey(), object); // <= } } }
        
로그인 후 복사

If we don't look closely, we may not notice the issue right away. However, let's speed up and just check the analyzer message.

V6086 Suspicious code formatting. 'else' keyword is probably missing. ScriptManagerW.java 209

Finally, a more interesting bug is here. Theobject instanceof String[]check will occur regardless of the result ofobject instanceof Integer. It may not be a big deal, but there's also theelseblock that will be executed after a failed check forString[]. As the result, thejsMapvalue byentry.getKey()will be overwritten if theobjectwasInteger.

There's another similar case, but the potential consequences are a little harder to understand:

public void bkspCharacter(EditorState editorState) { int currentOffset = editorState.getCurrentOffsetOrSelection(); if (currentOffset > 0) { MathComponent prev = editorState.getCurrentField() .getArgument(currentOffset - 1); if (prev instanceof MathArray) { MathArray parent = (MathArray) prev; extendBrackets(parent, editorState); } if (prev instanceof MathFunction) { // <= bkspLastFunctionArg((MathFunction) prev, editorState); } else { // <= deleteSingleArg(editorState); } } else { RemoveContainer.withBackspace(editorState); } }
로그인 후 복사

V6086 Suspicious code formatting. 'else' keyword is probably missing. InputController.java 854

Almost correct

Do you often have to write many repetitious checks? Are these checks prone to typos? Let's look at the following method:

public boolean contains(BoundingBox other) { return !(isNull() || other.isNull()) && other.minx >= minx && other.maxy <= maxx && other.miny >= miny && other.maxy <= maxy; }
로그인 후 복사

V6045 Possible misprint in expression 'other.maxy <= maxx'. BoundingBox.java 139

We find a typo, we fix it. The answer is simple, there should beother.maxx <= maxx.

Mixed up numbers

public PointDt[] calcVoronoiCell(TriangleDt triangle, PointDt p) { .... // find the neighbor triangle if (!halfplane.next_12().isHalfplane()) { neighbor = halfplane.next_12(); } else if (!halfplane.next_23().isHalfplane()) { neighbor = halfplane.next_23(); } else if (!halfplane.next_23().isHalfplane()) { // <= neighbor = halfplane.next_31(); } else { Log.error("problem in Delaunay_Triangulation"); // TODO fix added by GeoGebra // should we do something else? return null; } .... }
로그인 후 복사

Let's see the warning:

V6003 The use of 'if (A) {...} else if (A) {...}' pattern was detected. There is a probability of logical error presence. DelaunayTriangulation.java 678, DelaunayTriangulation.java 680

We don't even need to figure out what happens to half-planes, because it's clear that the!halfplane.next_31().isHalfplane()check is missing.

Wrong operation

public static ExpressionNode get( ExpressionValue left, ExpressionValue right, Operation operation, FunctionVariable fv, Kernel kernel0 ) { .... switch (operation) { .... case VEC_FUNCTION: break; case ZETA: break; case DIRAC: return new ExpressionNode(kernel0, left, Operation.DIRAC, fv); case HEAVISIDE: return new ExpressionNode(kernel0, left, Operation.DIRAC, fv); default: break; } .... }
로그인 후 복사

It seems that, in the second case,Operation.DIRACshould probably be replaced withOperation.HEAVISIDE. Or not? This method is called to obtain the derivative. After researching, we understand whatHEAVISIDE *is—the use of *Operation.DIRACfor it is correct. What about theDiracderivative? This one is a bit tricky to understand. We may trust the developers and suppress the warning. Still, it'd be better if they'd left any explanatory comment as they had done in some cases before.

case FACTORIAL: // x! -> psi(x+1) * x! return new ExpressionNode(kernel0, left.wrap().plus(1), Operation.PSI, null) .multiply(new ExpressionNode(kernel0, left, Operation.FACTORIAL, null)) .multiply(left.derivative(fv, kernel0)); case GAMMA: // gamma(x) -> gamma(x) psi(x) return new ExpressionNode(kernel0, left, Operation.PSI, null) .multiply(new ExpressionNode(kernel0, left, Operation.GAMMA, null)) .multiply(left.derivative(fv, kernel0));
로그인 후 복사

And we were motivated to conduct such research by the message of already favorite diagnostic V6067:

V6067 Two or more case-branches perform the same actions. Derivative.java 442, Derivative.java 444

On the one hand, this is an interesting case because we could have a false positive here. On the other, the warning would be useful either way, as it often highlights a genuine error. Regardless of the results, we need to check such code and either fix the error or write explanatory comments. Then the warning can be suppressed.

Vector or point?

private static GeoElement doGetTemplate(Construction cons, GeoClass listElement) { switch (listElement) { case POINT: return new GeoPoint(cons); case POINT3D: return (GeoElement) cons.getKernel().getGeoFactory().newPoint(3, cons); case VECTOR: return new GeoVector(cons); case VECTOR3D: return (GeoElement) cons.getKernel().getGeoFactory().newPoint(3, cons); } return new GeoPoint(cons); }
로그인 후 복사

The warning isn't hard to guess:

V6067 Two or more case-branches perform the same actions. PointNDFold.java 38, PointNDFold.java 43

Instead ofcons.getKernel().getGeoFactory().newPoint(3, cons)in the second case,cons.getKernel().getGeoFactory().newVector(3, cons)may have been intended. If we want to make sure of it, we need to go deeper again. Well, let's dive into it.

So,* getGeoFactory()returnsGeoFactory,let's look at the *newPoint *andnewVector* methods:

public GeoPointND newPoint(int dimension, Construction cons) { return new GeoPoint(cons); } public GeoVectorND newVector(int dimension, Construction cons) { return new GeoVector(cons); }
로그인 후 복사

It looks a bit strange. Thedimensionargument isn't used at all. What's going on here? Inheritance, of course! Let's find theGeoFactory3Dinheritor class and see what happens in it.

@Override public GeoVectorND newVector(int dimension, Construction cons) { if (dimension == 3) { return new GeoVector3D(cons); } return new GeoVector(cons); } @Override public GeoPointND newPoint(int dimension, Construction cons) { return dimension == 3 ? new GeoPoint3D(cons) : new GeoPoint(cons); }
로그인 후 복사

Excellent! Now we can admire the creation of four different objects in all their glory. We return to the code fragment with the possible error. ForPOINTandPOINT3D, the objects of theGeoPointandGeoPoint3Dclasses are created.GeoVectoris created forVECTOR, but poorGeoVector3Dseems to have been abandoned.

Sure, it's a bit strange to use a factory method pattern in two cases and call constructors directly in the remaining two. Is this a leftover from the refactoring process, or is it some kind of temporary solution that'll stick around until the end of time? In my opinion, if the responsibility for creating objects has been handed over to another class, it'd be better to fully delegate that responsibility.

Missing quadrillions

@Override final public void update() { .... switch (angle.getAngleStyle()) { .... case EuclidianStyleConstants.RIGHT_ANGLE_STYLE_L: // Belgian offset |_ if (square == null) { square = AwtFactory.getPrototype().newGeneralPath(); } else { square.reset(); } length = arcSize * 0.7071067811865; // <= double offset = length * 0.4; square.moveTo( coords[0] + length * Math.cos(angSt) + offset * Math.cos(angSt) + offset * Math.cos(angSt + Kernel.PI_HALF), coords[1] - length * Math.sin(angSt) * view.getScaleRatio() - offset * Math.sin(angSt) - offset * Math.sin(angSt + Kernel.PI_HALF)); .... break; } }
로그인 후 복사

Where's the warning? There it is:

V6107 The constant 0.7071067811865 is being utilized. The resulting value could be inaccurate. Consider using Math.sqrt(0.5). DrawAngle.java 303

Nous avons trouvé quatre instances de ce fragment de code. La valeur la plus précise est 0,7071067811865476. Relevez le défi et essayez de l'apprendre par cœur, hehe. Les trois derniers chiffres sont omis. Est-ce critique ? La précision est probablement suffisante, mais l'utilisation de constantes prédéfinies ou deMath.sqrt(0.5)— comme dans ce cas — aidera non seulement à récupérer les chiffres perdus, mais éliminera également le risque de fautes de frappe. Remarquez comment la lisibilité du code s'améliorera. Peut-être qu'en voyant 0,707... quelqu'un comprend immédiatement de quel genre de nombre magique il s'agit. Cependant, nous pouvons parfois faire un peu moins de magie simplement pour améliorer la lisibilité du code.

Conclusion

Notre petit voyage mathématique est terminé. Comme nous pouvons le constater, même après avoir relevé de nombreux défis géométriques, les gens peuvent encore commettre de simples erreurs de code ! Bien si le code est récent et que les développeurs peuvent toujours garder à l'esprit leurs intentions passées. Dans de tels cas, les gens peuvent comprendre quels changements sont nécessaires, le cas échéant, mais après une longue période, il peut ne pas être si facile de reconnaître la nécessité de résoudre ces problèmes.

Ainsi, l'analyseur s'avère assez efficace dès l'écriture du code plutôt que de l'exécuter une seule fois comme je l'ai fait. Surtout maintenant que nous disposons d'outils comme l'analyseur PVS-Studio.

Nos autres articles sur des sujets similaires

  1. Maux de tête liés à l'utilisation d'un logiciel mathématique.
  2. Mathématiciens : faites confiance, mais vérifiez.
  3. La grosse calculatrice est devenue folle.
  4. Analyse du projet Trans-Proteomic Pipeline (TPP).
  5. Recherche COVID-19 et variable non initialisée.
  6. Analyse du code de ROOT, framework d'analyse de données scientifiques.
  7. NCBI Genome Workbench : La recherche scientifique menacée.

위 내용은 훌륭한 수학자도 실수를 한다의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

원천:dev.to
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!