ESP32 calculates floats faultily opp. to my M4 and my M3/Due

hi,
my ESP32 calculates floats faultily opp. to my M4 and my M3/Due
(Arduino IDE 1.8.8 )

e.g, after a long calculation (1264 steps) by the Feather M4 I get the result 32.843750,
whilst the Feather ESP32 calculates 54196.625
update:
I now retested by the Due, and here I also get the result 32.843750, just like the M4.

:o

this is the relevant code:

//  Lunar Lander

  
// preprocessor defaults for time sync: 
//#define SYNC_REALTIME      // real time sync; outcomment for time lapse


#if defined (SAM)
   #include "avr/dtostrf.h"   // sprintf() and dtostrf() for floats
#endif


//----------------------------------------------------
// flight control
//----------------------------------------------------
// public:
float mFuel=8200;          // fuel mass in kg for landing
float hi=15300;            // act height in m
float vHorz=1685;          // Horizontal orbital speed m/s 

float sTargm=470000;       // horizontal way to target landing place

float burnPerc=0;          // user input: burnrate %
float ftilt;               // tilt horiz...vert -1...0...+1
float tiltDeg;             // tilt degrees -90°...0...+90°

//----------------------------------------------------
// private: 
float ti=0.0, dt=0.5;       // act time, delta time in sec

const float g=1.62;               // Moon gravity
const float MoonRad=3476000/2.0;  // Moon radius
const float mLander=6500;         // lander mass in kg with launch fuel
const float Isp=3050;             // Rocket engine Specific Impulse
const float FBrake=45000;         // Rocket engine max Propulsion Force 

float burnMax=FBrake/Isp;   // absolute max fuel burnrate 
float mTotal=mLander+mFuel; // brutto weight with full tanks  
float rBrake;               // user Rocket brake force 100%, percentual
float dFuel=0;              // delta fuel
float burnf=0;              // burnrate factor 0.0 ... 1.0

float dh=0.0;               // delta height in m
float scaleH=hi/100;        // scaler for tft.hight=100%
float vVert=0.0;            // Impact speed in m/s   
float accVert=0;            // vertical accel (sum)
float accBrake=0;           // acc by break rockets
float fCentrifug=0;         // centrifugal force by orbital speed
float accCentrif=0;         // centrifugal accel by orbital speed

float sHorzm=0.0;           // horizontal way flown in m



//----------------------------------------------------
// Serial LogBook
//----------------------------------------------------
void LogBook(){
   char sbuf1[50], sbuf2[50] ;   
   char* headline1 = "t.sec  hi.m vVert vHoriz   ";
   char* headline2 = "Burn tilt    brake acc  Fuel TBase.m";
   
   Serial.print(headline1);
   Serial.println(headline2);
   
   sprintf(sbuf1,    "%5.1f %5d %4d  %4d     ", 
                    ti, (int)hi, (int)vVert, (int)vHorz);
   sprintf(sbuf2,    "%3d%% %4d     %3.1f %4.1f %5d %f", 
                    (int)burnPerc, (int)(ftilt*90), accBrake, accVert, 
                    (int)mFuel, (float)(sTargm-sHorzm));
                    
   Serial.print(sbuf1); Serial.println(sbuf2);  
   Serial.println();  
}



uint32_t dtime;

