【问题标题】:Can I rely on this to judge a square number in C++?我可以依靠它来判断 C++ 中的平方数吗?
【发布时间】:2010-11-26 18:47:53
【问题描述】:

我可以依靠吗

sqrt((float)a)*sqrt((float)a)==a

(int)sqrt((float)a)*(int)sqrt((float)a)==a

检查一个数是否是完全平方?为什么或为什么不?
int a 是要判断的数字。我正在使用 Visual Studio 2005。

编辑:感谢所有这些快速的答复。我看到我不能依赖浮点类型比较。 (如果我像上面那样写,最后一个a 会被隐式转换为浮动吗?)如果我这样做

(int)sqrt((float)a)*(int)sqrt((float)a) - a < e  

我应该将e 的值取多少?

Edit2:嘿,我们为什么不把比较部分放在一边,决定(int)是否必要?如我所见,有了它,正方形的差异可能很大。但没有它,对于非正方形来说,差异可能很小。也许两者都不会。 :-(

【问题讨论】:

  • 大概aint,但你能确认一下吗?
  • 大家都被否决是怎么回事?
  • phoenie,出于所有实际目的,小于 0.1 左右的任何值都应该起作用,因为您基本上是在比较大约等于整数的值 - 任何其他整数输入都会导致输出大约为整数差的广场。
  • e 将是 |f'(x)|-|(f(x)-f(x+h))/h| 的最小值作为 h->0
  • 使用自然对数 - 更精确

标签: c++ math floating-point perfect-square


【解决方案1】:

您的问题已经得到解答,但这里有一个可行的解决方案。

您的“完美平方”是隐含的整数值,因此您可以通过使用一些整数平方根函数来确定要测试的值的整数平方根,从而轻松解决与浮点格式相关的精度问题。该函数将返回最大的数字r 的值v,其中r * r &lt;= v。一旦你有了r,你只需要测试是否r * r == v

unsigned short isqrt (unsigned long a)
{
    unsigned long rem = 0;
    unsigned long root = 0;

    for (int i = 16; i; i--) {
        root <<= 1;
        rem = ((rem << 2) + (a >> 30));
        a <<= 2;
        if (root < rem)
            rem -= ++root;
    }

    return (unsigned short) (root >> 1);
}

bool PerfectSquare (unsigned long a)
{
    unsigned short r = isqrt (a);

    return r * r == a;
}

【讨论】:

  • 糟糕。在编辑添加一些代码后,我刚刚阅读了您的答案。我只是盲目地复制了 isqrt 代码,甚至没有尝试进一步优化它。您在这些优化上花费的精力会得到我的支持。
  • 好吧,我在您(或这里的任何其他人)发布此解决方案的几个小时之前发布了此解决方案。我对您复制我的解决方案并在这里获得所有赞成票并不感到好笑。处理这个问题的体面方法是删除您编辑的代码,作为我的解决方案的一个点。
  • 您的解决方案?至少 wigy 包含了一个他从中复制代码的参考。你没有。
  • 我从我的一些至少有 10 年历史的源代码中复制了 isqrt 代码。整数平方根函数是微不足道的常识。关键是我是第一个提出将它们用于完美平方问题的人(这是我自己的想法,反正很琐碎),你的评论毫无意义。
  • 好吧,既然你承认计算平方根是常识,你至少可以消除你对 wigy 抄袭的指控并道歉。谢谢
【解决方案2】:

如果您不想依赖浮点精度,则可以使用以下使用整数数学的代码。

Isqrt 取自 here,为 O(log n)

// Finds the integer square root of a positive number
static int Isqrt(int num)
{
    if (0 == num) { return 0; }  // Avoid zero divide
    int n = (num / 2) + 1;       // Initial estimate, never low
    int n1 = (n + (num / n)) / 2;
    while (n1 < n)
    {
        n = n1;
        n1 = (n + (num / n)) / 2;
    } // end while
    return n;
} // end Isqrt()

static bool IsPerfectSquare(int num)
{
    return Isqrt(num) * Isqrt(num) == num;
}

【讨论】:

  • 虽然不错,但平方根的浮点版本可能会在数学处理器上完成,并且比这种迭代近似要快得多。
  • reinier,快速实施但不能正常工作有什么好处?
【解决方案3】:

最干净的解决方案是使用整数 sqrt 例程,然后这样做:

bool isSquare( unsigned int a ) {

  unsigned int s = isqrt( a );
  return s * s == a;

}

这将在整个 int 范围内以完美的精度工作。几个案例:

a = 0, s = 0, s * s = 0 (add an exception if you don't want to treat 0 as square)  
a = 1, s = 1, s * s = 1  
a = 2, s = 1, s * s = 1  
a = 3, s = 1, s * s = 1  
a = 4, s = 2, s * s = 4  
a = 5, s = 2, s * s = 4

当您接近 int 大小的最大值时也不会失败。例如。对于 32 位整数:

a = 0x40000000, s = 0x00008000, s * s = 0x40000000  
a = 0xFFFFFFFF, s = 0x0000FFFF, s * s = 0xFFFE0001

使用浮点数会遇到很多问题。您可能会发现 sqrt( 4 ) = 1.999999... 和类似问题,尽管您可以舍入到最近而不是使用 floor()

更糟糕的是,浮点数只有 24 个有效位,这意味着您不能将任何大于 2^24-1 的 int 转换为浮点数而不损失精度,这会引入误报/误报。不过,使用双精度数来测试 32 位整数应该没问题。

但请记住将浮点 sqrt 的结果转换回 int 并将结果与​​原始 int 进行比较。浮点数之间的比较从来都不是一个好主意。即使对于有限范围内的 x 平方值,也不能保证 sqrt( x ) * sqrt( x ) == xsqrt( x * x) = x

【讨论】:

  • 嘎。在预览中看起来不错。预览欺骗了我!这个网站咬人。
【解决方案4】:

其实这不是C++,而是一道数学题。

  1. 对于浮点数,您永远不应该依赖相等性。如果您要测试 a == b,只需针对 abs(a - b)
  2. 如果您要测试的数字是整数,您可能会对有关 Integer square root 的 Wikipedia 文章感兴趣

编辑:

正如克鲁格所说,我链接的文章没有回答任何问题。当然,菲妮,你的问题没有直接的答案。我只是认为您遇到的根本问题是浮点精度,也许您需要一些数学背景来解决您的问题。

对于不耐烦的人,文章中有一个指向lengthy discussion about implementing isqrt 的链接。归结为他的答案中发布的代码 karx11erx。

如果你有不适合无符号长整数的整数,你可以自己修改算法。

【讨论】:

  • 这篇文章没有回答。然而,这被作为投票最多的问题播出。去图吧。
  • Shay Erlichmen 提供了一个不错的算法。检查一下。
  • 实际上,您刚刚将我的解决方案重新发布到了涉及整数平方根的完美平方问题,我在您发布之前几个小时发布了该问题。在我看来,这是一个公然抄袭的案例。 >:(
  • 我不为名誉而战。我只是想回答问题。我删除了代码以满足 karx11erx。
【解决方案5】:

虽然其他人已经指出您不应该使用浮点数来测试相等性,但我认为您错过了利用完美正方形属性的机会。首先,对计算的根进行重新平方是没有意义的。如果a 是一个完美的正方形那么sqrt(a) 是一个整数,你应该检查:

b = sqrt((float)a)
b - floor(b) < e

e 设置得足够小。在取平方根之前,您还可以将许多整数作为非平方来交叉。检查Wikipedia可以看到a是正方形的一些必要条件:

平方数只能以 以 10 为基数的 00、1、4、6、9 或 25 位

另一个简单的检查是在扎根之前查看a % 4 == 1 or 0,因为:

偶数的平方是偶数, 因为 (2n)^2 = 4n^2。
奇数平方 数字是奇数,因为 (2n + 1)^2 = 4(n^2 + n) + 1.

这些基本上会在求根之前消除一半的整数。

【讨论】:

    【解决方案6】:

    我愿意。

    // sqrt always returns positive value. So casting to int is equivalent to floor()
    int down =  static_cast<int>(sqrt(value));
    int up   = down+1;                           // This is the ceil(sqrt(value))
    
    // Because of rounding problems I would test the floor() and ceil()
    // of the value returned from sqrt().
    if (((down*down) == value) || ((up*up) == value))
    {
         // We have a winner.
    }
    

    【讨论】:

    • 很好的评论。但我认为舍入函数比 2 个不同的测试更容易理解。
    【解决方案7】:

    更明显,如果更慢 -- O(sqrt(n)) -- 方式:

    bool is_perfect_square(int i) {
        int d = 1;
        for (int x = 0; x <= i; x += d, d += 2) {
            if (x == i) return true;
        }
        return false;   
    }
    

    【讨论】:

      【解决方案8】:

      正如 reinier 所说,你需要加上 0.5 以确保它四舍五入到最接近的整数,所以你得到

      int b = (int) (sqrt((float)a) + 0.5f);
      if((b*b) == a) /* perfect square */
      

      如果a 是一个完美的平方,那么b 必须(完全)等于a 的平方根。但是,我认为您不能保证这一点。假设 int 是 64 位,float 是 32 位(我认为这是允许的)。那么a 的阶数是 2^60,所以它的平方根阶数是 2^30。但是,float 仅在有效数字中存储 24 位,因此舍入误差为 2^(30-24) = 2^6。这大于 1,因此b 可能包含错误的整数。例如,我认为上面的代码没有将a = (2^30+1)^2 识别为完美正方形。

      【讨论】:

      • 使用float而不是double有什么特别的原因吗?
      • 我刚刚复制了reinier。如果我没记错的话,我写这个回复是作为对 reinier 的评论,但是太长了,无法发表正确的评论。
      【解决方案9】:

      不做两次相同的计算,我会用一个临时数字来做:

       int b = (int)sqrt((float)a);
       if((b*b) == a)
       {
           //perfect square
       }
      

      编辑: dav 提出了一个很好的观点。而不是依赖演员阵容,您需要先将浮点数四舍五入

      应该是这样的:

       int b = (int) (sqrt((float)a) + 0.5f);
       if((b*b) == a)
       {
           //perfect square
       }
      

      【讨论】:

      • 这明显是错误的。如果 a=35,则 b=5。 b*b - 25,即 !=35
      • @harshath.jr 因此 35 不是一个完美的正方形。
      • 不,不是。完美平方是一个整数,可以写成其他整数的平方。所以你的 a=35 的例子不是一个完美的正方形,我的例程不会这样识别!
      • 您很容易遇到浮点精度问题。例如,如果 a=25,浮点 sqrt 结果返回为 4.99999999,然后转换为 int,您将得到 b=4。
      • 这就是我们添加 0.5f 的原因。所以:4.5f + 0.5f = 5.0f = 5
      【解决方案10】:

      我没有遵循公式,我很抱歉。 但是您可以通过将浮点数转换为整数类型并将结果与​​浮点数进行比较来轻松检查浮点数是否为整数。所以,

      bool isSquare(long val) {
          double root = sqrt(val);
          if (root == (long) root)
              return true;
          else return false;
      }
      

      当然,这只有在您使用您知道适合整数类型范围的值时才可行。但既然如此,您可以通过这种方式解决问题,省去数学公式固有的复杂性。

      【讨论】:

      • 这可能不起作用,因为编译器可能会在比较左根之前将其转换为 long。至少在将浮点值与整数常量进行比较时会发生这种情况。您可能想要执行“if (root == double (long (root)))”(在此处使用 C++ 函数样式类型转换)。
      【解决方案11】:

      浮点数学本质上是不准确的。

      所以考虑一下这段代码:

      int a=35;
      float conv = (float)a;
      float sqrt_a = sqrt(conv);
      if( sqrt_a*sqrt_a == conv )
          printf("perfect square");
      

      这就是将会发生的事情:

      a = 35
      conv = 35.000000
      sqrt_a = 5.916079
      sqrt_a*sqrt_a = 34.999990734
      

      这很清楚 sqrt_a^2 不等于 a。

      【讨论】:

      • 您的 a=35 示例存在缺陷。 35 不是一个完美的正方形。
      • 我从来没有说过它是......他只是在展示它是多么的不准确,因为 35 有一个小数的方格。
      • 这个版本有很多反例。例如,如果 a 大于 2**24,则 sqrt_asqrt_a 的结果在小数点后没有位,因为浮点数具有 24 位精度。因此,测试 sqrt_asqrt_a == conv 是否返回 true 或多或少是巧合。
      【解决方案12】:

      基础优先:

      如果您在计算中(int)一个数字,它将删除所有逗号后数据。如果我没记错我的 C,如果您在任何计算 (+/-*) 中有 (int),它将自动假定所有其他数字为 int。

      因此,在您的情况下,您希望在所涉及的每个数字上浮动,否则您将丢失数据:

      sqrt((float)a)*sqrt((float)a)==(float)a
      

      是你想走的路

      【讨论】:

      • 我认为他只对将结果解释为 int 感兴趣。否则任何数字都符合条件
      • 最近的一个问题表明,即使 sin(x)==sin(x) 也不总是成立。不要相信浮点比较。
      • 真的。浮点运算是为精度而不是比较而构建的。
      • 取决于表达式解析器,一旦表达式中涉及浮点值,所有子表达式都会转换为浮点数。
      • 此解决方案可能有误报和误报。例如 sqrt((float)2) * sqrt((float)2) 没有理由不等于 2.0。
      猜你喜欢
      • 2015-05-24
      • 2016-01-10
      • 1970-01-01
      • 2018-01-20
      • 1970-01-01
      • 2020-05-06
      • 1970-01-01
      • 1970-01-01
      • 2011-12-18
      相关资源
      最近更新 更多