Почему коэффициент разницы, где h = FLT_MIN, всегда равен 0? FLT_MIN теряется в неточностях с плавающей запятой?

Я пытаюсь найти наклон кривой Безье, используя разностный коэффициент. Коэффициент разницы: ( 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?

Что в t? Как запустить ваш код? Можете ли вы опубликовать минимально воспроизводимый пример ?

3CxEZiVlQ 08.08.2024 05:29

Это может быть 0,0f, а может и не быть, но на самом деле это очень-очень маленькое положительное число (и деление на него приведет к взрыву вашего ответа). В любом случае вам не следует использовать это приближение для производной, производная функции Безье тоже является функцией, и вам следует ее рассчитать, чтобы избежать числовой нестабильности.

Pepijn Kramer 08.08.2024 06:21

@PepijnKramer Ответы в разделе ответов, пожалуйста.

Bathsheba 08.08.2024 08:43

«Плавающие неточности» не являются реальностью. Вы должны анализировать арифметику с плавающей запятой в соответствии с правилами арифметики с плавающей запятой; они отличаются от правил для действительных чисел. Если вы думаете о математике с плавающей запятой как о сломанной реальной математике, вы никогда не поймете в ней смысла. Когда вы это сделаете int x = 1; std::cout << x/4;, вы не удивитесь, получив 0, хотя мы все знаем, что это 0,25. Подойдите к математике с плавающей запятой таким же образом.

Pete Becker 08.08.2024 13:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
4
78
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Результатом добавления t + FLT_MIN может быть t. Вот как работают числа с плавающей запятой. FLT_MIN — минимальное нормализованное положительное значение, но это не значит, что если вы прибавите его, скажем, к 0.5, то получите число, отличное от 0.5. Числа с плавающей запятой позволяют работать как с очень маленькими значениями, так и с очень большими значениями, но не с маленькими и большими одновременно. Шаг приращения зависит от exponent значения.

знак экспонента (8 бит) мантисса (23 бита) 31 30 - 23 22 - 0

Вот простой пример (не учитывающий переполнение мантиссы):

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 значение.

Eric Postpischil 08.08.2024 14:11

Переосмысление адресов как различных типов указателей не является правильным способом доступа к байтам, представляющим значение. И в данном случае в этом нет необходимости, поскольку C и C++ предоставляют функции nextafter и nexttoward для получения последовательных значений с плавающей запятой.

Eric Postpischil 08.08.2024 14:19

@EricPostpischil, спасибо за ценный комментарий по поводу FLT_MIN. Меня часто критикуют за reinterpret_cast и за использование union в ответах. Я просто хотел ясно продемонстрировать причину.

Konstantin Makarov 08.08.2024 18:16
Ответ принят как подходящий

Если мы представим, что числа с плавающей запятой реализованы как десятичные числа с 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).

Eric Postpischil 08.08.2024 14:17

@EricPostpischil Я отвечал на вопросы Any idea what's causing this? why is FLT_MIN still 0?, вопрос нет how do I accurately calculate a numerical derivative

Alan Birtles 08.08.2024 14:28

В этом ответе говорится: «На самом деле вам нужно найти следующее ближайшее число». Это не то, что ОП хочет сделать.

Eric Postpischil 08.08.2024 14:59

@EricPostpischil, возможно, это не то, что им нужно делать, чтобы получить наилучшие результаты, но это вопрос, который они задали

Alan Birtles 08.08.2024 15:30

Нигде в вопросе ОП не упоминает о желании найти следующее ближайшее число.

Eric Postpischil 08.08.2024 15:36

@EricPostpischil не явно нет, но они пытаются добавить к числу наименьшее возможное число с плавающей запятой, что указывает на то, что они пытались найти следующее число

Alan Birtles 08.08.2024 16:55

@AlanBirtles Найти следующее ближайшее число. Моя цель действительно - добавить к числу наименьшее возможное значение с плавающей запятой. Я попробовал использовать std::nextafter, и он работает так, как задумано. Я также могу найти небольшую разницу «h» между результатом nextafter(t, FLT_MAX) и t. и результаты нормальных векторов на самом деле все еще достаточно точны, чтобы дать мне целые числа там, где я ожидаю, после сравнения результатов с примером Desmos. Огромное спасибо за ваше решение и ваш скромный вклад в развитие сообщества 🙌.

stuck_zipper 08.08.2024 18:13

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

Как передать предыдущий кадр как Sampler2D во фрагментный шейдер в Vulkan?
Найдите наименьший многоугольник с определенным ребром на двумерном графе Дейкстры
Самый быстрый алгоритм заполнения перекрывающихся прямоугольников пикселей
Wes_palettes: Как получить шестнадцатеричные коды цветов из палитры Весандерсона, если для нее установлено непрерывное количество цветов, превышающее предопределенные
SDL_Quit ошибка сегментации
Почему этот треугольный шейдер, который я написал, разбивается на четыре?
Принципы и практика программирования Страуструпа, 3-е издание, графика: почему передача функции обратного вызова в Window::timer_wait дает неожиданные результаты?
Шкала разрешения экрана в игре JavaFx
Как я могу использовать PowerShell для определения активной видеокарты с максимальной емкостью видеопамяти?
Gnuplot: установить соотношение сторон осей X и Y на трехмерном графике