//----------------------------------------------------
// Lander Move
//----------------------------------------------------
void LanderMove() {  
   static float t0=ti;     
   
   dtime=millis();
   if(hi>0) { 

      // Burn Ratio:
      // 100 = 100% == full brake power
      //  50 =  50% == half brake power
      //  0  =  0%  == zero brake power
      //  or anything in between
      //
      // 100% BURN RATIO (BRAKE  POWER) 
      //     => 45000kN propulsion force
      //     => 14,75 kg Fuel burn per second
      //     => brake accelation = 3m/s²
      
      // XEROX Board Computer program, debug:    
      if( sHorzm<15600.0) burnPerc=0;  //  sHorzm<15600    
      else
      if( vVert>60||vHorz>30 ) burnPerc=100;  //  
      else
      if(vVert>50) burnPerc=65;
      else       
      if(vVert>40&&hi<3000) burnPerc=45; 
      else
      if(vVert>35&&hi<1800) burnPerc=40; 
      else
      if(vVert>10&&hi<1100) burnPerc=35; 
      else
      if(vVert>=1&&hi<120) burnPerc=33; 
      else      
      burnPerc=0;

      // calculate control

      fCentrifug=vHorz*vHorz*mTotal/(MoonRad+hi);
      accCentrif=fCentrifug/mTotal;
      
      if(mFuel==0) burnPerc=0;          // no fuel, no burn ;)      
      burnf = burnPerc/100.0;           // factor  0...1

  //  vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 
      dFuel=  burnMax*burnf*dt;        // try burnrate: enough fuel? 
      dFuel = min(dFuel, mFuel);       // calc available rest fuel 
      
      burnf = (dFuel/burnMax)/dt;       // re-calc burnrate by rest fuel
      burnPerc = burnf*100;
      
      rBrake= FBrake*burnf;           // rel brake force  
      accBrake= rBrake/mTotal;        // rocket brake acceleration
      
      mFuel = max(mFuel-dFuel, (float)0);     // rest fuel >=0          
      mTotal= mLander+mFuel;          // new total mass   
  
      if(vVert>=5  ) {
        if(vVert>=40 && vHorz>2 )  ftilt=0.65; // 58.5°
        else 
        
        if(vVert>=30 && vHorz>2 )  ftilt=0.75; // 67.5°          
        else 
        if(vVert>=20 && vHorz>2 )  ftilt=0.80; // 72.0°                 
 
        else
        if(vHorz<=2 && vHorz>=-2) ftilt=0.0; 
        else        
        if(vHorz<0)    ftilt=0.0; 
    
        else ftilt=0.85;    // 76.5°          
      }
      else 
      if(vVert<5&&vHorz>20 )  ftilt=1; 
      else ftilt=0; 

      accVert = g - (1-abs(ftilt))*accBrake -accCentrif;
      vVert = vVert + accVert*dt;        // fractional vertical brake
      
      if(vHorz>0) 
      vHorz = vHorz - (ftilt)*accBrake*dt;   // fractional horizonal brake

      //  vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 
      sHorzm = sHorzm + vHorz*dt;            // horizontal way flown      
      dh = 0.5*accVert*dt*dt + vVert*dt ;    // delta height by res. grav+centrifug.+brake acc.
      hi = hi-dh;                            // new resulting height

      //-----------------------------------------------         
      // pause
      #ifdef SYNC_REALTIME  
         while( millis()-dtime < dt*1000 );  
      #endif   
      //-----------------------------------------------
                
      ti+=dt;       
      LogBook();
      t0=ti;           
   
      //-----------------------------------------------
      // Landing specs/ratings
      
                                                     
      if ( (( hi<=0 && vVert>=5 ) && vVert<8) )   // Damage              
      {  
        Serial.println();
        Serial.println(" !! Damage !!");    
    
      }
      
      else       
      if ( hi<=0 && vVert<5 && abs(sTargm-sHorzm)>100) {  // very good Landing but way off
        burnPerc=0;                
        Serial.println();
        Serial.println("Very good but way off!");    
      }

      else       
      if ( hi<=0 && vVert<5) {               // Perfect Landing
        burnPerc=0;                
        Serial.println();
        Serial.println("Perfect Landing!");      
      }


      else  
      if ( hi<=0 )        // B L A S T
      {  
        Serial.println();
        Serial.println(" !!! B L A S T !!!");          
      }
   }    
}


//----------------------------------------------------
// setup
//----------------------------------------------------
void setup() {
  #if defined (SAM)
     asm(".global _printf_float"); // sprintf() and dtostrf() for floats
  #endif
  
  Serial.begin(115200);
  delay(2000);
  Serial.println("Serial started!"); 

  sHorzm=0; // way flown 
  
  Serial.println();
   
  delay(dt*1000);  
  
  fCentrifug=vHorz*vHorz*mTotal/(MoonRad+hi);
  accCentrif=fCentrifug/mTotal;
  accVert=g-accBrake-accCentrif;
  LogBook();  
}


