C++ float-to-int conversion is undefined behavior and GSL's narrow() gets it wrong

C++ float-to-int conversion can be undefined behavior

Converting a float to an int in C++ is undefined behavior when the truncated value doesn't fit the destination type, yet compilers often don't warn. Even Microsoft's Guidelines Support Library (GSL) — which provides gsl::narrow() for safe narrowing — fails to handle this case, instead relying on benign UB. The author demonstrates the issue, explains why relying on hardware behavior is dangerous, and offers a proof-of-concept library plus UBSan detection.

Your code could suddenly stop working when the compiler happens to apply a different transformation.
  1. digitalPhonix

    Herb Sutter's comment on why it's ok is confusing to me:

    > Regarding the use of UB internally: It's okay and if anyone is worried about it the use of UB is benign on the platforms we target (e.g., they don't involve hitting any hardware trap representations for these types)

    Isn't the outcome of the UB (ie. whether it will "rm -rf /" or something else) dependent on both the target and the compiler? And the compiler (or future compiler) could plausibly make the assumption that the narrowing to an unrepresentable value will never occur and change behaviour because of it?

  2. gpvos

    Sounds like the standard should say that it results in an implementation-defined value (or wording to that effect). Saying it's UB gives the compilers way too much leeway.

  3. pjmlp

    Hopefully this will be part of UB fixes for C++29, where plenty of UB is being redefined as erroneous behaviour instead.

  4. dmitrygr

    > The correct fix is to bounds check before casting.

    This will do wonders for speed. Actually explicitly using the safe isntr might be better. Something like this will happily compile to a single instr and cause you no grief even if the compiler had it out for you with UB. These instrs all clearly define outputs for all inputs (note that said outputs may not match across architectures)

    static inline __attribute__((always_inline)) int f2i(float myFloat) {

    int myInt;

    #if defined(__arm__)

    asm("VCVT.S32.F32 %0, %1":"=r"(myInt), "t"(myFloat));

    #elif defined (__aarch64__)

    asm("FCVTZS %0, %1":"=r"(myInt), "w"(myFloat));

    #elif defined (__x86_64__)

    asm("CVTTSS2SI %0, %1":"=r"(myInt), "x"(myFloat));

    #else

    #if 0 // be boring

    if (myFloat <= TOO_SMALL_FLOAT || myFloat => TOO_BIG_FLOAT)

    abort();

    #else

    #warning "Embrace the UB"

    #endif

    myInt = (int)myFloat;

    #endif

    return myInt;

    }

  5. functionmouse

    float considered harmful

More from this day

2026-08-03