Могу ли я доверять преобразованию результата ceil() из реального в int?

#c #c #floating-point #floating-accuracy #ceil

#c #c #с плавающей запятой #плавающая точность #ceil

Вопрос:

Предположим, у меня есть некоторый код, такой как:

 float a, b = ...; // both positive
int s1 = ceil(sqrt(a/b));
int s2 = ceil(sqrt(a/b))   0.1;
  

Возможно ли это когда-нибудь s1 != s2 ? Меня беспокоит, когда a/b это идеальный квадрат. Например, возможно a=100.0 , и b=4.0 , тогда вывод ceil должен быть 5.00000 , но что, если вместо этого это 4.99999 так?

Аналогичный вопрос: есть ли вероятность, что 100.0/4.0 вычисляется значение say 5.00001 , а затем ceil округляется до 6.00000 ?

Я бы предпочел сделать это в целочисленной математике, но sqrt это своего рода план.

РЕДАКТИРОВАТЬ: предложения о том, как лучше реализовать это, также будут оценены! Значения a и b являются целочисленными значениями, поэтому фактический код больше похож на: ceil(sqrt(float(a)/b))

РЕДАКТИРОВАТЬ: основываясь на ответе levis501, я думаю, что сделаю это:

 float a, b = ...; // both positive
int s = sqrt(a/b);
while (s*s*b < a)   s;
  

Спасибо вам всем!

Комментарии:

1. Для чего нужны эти вычисления? Вы можете, например, изменить sqrt(x) < y into x < y * y , который сохраняет ваши целочисленные операции (до тех пор, пока y * y они не станут слишком большими).

2. К сожалению, очень возможно, что s1 != s2 , даже если a/b это идеальный квадрат. Арифметика с плавающей запятой, как известно, неточна, поэтому ваши шансы когда-либо получить 5.00000 (или любое число именно то, что вы хотите), довольно малы.

3. @peachykeen Даже если мы имеем дело с целыми числами? 5.0 может быть идеально представлено в виде числа с плавающей запятой, поэтому может 100.0 и 20.0 .

4. @Dan Если у вас есть реализация, в которой 100/4 вычисляется что-то близкое к 5 , у вас определенно есть проблема.

5. @GMan Я подумаю об этом… У меня есть a элементы, которые мне нужно распределить по b блокам. Когда a>b я хочу разделить каждый блок на 4, 9, 16, 25 и т. Д. Вложенные блоки. Таким s образом, значение представляет собой масштаб (2, 3, 4, 5 соответственно) или число делений блока по каждому измерению. Например, для 300 блоков и 20 блоков требуется, чтобы каждый блок был разделен на 16 (4×4).

Ответ №1:

Я не думаю, что это возможно. Независимо от значения sqrt(a/b) , оно выдает некоторое значение N, которое мы используем как:

 int s1 = ceil(N);
int s2 = ceil(N)   0.1;
  

Поскольку ceil всегда выдает целочисленное значение (хотя и представленное как double ), у нас всегда будет некоторое значение X, для которого первое производит X.0 и второе X.1 . Преобразование в int всегда будет усекать это .1 , поэтому оба приведут к X .

Может показаться, что было бы исключение, если бы X было настолько большим, что X.1 превысил диапазон double . Я не вижу, где это может быть возможно. За исключением случаев, близких к 0 (где переполнение не вызывает беспокойства), квадратный корень из числа всегда будет меньше входного числа. Следовательно, до того, как ceil(N) 0.1 может переполниться, значение, a/b используемое в качестве входных sqrt(a/b) данных, должно было бы уже переполниться.

Комментарии:

1.Я согласен, что мы должны ожидать доверия ceil , но я не могу найти, где (если вообще где-либо) в стандарте C на самом деле говорится о точности функций math.h . Предполагая точность 0,5ulp, он ceil должен возвращать целое число, и было бы довольно неразумно, если бы реализация не смогла этого сделать. Но мы знаем, что, скажем cos , не гарантируется точность 0,5ulp (или sqrt , что, я думаю, отвечает на второй вопрос), так почему должно ceil быть? Я имею в виду, кроме того, что очевидная реализация заключается в маскировании некоторых битов мантиссы (добавление 1, если эти биты еще не были 0).

2. Я думаю, это зависит от того, читаем ли мы «вычислить наименьшее целое значение не меньше x», чтобы означать: «гарантируется, что возвращаемое значение является целым числом, равным …» или означает: «точный математический результат равен …, но возвращаемое значение подвержено обычной неточности».

3. @SteveJessop: На самом деле, единственный случай, когда у вас есть шанс столкнуться с проблемой, — это 64-разрядный int . В типичной ситуации с 32-разрядным int и double с 53-разрядной мантиссой вы выполняете переполнение int задолго до того, как возникнут какие-либо трудности с преобразованием double в точное целочисленное значение.

4. Что касается того, почему мы ожидаем разницы, я думаю, это довольно просто. Используя ваш example ( sqrt ), все нецелые результаты иррациональны, поэтому они не могут быть представлены точно. Единственное место, где есть место для любого вопроса об этом, дающего действительно точный ответ, — это когда мы представляем его в виде идеального квадрата.

