I'm writing classes for basic types, so that code is logically the same on multiple platforms and compilers (like int_least16_t for int). For fun! (I'm still a student.)
And I read this:
And what's worse:
Floating-point types MAY support special values: ∞, NaN or -0
Which means that float MAY be unsigned... 
[edit: Yes it's difrent thing, but there is no: ",but must suport negative numbers". Yo, with out things like this in standart it may not suport normal 0...(I don't have specyfication.) see]
I know it's like with __int128, and the standard is just a standard, but still... IEEE-754 is from 1985, but some machines can be weird, and some legacy hardware doesn't have a floating unit.
As I understand, float is mandatory (not optional like int16_t), but can be in any standard, and any set of values can be possible?
Only thing we have are some macros (<cfloat>):
- FLT_MIN,- FLT_MAX- Even if- FLT_MIN = IEEE-754::FLT_MIN, float can be non IEEE-754. For example float with: flipped exponent with fraction...
- FLT_RADIX- Base system? If so, can help to write the exact value. But still, float can be 3 bit or 200 bit (in size)...
- FLT_EPSILON- (from 1 to next) We might use it (with base) to check fraction size...
- FLT_MANT_DIG- Is it "mantissa" digits / fraction size?
- FLT_MAX_EXP- Exponent filled with 1... in IEEE-754, but outside can be a random number?
If float is like IEEE-754 (sign, exponent, fraction), then it's easy,
but if -0 and NaN are optional then it MAY be different.
Because I can't tell them apart, I can't use bit representation
(in a safe manner). And if ∞ is optional, float is no longer a safe type.
The only way out I see is to add macro to compiler.
I know it's a theoretical problem, but I'm interested if there is any check possible or we all write implementation dependent code, when we use the float keyword?
Edit 2022 May 04:
I came up with this:
User eg. code:
//User eg. code:
int main()
{
   float_M a = 1f;
   float_M b = 0f;
   std::cout << a/b; //should output infinty (IEEE-754)
}
//Code:
class float_M
{
public:
#ifdef __STDC_IEC_559__
   float data;
//...
   float_M operator/(float_M x){return float_M(data/x.data);}
//...
#else
   /*union{
      float data;
      struct{//For noSign case ("absolutly catastrofic" case)
         uint_least8_t sign : 1;
         uint_least8_t exponent : 8;
         uint_least32_t fraction : 23;
      }
   }*/ //no noSign case 
   float data;
//...
   float_M operator/(float_M x){return divide(this, x);}
//funtion pointer alert!
   static /*const (1*) */ float_M (*divide)(float_M a, float_M b) =
      /*std::numeric_limits<float>::is_signed ?(*/
         std::numeric_limits<float>::has_infinity ?(
            std::numeric_limits<float>::has_quiet_NaN ?(
               []{return float_M(a.data/b.data);}
            ): &_divide_noNaN
         ): &_divide_noNaN
      /*): &_divide_noSign*/
//...
#endif
}
It's ugly (have funtion pointer), but prevents unnesesery jumps at runtime. I hope c++23 will have better macros.
Also, more links:
Follow-up: Can floats not suport negative
 
    