Use of fmod / variable gets corrupted /impossible output (using servo.h)

Im developing an incubator with moisture/heat/fan control and servos for rotating the eggs.
Because it needs to be real time for at least 21 days I use fmod to call my functions, with this basic construct

loop()
unsigned long currentMillis=millis();
if (fmod(currentMillis, MOISTURECHECKPERIOD) == 0) // timeto check moisture

I previously used delay() and I saw currentMillis variable change its value during the main loop ! Something in the servo library is not compatible.
Going to a real time implementation I used modulus % to find my timer intervals but that cannot calculate out to 21 days, so now use fmod

I will include a section of the code and its output - I cannot understand how my variable gets impossible values printed. It should check moisture every 30 seconds then run humidifier for 5000 milliseconds, not 4999

Code snippet:

// check for moisture need every MOISTURECHECKPERIOD service moisture
[tt]// if moisture needed, run moisture maker for MOISTURERUNTIME 30s every 3mins MOISTURECHECKPERIOD
if (fmod(currentMillis, MOISTURECHECKPERIOD) == 0) // timeto check moisture
 {
    if (digitalRead(HumWarnLED))  // then time to be moister !
    {
     #ifdef DEBUGMOIST
     Serial.print(currentMillis);
     Serial.print(" ");
     Serial.print(MOISTURECHECKPERIOD);
     Serial.println("humidifier On");
     #endif
      digitalWrite(MoistActivePin, HIGH); // moist on
      previousMoistMillis = currentMillis;  // start timer ( If the mainloop gets back here with the same millisecond count it should not matter being re-entrant)
    }
 }
 if ((currentMillis - MOISTURERUNTIME >= previousMoistMillis ) || (digitalRead(HumWarnLED)==0 ))
   {
     if ( digitalRead ( MoistActivePin) == HIGH)
     {
       #ifdef DEBUGMOIST
       Serial.print("humidifier Off at pM,cM, ");
       Serial.print(previousMoistMillis);Serial.print("-");
       Serial.print(currentMillis);Serial.print("=");
       Serial.println(currentMillis - previousMoistMillis);
       #endif
       digitalWrite(MoistActivePin, LOW); // moist off
     }
  }
 ... other code
[/tt]

Impossible Output: (snippet)

humidifier Off at pM,cM, 16829999-16830000=1
  16835 0:4:40:35, DHT Hum =,64.00, LM35 TEMP =,37.33, Avg Temp=,37.38, Heat=,1, Air=,0, Dry=,1, HMDFR=,0
  16840 0:4:40:40, DHT Hum =,64.00, LM35 TEMP =,37.54, Avg Temp=,37.44, Heat=,1, Air=,0, Dry=,1, HMDFR=,0
  16845 0:4:40:45, DHT Hum =,64.00, LM35 TEMP =,37.65, Avg Temp=,37.53, Heat=,1, Air=,0, Dry=,1, HMDFR=,0
  16850 0:4:40:50, DHT Hum =,63.00, LM35 TEMP =,37.86, Avg Temp=,37.60, Heat=,1, Air=,0, Dry=,1, HMDFR=,0
  16855 0:4:40:55, DHT Hum =,63.00, LM35 TEMP =,37.65, Avg Temp=,37.53, Heat=,1, Air=,0, Dry=,1, HMDFR=,0
16859999 30000.00humidifier On
  16860 0:4:41:0, DHT Hum =,63.00, LM35 TEMP =,37.65, Avg Temp=,37.53, Heat=,1, Air=,0, Dry=,1, HMDFR=,1
16860000 30000.00humidifier On
humidifier Off at pM,cM, 16860000-16864999=4999

impossible.txt (1.21 MB)

Further info:
Please explain this to me:

loop()
{
   if (fmod(26439999, 20000) == 0.0)
    Serial.println("Wrong");
}

Result

Wrong
Wrong
Wrong
etc