//----------------------------------------------------
// loop
//----------------------------------------------------   
void loop(void) {  
          
   if (hi>0)  {
      LanderMove();      
   }
  
}

the questionable value is the last one in either Serial output line (TBase.m)
actually the M4 == DUE result
32.843750
is probably the correct one...!


edit, update:
could be confirmed for MEGA2560 too, also 32.843750 here!

so just faulty for ESP32, both for fp32 and fp64 double as meanwhile tested.

Has anyone ever heard or read such a thing and perhaps can check the calculation results and possibly confirm either one for M3, M4 and/or ESP32 platforms?

Meanwhile, the fact that a ESP32 evaluate differently (i.e. 54196.625) from Due, M4, and Mega2560 (i.e., all 32.843750) was confirmed by another user in another forum.

(ESP32)
TBase.m
54196.625000

Anyone else here to test the code from above with a proprietary ESP32 vs. a Due or a M4?

dsyleixa:
Anyone elese here to test the code from above with a proprietary ESP32 vs. a Due or a M4?

Perhaps ask in the Expressif forums if you dont get an answer here.

Its their code.

yes I will do so as soon as there are more people who could confirm that issue on either MCU.

meanwhile a 2nd user could confirm my result, that M4 (identically to Due and Mega2560) evaluate to 32.843750 , differently to ESP32 (54196.625):

Running on a Feather M4 Express, the final value of TBase.m was 32.843750.

So I would appreciate very much if more people could test the code from above with a proprietary ESP32 vs. a Due or a M4...!

Hi dsyleixa,

The Arduino Zero (ARM Cortex M0+) agrees with the other ARM microcontrollers and evaluates to 32.843750. The ESP32 (WROOM32) generates 54196.625.

MartinL:
Hi dsyleixa,

The Arduino Zero (ARM Cortex M0+) agrees with the other ARM microcontrollers and evaluates to 32.843750. The ESP32 (WROOM32) generates 54196.625.

thank you very much!
How do you think about that issue?
Actually it's not quite reasonable that ESP32 evaluates differently to (all?) other boards...?

Hi dsyleixa,

I can only imagine that it has something to do with the ESP32's compiler. The results from the ARM and ESP32 boards seem to gradually diverge that may suggest some error in precision.

I noticed that problem still persists if the float data types are coverted to double.

has the ESP32 got a hardware fpu coprocessor, like the M4?
or just software-float computations?

PS, FYI:
I just reported that already at the Espressive github repo:

nonetheless, more testers are highly appreciated! 8)

has the ESP32 got a hardware fpu coprocessor, like the M4?
or just software-float computations?

I believe the ESP32 does have a single precision FPU, although there doesn't appear to be much information about it.

I found this in the ESP32-IDF (IoT Development Framework) documentation:

5.9.6 Floating Point Aritmetic
The ESP32 supports hardware acceleration of single precision floating point arithmetic (float) via Floating Point
Units (FPU, also known as coprocessors) attached to each core. The use of the FPUs imposes some behavioral
restrictions on ESP-IDF FreeRTOS.
ESP-IDF FreeRTOS implements Lazy Context Switching for FPUs. In other words, the state of a core’s FPU registers
are not immediately saved when a context switch occurs. Therefore, tasks that utilize float must be pinned to a
particular core upon creation. If not, ESP-IDF FreeRTOS will automatically pin the task in question to whichever core
the task was running on upon the task’s first use of float. Likewise due to Lazy Context Switching, interrupt service
routines must also not use float.

hm - thank you, interesting - nonetheless, the issue is also about fp64, but that would probably not affect the 32bit-fpu.
::?

Hi dsyleixa,

I managed to track down the bug.

After comparing the results from my Arduino Uno and ESP32 on a spreadsheet, I noticed that there was a spurious reading for the vertical acceleration (accVert variable) on the ESP32 during the 105th iteration.

