#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
intox < 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) .