【问题标题】:How to correctly work with Math.Pow()?如何正确使用 Math.Pow()?
【发布时间】:2016-10-17 01:44:20
【问题描述】:

我想找到方程的所有正整数解 a^3 + b^3 = c^3 + d^3 其中 a,b,c,d 是 1 到 1000 之间的整数;

for (int a = 1; a <= 1000; ++a)
{
    for (int b = 1; b <= 1000; ++b)
    {
        for (int c = 1; c <= 1000; ++c)
        {
            for (int d = 1; d <= 1000; ++d)
            {
                if (Math.Pow(a, 3) + Math.Pow(b, 3) == Math.Pow(c, 3) + Math.Pow(d, 3))
                {
                     Console.WriteLine("{0} {1} {2} {3}", a,b,c,d);
                }
            }
      }
}

我知道d = Math.Pow(a^3 + b^3 - c^3, 1/3),所以

for (int a = 1; a <= 1000; ++a)
{
    for (int b = 1; b <= 1000; ++b)
    {
        for (int c = 1; c <= 1000; ++c)
        {
            int d = (int)Math.Pow(Math.Pow(a, 3) + Math.Pow(b, 3) - Math.Pow(c, 3), 1/3);

            if (Math.Pow(a, 3) + Math.Pow(b, 3) == Math.Pow(c, 3) + Math.Pow(d, 3))
            {
                Console.WriteLine("{0} {1} {2} {3}", a,b,c,d);
            }
        }
   }
}

但是第二种算法产生的结果数量要少得多。我的代码中的错误在哪里?

我尝试(double)1/3,但第二个算法给我的结果仍然比第一个少

【问题讨论】:

  • 1/3 为 0,因为您在进行整数除法。所以,d 总是 1。试试(double)1/3
  • 尝试将 1/3 转换为 double
  • d 不应该是 int。注意舍入误差。
  • 我尝试了(double)1/3,但第二种算法给我的结果仍然少于第一种算法

标签: c# algorithm


【解决方案1】:

这些答案几乎没有一个是正确的。

你的问题是:

我的代码中的错误在哪里?

如果您使用双精度算术来解决整数问题,那么您做错了。完全不要使用Math.Pow,尤其不要使用它来提取立方根并期望你会得到一个精确的整数答案。

那么你应该如何实际解决这个问题呢?

让我们更聪明一点,不要做不必要的工作。您的程序发现 13 + 123 = 93 + 103,但也发现 123 + 13 = 103 + 93,以此类推。如果你知道第一个,那么你可以很容易地知道第二个。

那么我们应该怎么做才能提高效率呢?

首先,b 必须大于 a。这样我们就不会浪费任何时间来计算 13 + 123 = 123 + 13

同样,d 必须大于 c

现在,我们也可以说cd 必须 ab 之间。 你明白为什么吗?

一旦我们实施了这些限制:

    for (int a = 1; a <= 1000; ++a)
        for (int b = a + 1; b <= 1000; ++b)
            for (int c = a + 1; c < b; ++c)
                for (int d = c + 1; d < b; ++d)
                    if (a * a * a + b * b * b == c * c * c + d * d * d)
                        Console.WriteLine($"{a} {b} {c} {d}");

你的程序变得更快了。

现在,如果您愿意以更少的时间换取更多的内存,还有一些方法可以让它更快。你能想到这个程序浪费时间的一些方法吗?相同的计算重复进行多少次?我们如何才能改善这种情况?

我们可能会注意到,例如a * a * a 每次都通过三个内部循环计算!

    for (int a = 1; a <= 1000; ++a)
    {
        int a3 = a * a * a;
        for (int b = a + 1; b <= 1000; ++b)
        {
            int b3 = b * b * b;
            int sum = a3 + b3;
            for (int c = a + 1; c < b; ++c)
            {
                int c3 = c * c * c;
                int d3 = sum - c3;
                for (int d = c + 1; d < b; ++d)
                    if (d3 == d * d * d)
                        Console.WriteLine($"{a} {b} {c} {d}");
            }
        }
    }

但我们可以比这更聪明。例如:如果我们创建一个 Dictionary&lt;int, int&gt; 将立方体映射到它们的立方体根会怎么样?他们只有1000个!那么我们可以说:

    for (int a = 1; a <= 1000; ++a)
    {
        int a3 = a * a * a;
        for (int b = a + 1; b <= 1000; ++b)
        {
            int b3 = b * b * b;
            int sum = a3 + b3;
            for (int c = a + 1; c < b; ++c)
            {
                int c3 = c * c * c;
                int d3 = sum - c3;
                if (dict.HasKey(d3))
                {
                    d = dict[d3];
                    Console.WriteLine($"{a} {b} {c} {d}");
                }
            }
        }
    }

现在您不必计算 d 的立方或立方根;你只要看看它是否是一个立方体,如果是,它的立方根是什么。

【讨论】:

  • @Anatoly 在我们进入这个循环之前,字典会被预先计算,你可能会在类的静态构造函数中将它设置为 static readonly IReadOnlyDictionary dict; 字典对于只读操作是线程安全的。
  • @Anatoly:我们也从不声明它;我想读者可以填写如何初始化包含从立方体到根的映射的字典的详细信息。不要只是从互联网上复制粘贴代码; 想想它是如何工作的
  • @EricLippert 对不起,我明白了。你的算法的复杂度是多少?
  • @Anatoly:我邀请你自己解决这个问题。它塑造性格。
  • @Anatoly:是的;只要您愿意以更少的时间换取更多的空间,就有办法让这个算法更快。此外,这个方程得到了很好的研究。例如,我们知道有一种模式可以让您生成 有理 解决方案,但目前还没有一种已知模式可以生成 整数 解决方案。如果您对此主题感兴趣,请考虑阅读fq.math.ca/Scanned/38-3/chamberland.pdf
【解决方案2】:

即使是双精度数,1/3 也不能用任何 IEEE 浮点类型精确表示。

因此,您实际上并不是在计算一个值的立方根,而是将该值提高到 0.333333333333333333333333333334 的幂次方或稍微偏离的版本。这会引入舍入误差,导致误差因素增加。

考虑 a = 995、b = 990、c = 990 的情况:您的第一个循环将生成 d = 995 的匹配项,但由于舍入错误,您的计算会产生 d = 994,这与您的条件不匹配。这很少会匹配,这解释了您所看到的结果。

要解决此问题,您可以完全使用浮点数,但这会变得一团糟,因为即使在这种情况下,由于表示问题,您也必须检查范围,并且只有在找到可能合适的范围后,您才必须检查整数版本。另一种解决方案是从数学的角度而不是蛮力来解决这个问题,尽管这可能需要内核方法并且变得非常混乱。

【讨论】:

  • 你能提供一个代码吗?我不明白如何解决问题
  • 您打算如何使用它?消除错误并不容易,因此了解您打算如何使用它将有助于推荐可行的解决方案;修复代码超出了我的能力范围,因为它需要两层,第一层完全以浮点近似解,然后第二层尝试基于浮点解来确定整数解。但是,即使提供建议也需要更多信息,因为大多数结果都是 A+B=B+A 的形式。
【解决方案3】:

替换

int d = (int)Math.Pow(Math.Pow(a, 3) + Math.Pow(b, 3) - Math.Pow(c, 3), 1/3);

int d = (int)Math.Round(Math.Pow(Math.Pow(a, 3) + Math.Pow(b, 3) - Math.Pow(c, 3), 1.0/3), 0);
if(d > 1000) continue; // Restrict solutions as in brute force algorithm

修复的两个错误是,

  1. 1/3 计算为整数除法,结果为 0。使用 1.0/3 强制进行浮点除法。
  2. 由于浮点错误,计算可能会返回3.999999994 而不是4。当转换为整数时,它会被截断为3,从而有效地删除解决方案。使用 Math.Round(3.999999994, 0) 将其向上舍入为 4.0,然后将其作为整数转换为 4

【讨论】:

  • 你的解决方案给了我+24的结果然后是第一个算法(n = 100)
  • 这是因为在您的第一个算法中,d 被限制为 d n 的解决方案。要获得相同数量的结果,只需排除 d > n 时的情况。我已经更新了答案:)
