0

I've encountered a bug, where my variable with infinity is not equal to infinity. And I'm not even sure, in which cases.

Here is minimal example:

#include <iostream>
#include <limits>
#include <cmath>

using namespace std;


constexpr auto NT_INF = std::numeric_limits<double>::infinity();


// prints a number byte-by-byte
void printN(double n){
    auto tmp = (uint8_t*)&n;
    for (int i = 0; i < 8; i++) {
        wprintf(L"%4d ", tmp[i]);
    }
    wprintf(L"\n");
};


double * genNumbers() {
    double * numbers = new double[6];
    
    numbers[0] = 100;
    numbers[1] = 1;
    numbers[2] = 198263783302;
    numbers[3] = 198263783302;
    numbers[4] = 100;
    numbers[5] = 0;
    
    return numbers;
}


int main()
{
    // leave no chance for the compiler to optimize variables away
    double * numbers = genNumbers();
    
    // floor((100. * 1.) / (198263783302. - 198263783302.)) / 100.;
    numbers[5] = floor((numbers[0] * numbers[1]) / (numbers[2] - numbers[3])) / numbers[4];
    double number = numbers[5];

    printN(number);
    printN(NT_INF);
    
    wprintf(L"%f, %f, %d, %d, %d, %d\n", number, NT_INF, number == 1.0 / 0.0, number == NT_INF, isinf(NT_INF), isinf(number));
    
    delete numbers;

    return 0;
}

My compiler flags for GCC are: -fno-reorder-blocks -ffast-math -march=native -fno-exceptions -Ofast -fmodulo-sched -fgcse-sm -fgcse-las -fno-inline-small-functions -flto=2 -fgcse-lm -fira-region=all

If you try to run it in onlinegdb, then you will get something like

   0    0    0    0    0    0  240  127 
   0    0    0    0    0    0  240  127 
inf, inf, 1, 1, 1, 0

So NT_INF and number are the same, judging by printN output. At the same time, isinf says number is not infinity... But printf says it is???

If I try to compile locally, the situation is even worse (I suppose, it's so due to -march=native).

   0    0    0    0    0    0  240  127 
   0    0    0    0    0    0  240  127 
inf, inf, 0, 0, 1, 0

So here the number variable isn't even equal to infinity, using simple ==

I have absolutely no idea what's wrong with this example (an my real code). Maybe it's due to some flag or their combination, but there is too much of combinations to try them.

LiaVa
  • 198
  • 1
  • 10
  • 3
    https://stackoverflow.com/questions/7420665/what-does-gccs-ffast-math-actually-do – KamilCuk Jun 04 '23 at 11:51
  • 1
    `delete numbers;` should be `delete[] numbers;`. Better to use `std::vector` to make these memory management errors a things of the past. – Eljay Jun 04 '23 at 11:55
  • Why all the "magic" with integer casts? None of your numbers in main is infinte.For testing infinity there is : [std::isinf](https://en.cppreference.com/w/cpp/numeric/math/isinf). Anyway why are you mixing printf with C++? And why are you not using `std::vector` for your `numbers` (in current C++ new/delete should hardly ever be needed, this to avoid memory leaks). Not that you have an array of doubles, not some bit of memory representing a dobule. – Pepijn Kramer Jun 04 '23 at 11:55
  • @Eljay Nah.. just use std::vector, or in this case std::vector or even std::array – Pepijn Kramer Jun 04 '23 at 11:56

1 Answers1

6

-ffast-math implies -ffinite-math-only, which means that the compiler may optimize under the assumption that floating point values are always finite (i.e. never infinity or NaN). You are violating that assumption, so your code has practical undefined behavior.

See GCC documentation.

-Ofast also implies -ffast-math by the way and I think several other options are redundant with it. Also, the choices seem weird to me. Everything seems to be trying for maximum performance, but then you have -fno-reorder-blocks and -fno-inline-small-functions which disable basic optimizations.

Several of your compiler options cause the compiler to behave non-conforming to C, C++ and IEEE 754 standards. You should not use such optimizations options if you do not exactly understand what they do and imply for standard divergence.

user17732522
  • 53,019
  • 2
  • 56
  • 105