Multiplying doubles return incorrect answer

Really rusty programmer here, I'm modifying an existing project. Jumped in way over my head, but nearly done after a few weeks. It's a updated version of the "Tiny Reflow Controller". Moved to a ATmega644P processor as the 328P was out of memory.

This bit of code here is for after the parameter to edit has been selected and the cursor has been moved to the value of the desired digit to edit and selected this is to increment or decrement the value of the parameter. These are doubles(parameters[programPointer]) as the PID lib I'm using expects doubles, sorry for the horriable name, it was short for programmingPointer.


     if (edit && menuPointer != 10) {  //Is pointer inside the parameter value?
       double enc = encoder;
       double Z = .001;
       for (int x = 0; x < menuPointer; x++) {  //compare to the menu Pointer and  
         Z = Z * 10.0;                           //run times 10 increments 
       }
       Serial.print(Z, 4);
       Serial.print(" ");
       Serial.println((enc * Z), 4);
       parameters[programPointer] += (enc * Z);  //(((pow(10,menuPointer))*.001)));
       if (parameters[programPointer] < 0 ){   //catch negative parameter value
         parameters[programPointer] = 0;       //if so zero it out
       }
     }

MenuPointer is the powers of 10 selected and to generate a number from .001 to 1000000 to add or subtract from the original parameter for the number of encoder counts. The serial outputs are for debugging. I have tried everything I know or can google. I even moved the variable of encoder into a double enc to see if that was the issue. But I get returns from enc * Z of odd numbers 1000000.0625 in the millions place, 100000.0078 in the 100 thousands place, 10000.0009 in the 10 thousands place, and 1000.0001 in the thousands place. I have played with order of operations.

The only thought I have left is that .001 isn't being stored properly, it was originally in he += line and Z was initialized as 1. I've seen posts( I really have tried exhaustive searches) about using UL to force the compiler to use a unsigned long, but I cannot find an equivalent for double. Again many of the parameters are passed to a PID lib and it only seems to accept doubles.

I found the
(((pow(10,menuPointer))*.001)))
code online and it returns incorrect results too. Thanks!

You have learned that "double" on your machine is a lie. You only get single precision floating point on your machine, which means you get 6 or maybe 7 good digits. (With a genuine "double" you would get 15 or 16 good digits.)

Darn right it isn't being stored properly.
Here, play with this to see what's going on: IEEE-754 Floating Point Converter

1 Like

can you explain why you can't work with integrals?

what does it mean to subtract .001 from the number of encoder counts? (are 'counts' the number of encoder ticks ?)

if you need to really deal with 0.001, work with integral number expressed in milli-something and only at the last step if you need a floating point value you divide by 1000.0. This way you won't loose precision along the way

What @J-M-L is suggesting is like what is said about, for example, dealing with money. When writing code for a vending machine, a price such as $1.85 should be stored as 185 cents, not as 1.85 dollars. Otherwise you end up with the same kind of trouble you are having.

I actually thought I understood this, I knew that the doubles didn't exist, but the lib complains if they are called anything else. I never tried adding 1000000 to .025 as I knew that would cause an error. From the Arduino page

3.4028235E+38 and as low as -3.4028235E+38

I thought as long as I kept all values inside the non exponent side(I forgot the term) I wouldn't get into trouble. This happens when adding 1000000 to zero.

Assuming you mean integers. Sorry for the poor wording. If the cursor is on the .001 position then the number of encoder clicks is muliplied by .001 and added to the existing value.

You are correct about the scaling later point, never thought of that. I knew very well assembly back on a Atmel 1200 something, but only done a few random things since then.

Agreed, thanks.

What happens if you change that to this?

       double Z = 0;
       switch (menuPointer) {
         case 0: Z = 1.0e-3; break;
         case 1: Z = 1.0e-2; break;
         case 2: Z = 1.0e-1; break;
         case 3: Z = 1.0e0;  break;
         case 4: Z = 1.0e1;  break;
         case 5: Z = 1.0e2;  break;
         case 6: Z = 1.0e3;  break;
         case 7: Z = 1.0e4;  break;
         case 8: Z = 1.0e5;  break;
         case 9: Z = 1.0e6;  break;
         default: Z = 0;
       }

That is what happens if you get mad at it.

understood.

makes sense to indeed keep an "integer" representation (I used integral number to not infer that it would be necessarily be the int type - likely something like long long or unsigned long long)

new term to me :slight_smile:

in C++ you talk about integral types

That actually works, and saves me a whole lot of changes. Thanks.
Can you explain to me why that works and the other doesn't.

Z = 1.0e-3

Should equal Z =.001 and multilpying by 10 should just make it 1.0e-2
I need to use switch more. I've got several projects in the works that requiring micros, so I'm excited to be getting more comfortable coding. I'm 5 weeks into this and learned a lot. So far this is the only part that completely stumped me. I'll post this to github when it's done and you can all get a good laugh out of it. Only about half of it is mine, hopefully the original code was good as that's what I've been learning from.

If I want meters to 3 places, I work in smaller units and lose the fractions.

If I work to micro-meters, I can lose 3 places to divisions and still have correct millimeters.

I probably did this wrong, it's modifying another's projects code. Sometimes the parameter is time, temp, PID values, baudrate, or other. I started trying to do a quick mod to 'just make it work' but somehow got into it and wanted something better.

Do all of these parameters have ten useful digits, ranging from millions down to thousandths?

Temperature? Millions?! What do you have that gets that hot?!

Maybe specify a high and low range for each parameter, so you don't have to waste time setting the millions on a setting that only really should go up to a couple of thousand, nor setting the decimals on something which is too big to require such fine-tuning. For example, I don't think you'd need the decimals for baud rate, so you might as well remove them from that menu setting.

In hindsight I probably went about it all wrong. The reason I went with this method was that I could load parameters[programPointer] into or out of the eeprom with a simple for loop. And the menu was just cycled through that for setting them all the same way. I did a const char parameterNames[][26] that has the name for them all. So a menu was completely looped, just cycle through displaying the name and value. My values range from .025 for the I in the PID and 115200 for the baud rate. I had deleted the millions and even 100K steps, but was still getting garbage in the low digits, so that is why I'm here.

Suggestions.
When dealing with EEPROM, it's really useful to learn to work with structures. That will allow you to read/write a grouping of disparate type variables without resorting to reading each individually. If you use put/get, you needn't worry about same-value variables being written repetitively, put will only write something different.

Note that, when putting/getting floating point values, anything that has been subject to a recalculation is possibly going to be different(insignificant changes still are different), so base your assumptions about EEPROM wear accordingly, or guard any write of a floating point number with a change-threshold.

Thank you. I'm using put and only writing to them from a menu option, so a user can make changes and then choose to save them or not. I get them from the eeprom on startup.

Every time I search for something using the term Arduino I have to limit the results to the last year or so as I have been down the path of reading something completely outdated more often than not. To increase the suffering levels many articles are not dated so you can't guess. Would you happen to have a link to some relevant education on the subject?

I gave you this to play with: IEEE-754 Floating Point Converter
Your 0.001 is not really 0.001 but rather 0.001000000047497451305389404296875 as the demo at that link will show you.
Try multiplying by 10 and feeding it back into the demo to see what it gets you. And again, and again. You will end up with slightly more than 1.

2 Likes

Thanks, embarrassing though, I thought I understood that. I'm a little horrified by this, I know it's enough accuracy to get to the moon and back.