It turns out that absolute abs() function in C should only be used with integers. However, it appears that while the AVR and ARM abs() function also accepts float data types, the ESP32 doesn't.

Replacing the abs() function for the equivalent floating point fabsf() function instead solves the issue. The AVR, ARM and ESP32 are now in agreement.

Nice work! I was suspecting one of the “questionable “ Arduino macros like abs(), but wasn’t sure how that would fail...

+1
I double that!
Great work, congratulations , and many thanks for your efforts
8) 8) 8)!

I took a closer look into this issue and found that it's a bit complicated. The standard C++ and C implementations of abs() indeed don't work with float. However, Arduino has replaced that with their own macro implementation in their hardware cores:
ArduinoCore-avr/cores/arduino/Arduino.h at 1.6.23 · arduino/ArduinoCore-avr · GitHub

#define abs(x) ((x)>0?(x):-(x))

which does work with any type. This macro will be used in all .ino files, and any other source file that #includes Arduino.h.

So maybe the ESP32 core doesn't have this macro.

So maybe the ESP32 core doesn't have this macro.

I had a peek at the ESP32's "Arduino.h" file, it uses the same macro.

zoomx:
abs() does not work on floats · Issue #362 · arduino/reference-en · GitHub
So maybe the ESP32 core doesn't have this macro.

MartinL:
I had a peek at the ESP32's "Arduino.h" file, it uses the same macro.

no, although the macro existed, the ESP32 worked with standard <math.h> C code which requires fabs() for double and fabsf() for float, basically like gcc for Linux too (which produce the same faulty values by abs).

The weird thing is, the Arduino core on ESP32 has an abs() macro as well:
arduino-esp32/cores/esp32/Arduino.h at master · espressif/arduino-esp32 · GitHub

So there is probably a bug in the Arduino core for ESP32 here, where it isn't using the "Arduino-ified" abs() macro for some reason.

If you look at the github discussion, that could be resolved by a PR using std::abs() instead, which works correctly for either value type.
Instead, using Arduino macros for abs() is actually a bad practice.

Thanks for the link, I followed it through to the pull request: https://github.com/espressif/arduino-esp32/pull/2738.

So seems that the ESP's abs() macro in the "Arduino.h" file is being subsequently overriden by another redefinition of macro. This redefinition is in the included file (which in turn calls on the ), a number of lines further down. The standard library's abs() doesn't process float data types.

This explains why the abs() macro in the "Arduino.h" file exists, but is never used.

The standard library's abs() doesn't process float data types.

You are right, I stand corrected - I was confused by a chart table

abs() for fp is only since C++17, not for C++11 or C++14:

(since C++17)
float abs( float arg );
(1)
double abs( double arg );

std::abs(float), std::fabs, std::fabsf, std::fabsl

C++

Numerics library

Common mathematical functions

Defined in header

Defined in header

(since C++17)
float abs( float arg );
(1)
double abs( double arg );
(2)
long double abs( long double arg );
(3)
Defined in header

(4)
float fabs ( float arg );

float fabsf( float arg );
(since C++11)
double fabs ( double arg );
(5)
(6)
long double fabs ( long double arg );

long double fabsl( long double arg );
(since C++11)
double fabs ( IntegralType arg );
(7) (since C++11)

https://en.cppreference.com/w/cpp/numeric/math/fabs

Hi dsyleixa,

Thanks for investigating, that's interesting, I think it makes sense that they're chosing to effectively overload the abs() function, so that it will operate with float data types in C++17 in the future.

In the ESP32's "Arduino.h" file, I noticed that they moved the min() and max() macros to the bottom of the file (after the include). If the Arduino abs() macro definition:

// undefine stdlib's abs if encountered
#ifdef abs
#undef abs
#endif

#define abs(x) ((x)>0?(x):-(x))

...is also moved to the bottom of the "Arduino.h" file as well, it effectively overrides the standard library's abs() function and your LunarLander code works with abs() oncemore.

In other words the Arduino abs() macro itself works just fine, it's just that it's currently in the wrong place in the ESP32's "Arduino.h" file.