Float maximum reached?

I want to increase a value every time a high on a pin is detected. This works if the value is 3455.101
When the value is 34551.101 (1 number more) the value is not increased anymore.

bool PulseReceived = false;
bool new_state = false;
bool previous_state = false;

float VALUE = 34551.101;     // start value meter
float FACTOR = 0.001;
float PULSE_COUNT = 0;

void pulse_setup() {
  pinMode(PULSE_PIN, INPUT);
}


void pulse_loop() {
  if ( digitalRead(PULSE_PIN) == LOW ) {
    digitalWrite(LED_BUILTIN, LED_ON);

    new_state = true;
    if (new_state != previous_state) {

    previous_state = new_state;
    
    VALUE = VALUE + FACTOR; // increase value with factor per pulse
    Serial.print("Pulse detected, increasing value with ");
    Serial.print(FACTOR, 3);
    Serial.print(" to: ");
    Serial.println(VALUE, 3);

    PULSE_COUNT = (PULSE_COUNT + 1);

    SAVE_BACKUP = true;

    Serial.print("Increading pulse count to: ");
    Serial.println(PULSE_COUNT);

    PulseReceived = true;
    digitalWrite(LED_BUILTIN, LED_ON);
    }
  }
  if ( digitalRead(PULSE_PIN) == HIGH ) {
    previous_state = false;
  }
}

output with value 34568.715

11:37:44.531 -> Pulse detected, increasing value with 0.001 to: 34568.715
11:37:44.531 -> Increading pulse count to: 2.00
11:37:44.720 -> Pulse detected, increasing value with 0.001 to: 34568.715
11:37:44.720 -> Increading pulse count to: 3.00
11:37:44.860 -> Pulse detected, increasing value with 0.001 to: 34568.715
11:37:44.860 -> Increading pulse count to: 4.00
11:37:44.998 -> Pulse detected, increasing value with 0.001 to: 34568.715
11:37:45.045 -> Increading pulse count to: 5.00
11:37:45.138 -> Pulse detected, increasing value with 0.001 to: 34568.715
11:37:45.138 -> Increading pulse count to: 6.00

output with value 3456.715 (1 number less)

11:37:44.531 -> Pulse detected, increasing value with 0.001 to: 3456.716
11:37:44.531 -> Increading pulse count to: 2.00
11:37:44.720 -> Pulse detected, increasing value with 0.001 to: 3456.717
11:37:44.720 -> Increading pulse count to: 3.00
11:37:44.860 -> Pulse detected, increasing value with 0.001 to: 3456.718
11:37:44.860 -> Increading pulse count to: 4.00
11:37:44.998 -> Pulse detected, increasing value with 0.001 to: 3456.719
11:37:45.045 -> Increading pulse count to: 5.00
11:37:45.138 -> Pulse detected, increasing value with 0.001 to: 3456.720
11:37:45.138 -> Increading pulse count to: 6.00

Why not use integers, and do the horrible floaty arithmetic when you want to present the results to a human?

(You have a 25 bit dynamic range, but only 23 bits to represent it)

The float number is a "floating point" number, that says it all.
The number "34551.101" will be put in 32-bits but something might fall off.
The value of "0.001" as a single floating point number is okay, but when added to "34551.101", then it is too small. The 32-bits are not enough.

You can add a million times "0.001" to "34551.101" but every time nothing will be added.

To count steps for a stepper motor or pulses, often a "unsigned long" is used.

Sometimes the value has to be increased with 0.5 increments. That's not possible with integers.

Thanks Koepel,

So you suggest to modify the code to unsigned long? Will it work with decimals too?

It's possible with fixed point numbers.

1 Like

Can you give some explanation?

Instead of .001, add 1 to a 32 bit integer.
Instead of .5, add 500 to a 32 bit integer.

2 Likes

When 0.001 is added with 34551.101 manually, the OP easily gets: 34551.102. So, why does the UNO based math fail to do it? I think that the OP expects more convincing analysis/answer.

Yes! DUE based math which supports binary64 (double precision) correctly adds 0.001 with 34551.001 and produces 34551.102.