【解决方案4】:

您的问题是,这一行的结果:int d = (int)Math.Pow(Math.Pow(a, 3) + Math.Pow(b, 3) - Math.Pow(c, 3), 1/3); 可以是非整数值,这不能满足您的要求。但是通过将其转换为int,您可以获得更多的解决方案。

您应该将 d 更改为 double,然后检查 d 是否为 1 到 1000 之间的整数值:

double d = Math.Pow(Math.Pow(a, 3) + Math.Pow(b, 3) - Math.Pow(c, 3), 1.0/3.0);
double epsilon = 0.000000001;
double dint = Math.Round(d, 0);
if (dint<=d+epsilon && dint>=d-epsilon && dint>=1-epsilon && dint<=1000+epsilon)
{
   Console.WriteLine("{0} {1} {2} {3}", a,b,c,d);
}

编辑:我添加了一个 epsilon 以确保您的双重比较有效

【讨论】:

  • (double) 1/3 呢?
  • 如果在第一行将结果转换为 int,为什么 d 是双倍的?
  • 你检查第三行吗?
  • 是的。也许你应该在这里使用Math.Round()。但是您可以将其保留为 double
猜你喜欢
  • 1970-01-01
  • 2016-01-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-09-24
  • 1970-01-01
相关资源
最近更新 更多