Use of uninitialized value in concatenation (.) at (eval 1) line 1. Use of uninitialized value in print at (eval 1) line 1. Diagnostics that occur within eval may also report the file and line number where the eval is located, in addition to the eval sequence number and the line number within the evaluated text itself.
alaskasworld.com

SimonSolar2C:
Please explain this to me:

Your constant exceeds the precision of a 32 bit floating point number.

In other words, what you see is not what the compiler sees. That's just the way floats roll.

SimonSolar2C:
Because it needs to be real time for at least 21 days I use fmod to call my functions, with this basic construct

There are several problems with what you're doing. Please stop.

This will always be the correct choice.

@Coding Badly, please correct me if I’m wrong but
26439999 =0000 0001 1001 0011 0111 0001 0011 1111 b
which occupies 25 bits so less than 4 bytes, so Im okay for now with that value fitting into an unsigned long?

I’ve often used modulus operator as a timing shorthand, no need to store last value, because the repeating period I seek should occur when the result is zero, ie
if (currentMillis % period_I_seek == 0 ) then do my timed operation …

However the % operator is limited to integers, so I used fmod operator to provide me with enough range for my 21 day millisecond count.
But what Im seeing is the fmod is not providing mathematically correct results :

#include <math.h>
 unsigned long x=26438000;
 
 void setup() {
  // put your setup code here, to run once:
  Serial.begin(57600);
      Serial.print(x);
}

  
void loop() {

  // put your main code here, to run repeatedly:
   {
    Serial.print("fmod(");
    Serial.print(x);
    Serial.print("),20000)=");
    Serial.println(fmod(x,20000.0));

    x+=1;
   }
}
fmod(26439989),20000)=19988.00
fmod(26439990),20000)=19990.00
fmod(26439991),20000)=19992.00
fmod(26439992),20000)=19992.00
fmod(26439993),20000)=19992.00
fmod(26439994),20000)=19994.00
fmod(26439995),20000)=19996.00
fmod(26439996),20000)=19996.00
fmod(26439997),20000)=19996.00
fmod(26439998),20000)=19998.00
fmod(26439999),20000)=0.00
fmod(26440000),20000)=0.00
fmod(26440001),20000)=0.00
fmod(26440002),20000)=2.00
fmod(26440003),20000)=4.00
fmod(26440004),20000)=4.00
fmod(26440005),20000)=4.00
fmod(26440006),20000)=6.00
fmod(26440007),20000)=8.00
fmod(26440008),20000)=8.00
fmod(26440009),20000)=8.00
fmod(26440010),20000)=10.00
fmod(26440011),20000)=12.00

I used fmod operator to provide me with enough range for my 21 day millisecond count.

Using millis() for timing you can time periods up to 49 and a bit days so why the use of other, more esoteric functions ?

Okay I see it now - fmod takes a double signed, so thats 23 bits precision, 9 bits for exponent.
Pity. I’ll have to rewrite all my timers using lastTimer method or write my own unsigned long modulus math function :wink:

Thanks for your help folks.

the % operator is limited to integers

millis() returns an unsigned long
An unsigned long is an integer

Why can't you use % ?

UKHeliBob:
millis() returns an unsigned long
An unsigned long is an integer

Why can't you use % ?

Because millis occasionally skips a value.

Because the code around the check can take more than one millisecond to run.

Because the division is far more expensive than the subtraction / comparison.

You literally wrote the book! Don't waver now.

So, forget about using modulo. Just do millis timing the conventional way ... subtract "then" from "now" and look for the difference to be >= the required delta-T. The eggs aren't going to care if they're turned a couple of milliseconds late.

All good reasons, but is using fmod() the solution to any of them ?

UKHeliBob:
All good reasons…

…to never use any form of modulus when working with Arduino time / millis.

UKHeliBob:
is using fmod() the solution to any of them ?

fmod is never the answer for questions involving millis.

Because it needs to be real time for at least 21 days

No problem, using millis() as it is intended to be used, with simple addition/subtraction of unsigned long integers.

Tutorial

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.