34551.102

Again, the OP expects to know the difference between binary32 and binary64 formats for the representation of float numbers.

Yes, it is possible. For example, you can use an integer with ten times the value of the final intended result, and increase it by 5.

See reply #2

Use the smallest possible increment as the value of the least significant part of the integer counter.

If the smallest possible change is "0.5 increments", then the integer counter would have units of "half increments".

% Define counter
long int half_increment_counter = 0 ;
% Define conversion to other unit of interest, example is 40 half steps per revolution of a stepper motor
const int half_increments_per_revolution = 40 ;

% "0.5 increment" change is one unit
half_increment_counter = half_increment_counter + 1 ;

% "full increment" change is two "0.5 increments" is two units
half_increment_counter = half_increment_counter + 2 ;

% Number of revolutions calculated from half_increment_counter
float revolutions = half_increment_counter / half_increments_per_revolution ;

If this doesn't make sense, then perhaps it would be helpful if you could provide a higher level description of your project.

The project asks a user to give in a value and a factor
the value has to be increased with the factor when there is a pulse detected.

so if the value is 100000 and the factor is 0.5, the new value is 100000 + 0.5 = 100000.5
This works if the amount of numbers is less or equal to 8. If the amount of numbers is larger it gives back wrong data.

Assuming you mean "significant digits", the value 10 000 000 decimal requires log (10000000) / log (2) bits to represent - that calculation comes out to over 23 bits.
A 32 bit float can only accurately represent a range of 23 bits (the mantissa), with an exponent of seven bits plus a sign bit.

1 Like

Maybe this would help: Kahan summation algorithm - Wikipedia

using

unsigned long PULSE_COUNT = 0;

and just accumulating the pulses directly, the upper limit is 4294967295.

  • Then just display this in text as 4294967.295 (add decimal point).
  • If using PULSE_COUNT in calculations, just multiply by 0.001 and cast to float.
  • Note float maximum = 3.4028235E+38. As the number gets bigger, you loose resolution at the low end because of the "floating" window of 6-7 digits of precision (looking at the number from left to right).

Remember that "floating point numbers" are essentially using a form of "scientific notation." A number like 0.023 is actually represented similarly to "2.3 * 10-2" (it uses base 2, but the idea is the same.)

