Unreal Engine nonsensical code duplication shenanigans

Looking in “GenericPlatformMath.h”, just like in so many other places in Unreal Engine’s (8 million lines?) code base, I found pages of the following. My question is, where does this strange (and harmful) practice come from, and who’s paid to do that?

  static CORE_API FORCENOINLINE float Fmod(float X, float Y);
  static CORE_API FORCENOINLINE double Fmod(double X, double Y);
  RESOLVE_FLOAT_AMBIGUITY_2_ARGS(Fmod);
  
  static FORCEINLINE float Sin( float Value ) { return sinf(Value); }
  static FORCEINLINE double Sin( double Value ) { return sin(Value); }
  
  static FORCEINLINE float Asin( float Value ) { return asinf( (Value<-1.f) ? -1.f : ((Value<1.f) ? Value : 1.f) ); }
  static FORCEINLINE double Asin( double Value ) { return asin( (Value<-1.0) ? -1.0 : ((Value<1.0) ? Value : 1.0) ); }
  
  static FORCEINLINE float Sinh(float Value) { return sinhf(Value); }
  static FORCEINLINE double Sinh(double Value) { return sinh(Value); }
  
  static FORCEINLINE float Cos( float Value ) { return cosf(Value); }
  static FORCEINLINE double Cos( double Value ) { return cos(Value); }
  
  static FORCEINLINE float Acos( float Value ) { return acosf( (Value<-1.f) ? -1.f : ((Value<1.f) ? Value : 1.f) ); }
  static FORCEINLINE double Acos( double Value ) { return acos( (Value<-1.0) ? -1.0 : ((Value<1.0) ? Value : 1.0) ); }
  
  static FORCEINLINE float Cosh(float Value) { return coshf(Value); }
  static FORCEINLINE double Cosh(double Value) { return cosh(Value); }
  
  static FORCEINLINE float Tan( float Value ) { return tanf(Value); }
  static FORCEINLINE double Tan( double Value ) { return tan(Value); }
  
  static FORCEINLINE float Atan( float Value ) { return atanf(Value); }
  static FORCEINLINE double Atan( double Value ) { return atan(Value); }
  
  static FORCEINLINE float Tanh(float Value) { return tanhf(Value); }
  static FORCEINLINE double Tanh(double Value) { return tanh(Value); }
  
  static CORE_API float Atan2( float Y, float X );
  static CORE_API double Atan2( double Y, double X );
  RESOLVE_FLOAT_AMBIGUITY_2_ARGS(Atan2);
  
  static FORCEINLINE float Sqrt( float Value ) { return sqrtf(Value); }
  static FORCEINLINE double Sqrt( double Value ) { return sqrt(Value); }
  
  static FORCEINLINE float Pow( float A, float B ) { return powf(A,B); }
  static FORCEINLINE double Pow( double A, double B ) { return pow(A,B); }
  RESOLVE_FLOAT_AMBIGUITY_2_ARGS(Pow);
  
  /** Computes a fully accurate inverse square root */
  static FORCEINLINE float InvSqrt( float F ) { return 1.0f / sqrtf( F ); }
  static FORCEINLINE double InvSqrt( double F ) { return 1.0 / sqrt( F ); }
  
  /** Computes a faster but less accurate inverse square root */
  static FORCEINLINE float InvSqrtEst( float F ) { return InvSqrt( F ); }
  static FORCEINLINE double InvSqrtEst( double F ) { return InvSqrt( F ); }

Floats and doubles are different things. What about this solution do you find nonsensical? What would a better approach be?

Doing nothing … ? These all already have float / double overloads. Look in “cmath”.

I think he meant “making wrappers at all is a kinda redundand thing”

  • Firstly you may consider the fact that at the time of the writing those overrides could just not exist at all. Or even may still not exist in libraries of old compiler versions;
    • Idk if true in this case, but theoretically possible;
  • Then it’s the matter of forcing the CamelCase naming convention across whole engine;
  • Then force the namespacing it into FMath:: to not let some max() break everything one more time;
  • And then - such wrappers let you write platform independent code, in case math libraries are inconsistent between platforms;
    • Idk if true in this case, but theoretically possible.

@IrSoil already covered a lot of good reasons for having a platform-independent API.

Just for fun, I did a bit of archeology in our SCM history and this code dates back to at least UE3 days and 2011 (very likely even older).

There’s at least one example where having a platform independent API was useful, in 2015 we provided a custom atan2 implementation due to compiler bugs:

FMath::Atan2() no longer calls atan2f() because of some compiler or library bugs causing it to randomly return NaN for valid input. It now uses a high-precision minimax approximation instead, measured to be 2x faster than the stock C version.

In some cases like this one it’s useful to be able to replace a standard library implementation with something faster, e.g. if the standard library has stricter requirements on accuracy that forces them to use a slower algorithm.

2 Likes