Могу ли я рассчитывать на это, чтобы судить о квадратном числе в C++?

Могу ли я положиться на

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) является необходимым? Как я вижу, с этим разница может быть велика для квадратов; но без этого разница может быть небольшой для не квадратов. Возможно, ни один не подойдет.:-(

12 ответов

На самом деле, это не C++, а математический вопрос.

  1. С числами с плавающей точкой вы никогда не должны полагаться на равенство. Если вы хотите протестировать a == b, просто протестируйте против abs(a - b)
  2. Если число, которое вы тестируете, является целым числом, вас может заинтересовать статья в Википедии о целочисленном квадратном корне

РЕДАКТИРОВАТЬ:

Как сказал Кругар, статья, на которую я ссылаюсь, ничего не отвечает. Конечно, нет прямого ответа на ваш вопрос, Фени. Я просто подумал, что основная проблема, с которой вы столкнулись, - это точность с плавающей запятой, и, возможно, вы хотели получить математический фон для своей задачи.

Для нетерпеливых есть ссылка в статье на длительное обсуждение реализации isqrt. Это сводится к коду karx11erx, опубликованному в его ответе.

Если у вас есть целые числа, которые не вписываются в длинную без знака, вы можете изменить алгоритм самостоятельно.

Если вы не хотите полагаться на точность с плавающей точкой, вы можете использовать следующий код, который использует целочисленную математику.

Isqrt взят отсюда и является 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;
}

На ваш вопрос уже дан ответ, но вот рабочее решение.

Ваши "идеальные квадраты" являются неявно целочисленными значениями, поэтому вы можете легко решить проблемы точности, связанные с форматом с плавающей запятой, используя некоторую целочисленную функцию квадратного корня для определения целочисленного квадратного корня значения, которое вы хотите проверить. Эта функция вернет наибольшее число r для значения v где r * r <= 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;
}

Чтобы не делать один и тот же расчет дважды, я бы сделал это с временным числом:

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

редактировать: Дав сделал хорошую мысль. вместо того, чтобы полагаться на актерский состав, вам нужно сначала закруглить поплавок

так и должно быть:

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

Я не следовал формуле, я прошу прощения. Но вы можете легко проверить, является ли число с плавающей запятой целым, приведя его к целочисленному типу, и сравнить результат с числом с плавающей запятой. Так,

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

Естественно, это выполнимо, только если вы работаете со значениями, которые, как вы знаете, будут вписываться в диапазон целочисленных типов. Но в этом случае вы можете решить проблему таким образом, избавив вас от внутренней сложности математической формулы.

Как говорит Рейнир, вам нужно добавить 0,5, чтобы убедиться, что оно округляется до ближайшего целого числа, так что вы получите

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

Чтобы это работало, b должен быть (точно) равен квадратному корню из a если a это идеальный квадрат. Тем не менее, я не думаю, что вы можете гарантировать это. Предположим, что int 64 бит и float 32 бита (я думаю, что это разрешено). затем a может быть порядка 2^60, поэтому его квадратный корень имеет порядок 2^30. Тем не менее, float хранит только 24 бита в значении, поэтому ошибка округления имеет порядок 2^(30-24) = 2^6. Это больше 1, так b может содержать неправильное целое число. Например, я думаю, что приведенный выше код не идентифицирует a = (2^30+1)^2 как идеальный квадрат.

Я бы сделал.

// 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.
}

Самое чистое решение - использовать целочисленную подпрограмму 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(),

Хуже того, у float есть только 24 значащих бита, что означает, что вы не можете привести к целому числу больше 2^24-1 без потери точности, что приводит к ложным положительным / отрицательным значениям. Используя удвоения для тестирования 32-битных целых, у вас все будет хорошо.

Но не забывайте приводить результат sqrt с плавающей точкой обратно к int и сравнивать результат с исходным int. Сравнение между поплавками никогда не бывает хорошей идеей; даже для квадратных значений x в ограниченном диапазоне нет никакой гарантии, что sqrt( x ) * sqrt( x ) == x, или это sqrt( x * x) = x,

Более очевидный, если медленнее - 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;   
}

В то время как другие отметили, что вы не должны проверять равенство с плавающей точкой, я думаю, что вы упускаете возможность использовать свойства совершенных квадратов. Во-первых, нет смысла пересчитывать вычисленный корень. Если a тогда идеальный квадрат sqrt(a) является целым числом, и вы должны проверить:

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

где e установлено достаточно мало. Есть также несколько целых чисел, которые вы можете пересечь как не квадратные, прежде чем брать квадратный корень. Проверяя Википедию, вы можете увидеть некоторые необходимые условия для a быть квадратным:

Квадратное число может заканчиваться только цифрами 00,1,4,6,9 или 25 в основании 10

Еще одна простая проверка будет видеть, что a % 4 == 1 or 0 до получения рута, так как:

Квадраты четных чисел четные, поскольку (2n)^2 = 4n^2.
Квадраты нечетных чисел нечетны, поскольку (2n + 1)^2 = 4(n^2 + n) + 1.

Это, по существу, устранит половину целых чисел, прежде чем принимать какие-либо корни.

Основы в первую очередь:

если вы (int) число в расчете, он удалит ВСЕ данные после запятой. Если я правильно помню свой C, если у вас есть (int) в любом вычислении (+/-*), он автоматически предполагает int для всех других чисел.

Так что в вашем случае вы хотите плавать на каждом задействованном числе, иначе вы потеряете данные:

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

это путь, которым вы хотите идти

Математика с плавающей точкой неточна по своей природе.

Итак, рассмотрим этот код:

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.

Другие вопросы по тегам