The "mantissa" ("2.3" in the example) is limited in the number of digits (bits) that can be present (about 6 digits for AVR floating point.

So 300000 is fine (3 * 105 - one digit), and 0.001 is fine ((1 * 10-3 - one digit), but to add them together you have to "normalize" the numbers so that the exponent is the same.
That means the .001 becomes ".00000001 * 105" - eight digits, and when rounded to the 6 digits that are available, it becomes 0.

(Put another way, "floating point" lets you use very large numbers, or very small numbers, but the "precision" is less than a fixed point number of the same size.)

1 Like

Counters take integer values. You are safe using an integer type here:

unsigned int PULSE_COUNT=0; // or unsigned long if you expect more than 65535 pulses

Then, you can use

// before setup()
float START_VALUE = 34551.101;     // start value meter

// VALUE is now an expression, not a variable
#define VALUE (START_VALUE + FACTOR * PULSE_COUNT)

...

// new pulse_loop()
void pulse_loop() {
  if ( digitalRead(PULSE_PIN) == LOW ) {
    digitalWrite(LED_BUILTIN, LED_ON);

    new_state = true;
    if (new_state != previous_state) {

    previous_state = new_state;

    PULSE_COUNT = (PULSE_COUNT + 1); // this now goes before using the new VALUE

    Serial.print("Pulse detected, increasing value with ");
    Serial.print(FACTOR, 3);
    Serial.print(" to: ");
    Serial.println(VALUE, 3);

    SAVE_BACKUP = true;

    Serial.print("Increasing pulse count to: ");
    Serial.println(PULSE_COUNT);

    PulseReceived = true;
    digitalWrite(LED_BUILTIN, LED_ON);
    }
  }
  if ( digitalRead(PULSE_PIN) == HIGH ) {
    previous_state = false;
  }
}```

Note that the new VALUE is still a float that has limited precision.

If you can live with 6.5 significant digits it will be OK. 

Otherwise you will have work in the integer realm on an Arduino Uno

Thanks for the answer. I tried to modify the program:

#define PULSE_PIN 19
#define LED_BUILTIN       2
#define LED_ON            HIGH
#define LED_OFF           LOW

unsigned long PULSE_COUNT=0; // or unsigned long if you expect more than 65535 pulses

// before setup()
float START_VALUE = 34551.101;     // start value meter

// VALUE is now an expression, not a variable
#define VALUE ( START_VALUE + FACTOR * PULSE_COUNT )

float FACTOR = 0.001;


bool PulseReceived = false;
bool new_state = false;
bool previous_state = false;

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("Program started");
  Serial.print("START_VALUE:   ");
  Serial.println(VALUE, 3);
  Serial.print("FACTOR:        ");
  Serial.println(FACTOR, 3);
  Serial.println("");
  delay(1000);
}

void loop() {
  if ( digitalRead(PULSE_PIN) == HIGH ) {
    digitalWrite(LED_BUILTIN, LED_ON);

    new_state = true;
    if (new_state != previous_state) {

    previous_state = new_state;

    PULSE_COUNT = (PULSE_COUNT + 1); // this now goes before using the new VALUE

    Serial.print("Pulse detected, increasing value with ");
    Serial.print(FACTOR, 3);
    Serial.print(" to: ");
    Serial.println(VALUE, 3);

    Serial.print("Increasing pulse count to: ");
    Serial.println(PULSE_COUNT);

    PulseReceived = true;
    digitalWrite(LED_BUILTIN, LED_ON);
    delay(50);
    }
  }
  if ( digitalRead(PULSE_PIN) == LOW ) {
    previous_state = false;
    delay(50);
  }
}

As you can see it doesn't work:

10:56:44.240 -> START_VALUE: 34551.102
10:56:44.240 -> FACTOR: 0.001
10:56:44.240 ->
10:56:51.996 -> Pulse detected, increasing value with 0.001 to: 34551.102
10:56:51.996 -> Increasing pulse count to: 1
10:56:52.982 -> Pulse detected, increasing value with 0.001 to: 34551.105
10:56:52.982 -> Increasing pulse count to: 2
10:56:53.498 -> Pulse detected, increasing value with 0.001 to: 34551.105
10:56:53.498 -> Increasing pulse count to: 3
10:56:53.920 -> Pulse detected, increasing value with 0.001 to: 34551.105
10:56:53.920 -> Increasing pulse count to: 4
10:56:54.060 -> Pulse detected, increasing value with 0.001 to: 34551.105
10:56:54.060 -> Increasing pulse count to: 5
10:56:54.340 -> Pulse detected, increasing value with 0.001 to: 34551.109
10:56:54.340 -> Increasing pulse count to: 6
10:56:54.481 -> Pulse detected, increasing value with 0.001 to: 34551.109
10:56:54.481 -> Increasing pulse count to: 7
10:56:54.621 -> Pulse detected, increasing value with 0.001 to: 34551.109
10:56:54.621 -> Increasing pulse count to: 8
10:56:54.809 -> Pulse detected, increasing value with 0.001 to: 34551.109
10:56:54.809 -> Increasing pulse count to: 9
10:56:54.903 -> Pulse detected, increasing value with 0.001 to: 34551.113
10:56:54.903 -> Increasing pulse count to: 10
10:56:54.996 -> Pulse detected, increasing value with 0.001 to: 34551.113
10:56:54.996 -> Increasing pulse count to: 11
10:56:55.559 -> Pulse detected, increasing value with 0.001 to: 34551.113
10:56:55.559 -> Increasing pulse count to: 12
10:56:56.262 -> Pulse detected, increasing value with 0.001 to: 34551.113
10:56:56.262 -> Increasing pulse count to: 13

I think this is what you mean with float has limited precision?

See reply #2