【问题标题】:Quadratic equation in AdaAda中的二次方程
【发布时间】:2010-12-21 20:59:46
【问题描述】:

我刚过来,决定尝试一些 Ada。 缺点是语法和函数与 C++ 相差甚远。 所以我不得不喜欢塞满各种东西才能让这件事发挥作用。

我的问题是,是否有比我在这里所做的更好的方法来进行计算

   IF(B < 0.0) THEN
      B := ABS(B);
      X1 := (B / 2.0) + Sqrt( (B / 2.0) ** 2.0 + ABS(C));
      X2 := (B / 2.0) - Sqrt( (B / 2.0) ** 2.0 + ABS(C));
   ELSE
      X1 := -(B / 2.0) + Sqrt( (B / 2.0) ** 2.0 - C);
      X2 := -(B / 2.0) - Sqrt( (B / 2.0) ** 2.0 - C);
   END IF;

我对负数有一些问题,这就是为什么我做了一个 IF 语句并使用 ABS() 将它们变成正数。但奇怪的是它对另一种情况非常有效,这很奇怪......

【问题讨论】:

  • 关于前两行 - 当你已经知道 B 是负数时,我会避免使用 abs() 。使用 B:=-B。即使编译器很聪明并且可以内联内容。

标签: math ada


【解决方案1】:

解二次方程并不像大多数人想象的那么简单。

解决a x^2 + b x + c = 0的标准公式是

delta = b^2 - 4 a c
x1 = (-b + sqrt(delta)) / (2 a)   (*)
x2 = (-b - sqrt(delta)) / (2 a)

但是当4 a c &lt;&lt; b^2 时,计算x1 涉及减去接近的数字,并且会使您失去准确性,因此请改用以下内容

delta as above
x1 = 2 c / (-b - sqrt(delta))     (**)
x2 = 2 c / (-b + sqrt(delta))

它产生了一个更好的 x1,但它的 x2 与上面的 x1 有同样的问题。

因此计算根的正确方法是

q = -0.5 (b + sign(b) sqrt(delta))

并使用x1 = q / ax2 = c / q,我觉得这非常有效。如果您想处理delta 为负数或复数系数的情况,则必须使用复数算术(这也很棘手)。

编辑:使用 Ada 代码:

DELTA := B * B - 4.0 * A * C;

IF(B > 0.0) THEN
    Q := -0.5 * (B + SQRT(DELTA));
ELSE
    Q := -0.5 * (B - SQRT(DELTA));
END IF;

X1 := Q / A;
X2 := C / Q;

【讨论】:

  • 很好的解释!这是一个很好的例子,说明为什么某些东西的标准教科书公式可能不是编写代码的最佳指南。
  • 文体:保留字小写和标题大写标识符是正常的,'if'语句中不需要括号,例如if B &gt; 0.0 thenDelta := etc.
  • 问题:我们在这里使用浮点数吗?在这种情况下,相近的值之间的减法真的会有准确性问题吗?
  • @debater:是的。 1.0000001 - 1 = 0.0000001 并且您丢失了 8 个有效数字。如果您不相信,请尝试上面的代码和 a=1, b=10000000, c=1 的高中公式。
【解决方案2】:

给定 ax2 + bx + c = 0 quadradic formula 给出 x = (-b +/- sqrt(b2-4ac) ) / 2a。判别式 d = b2-4ac 对于实值根为正,对于具有非零虚部(即非实复数)的根为负,当根为双根。

因此,Ada 代码将是:

D := B ** 2.0 - 4.0 * A * C;
IF D >= 0.0 THEN
  X1 := (-B + Sqrt(D)) / (2.0 * A);
  X2 := (-B - Sqrt(D)) / (2.0 * A);
ELSE
  -- Deal with the fact that the result is a non-real complex number.
END IF;

注意:我对 Ada 有点生疏,但这应该接近正确的语法。

【讨论】:

  • 关闭;所有数字都必须是实数(第一行为 4.0,第二行为 0.0,第三和第四行为 2.0)。此外,不需要在条件表达式周围加上括号; if D &lt;= 0.0 then.
【解决方案3】:

二次公式为x = ( -b +/- sqrt ( b ** 2 - 4*a*c ) ) / ( 2 * a )

我猜 a 是 1。

所以x = -( b/2 ) +/- sqrt ( ( ( b ** 2 ) / 4 ) - c )

计算d = ( b ** 2 ) * 0.25 - c,然后检查它的符号。

如果d 的符号为负,则表示有复根;随心所欲地处理它们。

如果b 恰好是负数,则将- c 替换为+ abs ( c ) 会给你带来垃圾。

通常乘以 0.5 或 0.25 比除以 2.0 或 4.0 更好。

【讨论】:

    【解决方案4】:

    虽然我不认识 Ada,但我看到以下可以优化的东西:

    1. IF 指令的第一个分支中,您已经知道B 是否定的。所以你可以说B := -B 而不是B := ABS(B)。或者更好:只需在第一个分支中使用 B 的地方使用 -B
    2. 您使用了四次子表达式B/2.0。将B/2.0 分配给辅助变量B_2(如果您不想使用另一个变量,则将其再次分配给B)并改用它可能更有效(也更清晰)。
      sqrt 也计算了两次。将其分配给辅助变量可以节省运行时间(并使读者清楚地知道完全相同的子表达式被使用了两次)。
    3. 比使用B_2*B_2 代替**2.0 可能更快;如果 Ada 中有一个专用的 square 函数,那就更好了。

    【讨论】:

      【解决方案5】:

      对我来说,这个问题更多地与数字算法有关,而不是与 Ada 语言有关。 与数值计算一样,人们必须经常(如果不是-总是-)参考参考/学术论文。

      这些问题总是让我想起这一点: https://en.wikipedia.org/wiki/Fast_inverse_square_root

      如果您“做数学”或找到一些解决您的问题的论文,您只能找到以下技巧。

      float Q_rsqrt( float number )
      {
          long i;
          float x2, y;
          const float threehalfs = 1.5F;
      
          x2 = number * 0.5F;
          y  = number;
          i  = * ( long * ) &y;                       // evil floating point bit level hacking
          i  = 0x5f3759df - ( i >> 1 );               // what the...? 
          y  = * ( float * ) &i;
          y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
      //  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed
      
          return y;
      }
      

      PS:正如 wikiepdia 文章指出的那样,这种实现对于现在大多数平台来说可能已经过时了

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-08-06
        • 2013-03-02
        • 1970-01-01
        • 2013-12-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多