Splitting an integer into its digits - floating point/rounding/conversion error?

As part of a multiple-7-segment multiplexing test I'm using this function to split an integer into it's digits:

#define segments 3
byte d[segments];

void setDigits(int number, byte digits) {
  for (byte i = 0; i < digits; i++) {
    d[i] = (number % int(pow(10, i+1))) / pow(10, i);
  }

}

Basically "number" counts up from zero, I run the value through setDigits() and use the d array to display the digits on the several 7-segment displays.

The weird thing is it is a bit "off". All numbers from 0 to 98 are split correctly. 99 though gets converted to 9,0,0 (read from d[0] to d[2]). 100-108 are fine again but 109 is 9,1,1 and so on. It gets worse for higher numbers, for every count of 100 the error shifts by one.

I did alter the code with an explicit typecast on the off-chance that something wacky goes on like this:

    d[i] = (byte) (number % int(pow(10, i+1))) / pow(10, i);

but no avail.

I double checked the "formula" in a spreadsheet and it seems fine.

What am I missing here? Thanks a ton.

pow is a floating point function. When you cast a floating point number into an int, it does not round, it truncates, so 31.99999999 will become 31.

I know. Floor'ing the number is exactly what I expect it to do. 31.9999 becoming 31 is what I want. Yet, 99 becomes 9, 109 becomes 119 while numbers inbetween are correct. Running the same code in Java works fine.

[edit to add] My guess is this might be a floating point error but I can't figure out how to making sure it in fact is, or find another way around this.

pow() is a function returning floats. Float operations are not exact. Ie when doing something where the answer "should be" 100.00", a float operation may return 100.000001 or 99.9999999. That is what (propably) is happening in your case. The int( ) opertion which is converting to integer mey do a truncate rather than a round. Your spreadsheet is more forgiving (having been written for use by not-so-computer-science-minded customers).

Choose a differnet algorithm. For example use number %10 to get the last digit. Then reduce number by dividing it by ten, that way the last digit is now what whould be the tens.

Thanks Msquare, of course... round returns a float... doh!

Quickly wipped up this one:

int ipow(int base, int exponent) {
  int res;

  if (exponent == 0) {
    res = 1;
  } else if (exponent < 0) {
    res = 0;
  } else {
    res = base;
    for (int i = 1; i < exponent; i++) {
      res = res*base;
    }
  }
  
  return res;
}

Which is not exactly correct but it does the trick I need it to do and voila, no floating point errors anymore.
bows

If you first do the division, to get the specified digit into place, and then do modulus by 10, it works fine:

void setDigits(int number, byte digits) {
  for (byte i = 0; i < digits; i++) {
    d[i] = int(number / pow(10, i)) % 10;
  }
}

Wow that is indeed very clever, thanks TRex.

At the moment I can't think that far hence I'll just ask: Is this working by chance in the sense that it just happens not to generate floating point errors or is this guaranteed not to happen?

number / pow() will always generate an int since number is int, I can think that far. My guess is that integer division will just truncate the decimals from pow()?

Yip, the int division truncates the number, so 9.9 becomes 9. so what you are doing here is:
Moving the decimal next to the digit that you want by dividing by an incremental power of ten,
dropping the decimal part by doing a conversion to int,
dropping digits to the left of the one you want by getting the modulus of 10

You can do this without using floats.
The hardest part of this example was getting the print outs right.
And if you would rather have a text string, I can throw the sign in as well. :wink:

byte setDigits( int number, byte *d, byte len ) // returns # of digits  
{
  static byte  idx; // static stays
  
  long  fullrange = number;

  if ( fullrange < 0L )
  {
    fullrange *= -1L;
  }  

  for ( idx = 0; idx < len; idx++ )
  {
    d[ idx ] = 0;
  }

//                        Here is the actual conversion to array
  idx = 0;
  while (( fullrange ) && ( idx < len ))
  {
    d[ idx++ ] = fullrange % 10;
    fullrange /= 10;
  }
//                        Finished actual conversion to array


  return idx;
}

void printDigits( byte *d, byte len )   
{
  if ( !len )
  {
    Serial.println( "0" );
    return;
  }
  
  do
  {
    len--;
    if ( d[ len ] )
    {
      Serial.print( d[ len ], DEC );
      Serial.print( " " );
    }
  }
  while ( len );
  
  Serial.println( );
}


void setup( void )
{
  Serial.begin( 115200 );
  Serial.println( "int digits, does not show SIGN\n" );

  const byte places = 5; 
  byte digits[ places ];
  byte converted;

  Serial.println( 0, DEC );
  converted = setDigits( 0, digits, places );
  printDigits( digits, converted );

  Serial.println( );
  Serial.println( 1, DEC );
  converted = setDigits( 1, digits, places );
  printDigits( digits, converted );

  Serial.println( );
  Serial.println( 123, DEC );
  converted = setDigits( 123, digits, places );
  printDigits( digits, converted );

  Serial.println( );
  Serial.println( 12345, DEC );
  converted = setDigits( 12345, digits, places );
  printDigits( digits, converted );

  Serial.println( );
  Serial.println( -32767, DEC );
  converted = setDigits( -32767, digits, places );
  printDigits( digits, converted );

  Serial.println( );
  Serial.println( -32768, DEC );
  converted = setDigits( -32768, digits, places );
  printDigits( digits, converted );
}

void loop( void )
{
}

pow( ) should be abolished. Nearly every piece of code that I have even seen which uses it, was wrong.

You have seen it misused then.

BTW, I put the low order digit in d[ 0 ] where with text the high order digit goes first.
That would be a matter of dividing by 10000 for the 1st digit and cycling the remainder, divide 10000 by 10 and looping through that, not saving leading zeros unless you want those.

I use a long as intermediate in setDigits() there because using an int variable when the number is -32768 gets anomalous results.

What will you do about sign, or will you not be working with negative values?