Pages: 1 [2]   Go Down
Author Topic: Arduino Uno stops working for no reason?  (Read 1452 times)
0 Members and 1 Guest are viewing this topic.
Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Wow, I can't believe I didn't add the ".0" in the float.  The error no longer shows up but it still prints out "0.00".

Hmm, that looks like a bug:

Code:
float floatVar = 4294967290.0;

void setup()
{
  Serial.begin(115200);
  Serial.println(floatVar);
}

void loop(){}

Output:

Code:
0.00

Even given that the function Print::printFloat looks like it doesn't handle the integral part being > 2^32, my test case is under that.
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 178
Posts: 12289
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


It is possible 4294967290.0 becomes a number larger than that value when the compiler converts it to a float.  Let's see...

  f2:   40 e0          ldi   r20, 0x00   ; 0
  f4:   50 e0          ldi   r21, 0x00   ; 0
  f6:   60 e8          ldi   r22, 0x80   ; 128
  f8:   7f e4          ldi   r23, 0x4F   ; 79


0x4F800000 = 4.2949673e9

We started with 4.294967290e9 and ended up with 4.2949673e9.  So "729" was rounded up to "730".  The final value is too large for an unsigned long.

The highest printable value you can feed to the compiler is 4294967167.0 which is converted to and printed as 4294967040.0.  Basically, the value is right at the fringe of precision for a float.
« Last Edit: June 26, 2012, 11:23:43 pm by Coding Badly » Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Ah yes. Well the significand for a float is 24 bits, giving a maximum value of 16777216 that could be stored without scaling. 4294967290 is well over that. So far so good.

This test program here:

Code:
volatile float floatVar = 4294967290.0;

void setup()
{
  Serial.begin(115200);
 
//  floatVar /= 100.0;
 
  Serial.print ("floatVar = ");
  Serial.println(floatVar);
 
  unsigned long foo = floatVar;
  Serial.print ("foo = ");
  Serial.println(foo);
}

void loop() {}

Prints:

Code:
floatVar = 0.00
foo = 0

If you uncomment the divide by 100, you get:

Code:
floatVar = 42949672.00
foo = 42949672

Slightly out, but that's loss of precision for you. We are accurate up to 7 digits.

The fact that the larger number can be divided by 100 to give the right result suggest that the float is actually stored correctly. So the culprit is this line:

Code:
  unsigned long foo = floatVar;

I am starting to have my doubts about fixunssfsi:

Code:
// Extract the integer part of the number and print it
  unsigned long int_part = (unsigned long)number;
     90c: 0e 94 5b 06 call 0xcb6 ; 0xcb6 <__fixunssfsi>
     910: 7b 01        movw r14, r22
     912: 8c 01        movw r16, r24

I mean, what use is it to have the ability to print floats in the Print class, if it fails spectacularly with numbers larger than a long? May as well not have it.
Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

We started with 4.294967290e9 and ended up with 4.2949673e9.  So "729" was rounded up to "730".  The final value is too large for an unsigned long.

I get what you are saying now. We ended up with 4294967300 which is too large. But surely showing it as zero is going too far? Printing "ovf" or something would be better.
Logged

Global Moderator
Offline Offline
Brattain Member
*****
Karma: 452
Posts: 18694
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

http://code.google.com/p/arduino/issues/detail?id=967
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 178
Posts: 12289
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

But surely showing it as zero is going too far? Printing "ovf" or something would be better.

I agree.  Printing zero for the overflow case is not graceful.


This sets MaximumPrintableValue to the correct value for an Arduino...

Code:
#include <limits.h>
#include <float.h>

#if FLT_RADIX == 2
  #if FLT_MANT_DIG < 32
    #if ULONG_MAX == 0xFFFFFFFF
      const float MaximumPrintableValue = ((1ul << FLT_MANT_DIG) - 1) << (32 - FLT_MANT_DIG);
    #endif
  #endif
#endif

The ULONG_MAX condition is a bit troublesome.  If we had a constant that provided the number of bits in an unsigned long (32) the snippet could be made highly portable.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 33
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


It is possible 4294967290.0 becomes a number larger than that value when the compiler converts it to a float.  Let's see...

  f2:   40 e0          ldi   r20, 0x00   ; 0
  f4:   50 e0          ldi   r21, 0x00   ; 0
  f6:   60 e8          ldi   r22, 0x80   ; 128
  f8:   7f e4          ldi   r23, 0x4F   ; 79


0x4F800000 = 4.2949673e9

We started with 4.294967290e9 and ended up with 4.2949673e9.  So "729" was rounded up to "730".  The final value is too large for an unsigned long.

The highest printable value you can feed to the compiler is 4294967167.0 which is converted to and printed as 4294967040.0.  Basically, the value is right at the fringe of precision for a float.
That just blew my mind......  Is that assembly?
Logged

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 178
Posts: 12289
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


Yes, a partial assembly listing.  It is a snippet of the output from the assembler or disassembler. 

Something like this would have been fed to the assembler...

          ldi   r20, 0x00   ; 0
          ldi   r21, 0x00   ; 0
          ldi   r22, 0x80   ; 128
          ldi   r23, 0x4F   ; 79


Each line is either a machine instruction, a command for the assembler, or a comment.  In the snippet above, all four lines are machine instructions.  "ldi" is load an immediate value (a constant) into a register.  r20, r21, r22, and r23 are registers.  Registers are a lot like eight-bit variables in C.  The hexadecimal values on the right of the comma are the values to load into the registers.  The equivalent C might be something like this...

  uint8_t var20;
  uint8_t var21;
  uint8_t var22;
  uint8_t var23;

  var20 = 0x00;
  var21 = 0x00;
  var22 = 0x80;
  var23 = 0x4F;


In the assembly listing, the first column, the hexadecimal value before the colon, is the program address.  The following hexadecimal values are the opcodes.  Taking the first one as an example...

f2:   40 e0          ldi   r20, 0x00   ; 0


0x40 is stored at address 0x0F2.  0xE0 is stored as address 0x0F3.  To the processor, the 0x40 0xE0 means "load the r20 register with zero".

In the Arduino world, a float is four bytes.  Essentially, the four ldi instructions are loading each of the four bytes of the floating-point number 4.2949673e9 into the r20, r21, r22, and r23 registers.
Logged

Pages: 1 [2]   Go Up
Jump to: