Я пытаюсь найти наклон кривой Безье, используя разностный коэффициент. Коэффициент разницы: ( f(x + h) - f(x) ) / h
В исчислении мы обычно используем пределы, в которых мы предполагаем, что h бесконечно мало, и вычисляем формулу, чтобы найти f'(x), которая является формулой производной. Здесь я хотел сделать что-то подобное и сделать h равным FLT_MIN или FLT_TRUE_MIN, который должен быть наименьшим положительным значением с плавающей запятой, и добавить его к «t», чтобы приблизиться к добавлению наименьшего возможного числа к «t». Однако, когда я запускаю getApproxNormal(0.5f), я просто получаю 0, а не примерно то, что должно быть в норме.
// _p0 = (0,0)
// _p1 = (0,100)
// _p2 = (100,100)
typedef sf::vector2f Vec2f;
getBezierPoint(float t) // float t is a value between 0 and 1 that
//determines the percentage of the curve to sample the point for
//example when t = 0.5 that's 50% between the start and the endpoint of
//the bezier curve
{
float x = powf(1 - t, 2) * _p0.x + 2 * (1 - t) * t * _p1.x + std::pow(t, 2) * _p2.x;
float y = powf(1 - t, 2) * _p0.y + 2 * (1 - t) * t * _p1.y + std::pow(t, 2) * _p2.y;
return Vec2f(x, y);
}
getApproxNormal(float t)
{
Vec2f d = (getBezierPoint(t + FLT_MIN) //both calls return the same value
- getBezierPoint(t)) / FLT_MIN;
return -(d.x / d.y); // returns the negative inverse of the derivative
}
Есть идеи, что является причиной этого?
Я попытался заменить FLT_MIN на 0,000001f, и это где-то примерно, но почему FLT_MIN все еще равен 0?
Это может быть 0,0f, а может и не быть, но на самом деле это очень-очень маленькое положительное число (и деление на него приведет к взрыву вашего ответа). В любом случае вам не следует использовать это приближение для производной, производная функции Безье тоже является функцией, и вам следует ее рассчитать, чтобы избежать числовой нестабильности.
@PepijnKramer Ответы в разделе ответов, пожалуйста.
«Плавающие неточности» не являются реальностью. Вы должны анализировать арифметику с плавающей запятой в соответствии с правилами арифметики с плавающей запятой; они отличаются от правил для действительных чисел. Если вы думаете о математике с плавающей запятой как о сломанной реальной математике, вы никогда не поймете в ней смысла. Когда вы это сделаете int x = 1; std::cout << x/4;
, вы не удивитесь, получив 0, хотя мы все знаем, что это 0,25. Подойдите к математике с плавающей запятой таким же образом.
Результатом добавления t + FLT_MIN
может быть t
. Вот как работают числа с плавающей запятой. FLT_MIN
— минимальное нормализованное положительное значение, но это не значит, что если вы прибавите его, скажем, к 0.5
, то получите число, отличное от 0.5
. Числа с плавающей запятой позволяют работать как с очень маленькими значениями, так и с очень большими значениями, но не с маленькими и большими одновременно. Шаг приращения зависит от exponent
значения.
Вот простой пример (не учитывающий переполнение мантиссы):
float t = 0.5f;
auto i = *reinterpret_cast<uint32_t*>(&t);
i++; // mantissa increment
float next_t = *reinterpret_cast<float*>(&i);
std::cout.precision(10);
std::cout << next_t << " - " << t << " = " << next_t - t;
Выход:
0.5000000596 - 0.5 = 5.960464478e-08
То есть ближайшее число, большее 0.5
, отличается от него на 5.960464478e-08
.
«FLT_MIN
— минимальное значение, которое можно получить в результате расчетов» неверно. Минимальное значение, которое может быть результатом арифметической операции float
, равно −∞. Минимальное положительное значение, которое может быть результатом арифметической операции float
, равно FLT_TRUE_MIN
. FLT_MIN
— минимальное нормальное float
значение.
Переосмысление адресов как различных типов указателей не является правильным способом доступа к байтам, представляющим значение. И в данном случае в этом нет необходимости, поскольку C и C++ предоставляют функции nextafter
и nexttoward
для получения последовательных значений с плавающей запятой.
@EricPostpischil, спасибо за ценный комментарий по поводу FLT_MIN
. Меня часто критикуют за reinterpret_cast
и за использование union
в ответах. Я просто хотел ясно продемонстрировать причину.
Если мы представим, что числа с плавающей запятой реализованы как десятичные числа с 5-значным числом и 2-значным показателем, то наименьшее возможное число будет 1,0000 * 10 -99 если мы добавим это к 1,0000 * 10 0 то после округления до нашего 5-значного числа мы все равно есть 1.0000 * 10 0. Числа с плавающей запятой по существу реализуются таким же образом, только с использованием двоичной мантиссы и показателя степени, а не десятичных.
Поэтому добавление FLT_MIN
к чему-либо, кроме другого очень маленького числа, скорее всего, округлит до того же числа. На самом деле вам нужно найти следующее ближайшее число, к счастью, в C++ предусмотрены функции для этого https://en.cppreference.com/w/cpp/numeric/math/nextafter:
getApproxNormal(float t)
{
float next = std::nextafter(t, FLT_MAX);
float diff = next - t;
Vec2f d = (getBezierPoint(next)
- getBezierPoint(t)) / diff;
return -(d.x / d.y); // returns the negative inverse of the derivative
}
Обратите внимание: из-за ограничений точности с плавающей запятой вы можете не получить ожидаемый точный ответ.
Следующее представимое число обычно не подходит для численного расчета производной. Учтите, что если производная f(x) равна, скажем, 1/3, то f(x+u), где u — ULP x, отличается от f(x) только на 1/3 своей ULP, поэтому f(x+u) может равняться f(x), а числовая производная будет рассчитываться как ноль. Что-то около «середины» точности может работать лучше, что-то вроде |x|•sqrt(FLT_EPSILON).
@EricPostpischil Я отвечал на вопросы Any idea what's causing this? why is FLT_MIN still 0?
, вопрос нет how do I accurately calculate a numerical derivative
В этом ответе говорится: «На самом деле вам нужно найти следующее ближайшее число». Это не то, что ОП хочет сделать.
@EricPostpischil, возможно, это не то, что им нужно делать, чтобы получить наилучшие результаты, но это вопрос, который они задали
Нигде в вопросе ОП не упоминает о желании найти следующее ближайшее число.
@EricPostpischil не явно нет, но они пытаются добавить к числу наименьшее возможное число с плавающей запятой, что указывает на то, что они пытались найти следующее число
@AlanBirtles Найти следующее ближайшее число. Моя цель действительно - добавить к числу наименьшее возможное значение с плавающей запятой. Я попробовал использовать std::nextafter
, и он работает так, как задумано. Я также могу найти небольшую разницу «h» между результатом nextafter(t, FLT_MAX)
и t. и результаты нормальных векторов на самом деле все еще достаточно точны, чтобы дать мне целые числа там, где я ожидаю, после сравнения результатов с примером Desmos. Огромное спасибо за ваше решение и ваш скромный вклад в развитие сообщества 🙌.
Что в
t
? Как запустить ваш код? Можете ли вы опубликовать минимально воспроизводимый пример ?