5. Ну, я ожидаю ceil быть точным просто потому, что я могу представить себе простую реализацию, которая есть. Все, над чем я мучаюсь, — это то, гарантировано ли, что оно будет точным, и если да, то где (C99 и IEEE 754 являются кандидатами). Одновременно причина, по которой я не ожидаю sqrt , что она обязательно будет точной в пределах 0,5ulp, не является (как в случае с вами) просто потому, что она не может быть абсолютно точной. Это потому, что я случайно знаю, что его сложно реализовать с точностью до 0,5ulp без чрезмерно высоких затрат. Также ничего общего со стандартом, поэтому я тоже не совсем доволен…

Ответ №2:

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

 /* return the smallest positive integer whose square is at least x */
int isqrt(double x) {
  int y1 = ceil(sqrt(x));
  int y2 = y1 - 1;
  if ((y2 * y2) >= x) return y2;
  return y1;
}
  

Это будет обрабатывать нечетный случай, когда квадратный корень из вашего отношения a/b находится в пределах точности double .

Комментарии:

1. Мне нравится этот подход. Это доказуемо правильно, без необходимости знать или полагаться на детали реализации с плавающей запятой.

Ответ №3:

Равенство чисел с плавающей запятой действительно является проблемой, но ИМХО нет, если мы имеем дело с целыми числами.

Если у вас есть случай 100.0/4.0 , он должен идеально вычисляться 25.0 , поскольку 25.0 точно представим как значение с плавающей точкой, в отличие от, например 25.1 .

Ответ №4:

Да, вполне возможно, что s1 != s2 . Но почему это проблема? Кажется достаточно естественным, что s1 != (s1 0.1) .

Кстати, если вы предпочитаете 5.00001 округлять до 5.00000 вместо 6.00000 , используйте rint вместо ceil .


И чтобы ответить на актуальный вопрос (в вашем комментарии) — вы можете использовать sqrt , чтобы получить отправную точку, а затем просто найти правильный квадрат, используя целочисленную арифметику.

 int min_dimension_greater_than(int items, int buckets)
{
    double target = double(items) / buckets;
    int min_square = ceil(target);
    int dim = floor(sqrt(target));
    int square = dim * dim;
    while (square < min_square) {
        seed  = 1;
        square = dim * dim;
    }
    return dim;
}
  

И да, это можно значительно улучшить, это всего лишь краткий набросок.

Комментарии:

1. s1 и s2 являются целыми числами. приведение усекает десятичное число

2. Обратите внимание, что s1, s2 являются целыми числами. Я изучу rint, не уверен, будет ли он доступен для всех платформ, которые мне нужно поддерживать.

3. @Dan: если у вас нет rint , то floor(x 0.5f) даст тот же результат.

4. @Mike, спасибо. В этом случае rint это определенно не то, что я хочу. Мой комментарий к вопросу объясняет мой вариант использования.

5. @levis501 да, я знаю, что они оба усечены: я просто не понимаю, зачем вам добавлять 0.1 , если вы ожидаете, что он будет усечен. IOW: либо это не операция, и я не знаю, почему ее написал OP, либо нет, и я не знаю, почему он удивлен.

Ответ №5:

s1 всегда будет равно s2 .

Стандарты C и C мало что говорят о точности математических процедур. В буквальном смысле невозможно реализовать стандарт, поскольку стандарт C гласит, что sqrt(x) возвращает квадратный корень из x, но квадратный корень из двух не может быть точно представлен с плавающей запятой.

Реализация подпрограмм с хорошей производительностью, которые всегда возвращают правильно округленный результат (в режиме округления до ближайшего это означает, что результатом является представимое число с плавающей запятой, ближайшее к точному результату, с разрешенными связями в пользу младшего нулевого бита), является сложной исследовательской проблемой. Хорошие математические библиотеки нацелены на точность менее 1 ULP (поэтому возвращается одно из двух ближайших представимых чисел), возможно, что-то чуть больше .5 ULP. (ULP — это единица наименьшей точности, значение младшего бита, заданное конкретным значением в поле экспоненты.) Некоторые математические библиотеки могут быть значительно хуже этого. Вам нужно будет спросить своего поставщика или проверить документацию для получения дополнительной информации.

Так что sqrt может быть немного не в порядке. Если точный квадратный корень является целым числом (в пределах диапазона, в котором целые числа точно представимы в формате с плавающей запятой), а библиотека гарантирует, что ошибки меньше 1 ULP, то результат sqrt должен быть точно правильным, потому что любой результат, отличный от точного результата, находится на расстоянии не менее 1 ULP.

Аналогично, если библиотека гарантирует, что ошибки меньше 1 ULP, то ceil должен вернуть точный результат, опять же, потому что точный результат представим, а любой другой результат будет удален как минимум на 1 ULP. Кроме того, природа ceil такова, что я ожидаю, что любая разумная математическая библиотека всегда будет возвращать целое число, даже если остальная часть библиотеки не была высокого качества.

Что касается случаев переполнения, если ceil(x) находится за пределами диапазона, в котором все целые числа точно представимы, то ceil(x) .1 ближе к ceil(x), чем к любому другому представимому числу, поэтому округленный результат добавления .1 к ceil(x) должен быть ceil(x) в любой системе, реализующей стандарт с плавающей запятой (IEEE 754). Это при условии, что вы находитесь в режиме округления по умолчанию, который является округлением до ближайшего. Можно изменить режим округления на что-то вроде округления до бесконечности, что может привести к тому, что ceil(x) .1 будет целым числом выше, чем ceil(x) .