c++graphicsfloating-pointlimitcalculus

why does the difference quotient where h = FLT_MIN always equals 0? How can I add the smallest number possible to a float?


I am trying to find the slope of a Bezier curve using the difference quotient:

( f(x + h) - f(x) ) / h

In calculus we usually use limits where we assume h is infinitely small and we evaluate the formula to find f'(x) which is the derivative formula.

Here, I wanted to try a similar concept and make h equal to FLT_MIN or FLT_TRUE_MIN which is supposed to be the smallest positive float value, and add it to 't' to approximate adding the smallest number possible to 't'. However, when I run getApproxNormal(0.5f) I just get 0 or NaN instead of somewhere in the ballpark of what the normal vector ratio should be.

sf::vector2f is a simple class that defines a mathematical vector with two coordinates (x and y)

#include <cmath>
#include <float.h>
class Vec2f //sf::vector2f
{
public:
    float x, y;
    Vec2f() : x(0), y(0) {}
    Vec2f(float X, float Y) : x(X), y(Y) {}
    friend Vec2f operator- (const Vec2f& a)
    {
        return Vec2f(-a.x, -a.y);
    }
    friend Vec2f operator- (const Vec2f& left, const Vec2f& right)
    {
        return Vec2f(left.x - right.x, left.y - right.y);
    }
    friend Vec2f operator/ (const Vec2f& left, float right)
    {
        return Vec2f(left.x / right, left.y / right);
    }
};

const Vec2f _p0(0, 0);
const Vec2f _p1(0, 100);
const Vec2f _p2(100, 100);

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);
}
float getApproxNormal(float t)
{
    Vec2f d = (getBezierPoint(t + FLT_MIN) // issue is that both calls 
        //return the same value even after adding FLT_MIN
        - getBezierPoint(t)) / FLT_MIN;

    return -(d.x / d.y); // returns the negative inverse of the derivative
}

void main()
{
    float normal = getApproxNormal(0.5f); // return NaN
}

As far as I know, it's difficult for a float to represent a larger number without sacrificing the smaller digits causing data loss in the least significant digits. This must mean that the smallest number I can add to a larger number varies depending on the larger number. If this is the case, how can I find the smallest number that I can add to a given float value.


Solution

  • If we imagine floating point numbers are implemented as decimals with a 5 digit number and a 2 digit exponent then the smallest possible number would be 1.0000 * 10 -99 if we add this to 1.0000 * 10 0 then after rounding to our 5 digit number we still have 1.0000 * 10 0. Floating point numbers are essentially implemented in the same way just with binary mantissa and exponent rather than decimal ones.

    Therefore adding FLT_MIN to anything other than another very small number will likely round out to the same number. What you actually want to do is find the next nearest number, fortunately c++ provides functions to do this 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
    }
    

    Note that due to the limitations of floating point accuracy you might not get the exact answer you expect.