Count digits of a number

Hello,

I found some examples to count the digits in a number.

For example:

uint8_t countDigits(int num)
{
  uint8_t count = 0;
  while(num)
  {
    num = num / 10;
    count++;
  }
  return count;
}

But if I'd like to count the digits in just an uint8, it would be much faster this way, right?

uint8_t countDigits(uint8_t num)
{
  uint8_t count;
  if (num >= 100)  count = 3;
  else if (num >= 10)  count = 2;
  else count = 1;
  return count;
}

Yes

If you are interested in a maximum of 3 digits then OK.
On a 32 bit platform, an int can contain 10 decimal digits and your method could get tedious.
Maybe also look at the log() function.

That's probably the slowest possible way to do it. And least accurate, since it operates on a floating point number with a 24bit mantissa.

Multiplies are significantly faster than divides, so maybe:

uint8_t countDigits(uint32_t num)
{
  uint8_t count = 1;  // everything has at least one digit, right?
  uint32_t comparison = 10;
  while (comparison <= num) {
    comparison *= 10;
    count++;
  }
  return count;
}

(the compiler might even optimize the multiply by 10. Then again, it MIGHT have optimized the divide by 10 (though that's still slower than an optimized multiply))

Exactly. Those are the compromises which sometimes have to be made between speed of execution, speed of writing the code and , maybe, precision of the result. Anyway, it is quite compact:

uint8_t countDigits(int num)
{
   return(  1 + log10( num )  ) ;
}

Nice.

As you indicated with the input parameter type, this only works with positive numbers. I'm not even sure, if the minus sign should be considered as a digit or not?! :slight_smile: Perhaps it's the question of the counting purpose itself.

if you are looking for speed, see this trials we did some years ago:

// Stellen einer Zahl ermitteln
// https://forum.arduino.cc/index.php?topic=634400.0

/*
  Bei Ausgabe = 2 microbahn (Zeitberechnung ohne Ausgaben) - offizielles Testset per 4.9.2019
       |   Compiler - Standard   | random | optimize "00"
  Var   Sketch   Global   micros | millis | Sketch    Global   micros
  1     2214     262      1296       640    2934      262      1412      OK   // Division mit extra ifs
  2     2254     262        40       199    3004      262       108      OK   // geschachteltes If
  3     2208     262      1016       561    2914      262      1124      OK   // Division (ein If weniger als v1)
  4     2244     262        40       198    2986      262       108      OK   // geschachteltes If, invers
  5     2228     262       940       438    2926      262      1000      OK   // ltoa
  6     3308     262      1204         -    4016      262      1260      NOK  // log 10
  7     2240     262       112       235    2942      262       196      OK   // Multiplikation invers mit ifs
  8     2242     262        44       203    2978      262       108      OK   // switch Variante
  9     2224     262        44       202    2976      262       120      OK   // switch Variante, invers
  10    2196     262      1460       583    2894      262      1576      OK   // Division, invers (aber langsamer als v3)
  11    2204     414       252       254    2922      400       400      OK   // Suche in Array/Potenzen
  12    2572     262       904       496    3268      262       772      OK   // Multiplikation 64bit

  13    2242     262        40       200
  14    2246     262        40       200
*/

#define VARIANT 14
//#pragma GCC optimize "O0"

#define AUSGABE 9                       // 1 noiasca   2 microbahn   9 random_checker

#if (VARIANT == 1)
uint8_t neededDigits(int32_t value)     // V1 Division mit extra ifs
{
  if (value == -2147483648) return 11;  // notwendig, da sonst bei GCC optimize "O0" für diesen Wert ein falscher errechnet würde
  if (value == 0) return 1;
  uint8_t digits = 0;
  if (value < 0)
  {
    digits++;
    value *= -1;
  }
  while (value > 0)
  {
    value /= 10;
    digits++;
  }
  return digits;
}
#endif

#if (VARIANT == 2)
uint8_t neededDigits(int32_t value)    // V2 geschachteltes If, Ausgangsbasis: https://www.mikrocontroller.net/topic/79744 inkl. signed
{
  if (value == -2147483648) return 11; // hack für -2147483648
  uint8_t sign = 0;
  if (value < 0) {
    value = -value;
    sign = 1;
  }
  if (value < 100000) {
    if (value < 1000) {
      if (value < 100) {
        if (value < 10)
          return 1 + sign;
        return 2 + sign;
      }
      return 3 + sign;
    }
    if (value < 10000)
      return 4 + sign;
    else
      return 5 + sign;
  }
  if (value < 100000000) {
    if (value < 10000000) {
      if (value < 1000000)
        return 6 + sign;
      return 7 + sign;
    }
    return 8 + sign;
  }
  if (value < 1000000000)
    return 9 + sign;
  else
    return 10 + sign;
}
#endif

#if (VARIANT == 3)                      
uint8_t neededDigits(int32_t value)     // V3 Division - optimiert wegen 0 
{
  if (value == -2147483648) return 11;  // hack
  uint8_t digits = 1;
  if (value < 0)
  {
    digits++;
    value *= -1;
  }
  while (value >= 10)
  {
    value /= 10;
    digits++;
  }
  return digits;
}
#endif


#if (VARIANT == 4)                     // Geschachteltes If, umgebaut auf negative Logik
uint8_t neededDigits(int32_t value)    // V4 Derivat von : https://www.mikrocontroller.net/topic/79744
{
  uint8_t sign = 1;
  if (value >= 0) {
    value = -value;
    sign = 0;
  }
  if (value >   -100000) {
    if (value >   -1000) {
      if (value >  -100) {
        if (value > -10)
          return 1 + sign;
        return 2 + sign;
      }
      return 3 + sign;
    }
    if (value > -10000)
      return 4 + sign;
    else
      return 5 + sign;
  }
  if (value > -100000000) {
    if (value > -10000000) {
      if (value > -1000000)
        return 6 + sign;
      return 7 + sign;
    }
    return 8 + sign;
  }
  if (value > -1000000000)
    return 9 + sign;
  else
    return 10 + sign;
}
#endif

#if (VARIANT == 5)
uint8_t neededDigits(int32_t value)                       // V5 ltoa
{
  char tempStr[12];
  return strlen( ltoa( value, tempStr, 10 ) );
}
#endif


#if (VARIANT == 6)                                         
uint8_t neededDigits(int32_t value)                     // V6 falsch -2147483648 needs 2 und bei Stellenüberläufen 999999 und größer  
{
  if ( value < 0 )
    return static_cast<unsigned>(log10(-value)) + 2;
  else
    return static_cast<unsigned>(log10(value)) + 1;
}
#endif

#if (VARIANT == 7)
uint8_t neededDigits(int32_t value)                     // V7 
{
  if ( value > 999999999) return 10;                    // keine Überschreitung der Multiplikation bei 32bit
  if ( value < -999999999) return 11;                   // keine Überschreitung der Multiplikation bei 32bit
  //if ( value == 0) return 1;
  uint8_t digits = 1;
  int32_t temp = -1;
  int32_t absolut = value;
  if (value > 0)
  {
    digits = 0;
    absolut = -value;
  }
  while (absolut <= temp)
  {
    temp *= 10;
    digits++;
  }
  return digits;
}
#endif

#if (VARIANT == 8)                                  
uint8_t neededDigits(int32_t value) {               // V8:
  if (value == -2147483648) return 11;              // hack
  byte digit = 0;
  if (value < 0) {
    value = -value;
    digit++;
  }
  switch (value) {
    case 1000000000L ... 2147483647L:  digit++;
    case 100000000 ... 999999999:  digit++;
    case 10000000 ... 99999999:  digit++;
    case 1000000 ... 9999999:  digit++;
    case 100000 ... 999999:  digit++;
    case 10000 ... 99999:  digit++;
    case 1000 ... 9999:  digit++;
    case 100 ... 999:  digit++;
    case 10 ... 99:  digit++;
    case 0 ... 9:   digit++;
    default:  break;
  }
  return digit;
}
#endif

#if (VARIANT == 9)                                  // OK
uint8_t neededDigits(int32_t var) {
  byte digit = 1;
  if (var >= 0) {
    var = -var;
    digit--;
  }
  switch (var) {
    case -2147483648 ... -1000000000:  digit++;
    case -999999999 ... -100000000:  digit++;
    case -99999999 ... -10000000:  digit++;
    case -9999999 ... -1000000:  digit++;
    case -999999 ... -100000:  digit++;
    case -99999 ... -10000:  digit++;
    case -9999 ... -1000:  digit++;
    case -999 ... -100:  digit++;
    case -99 ... -10:  digit++;
    case -9 ... 0:   digit++;
    default:  break;
  }
  return digit;
}
#endif


#if (VARIANT == 10)
uint8_t neededDigits(int32_t value)                  // OK 10 Division mit inverse Logik (korrigiert 3)
{
  uint8_t digits = 2;
  if (value >= 0)
  {
    digits--;
    value *= -1;
  }
  while (value <= -10)
  {
    value /= 10;
    digits++;
  }
  return digits;
}
#endif


#if (VARIANT == 11)
uint8_t neededDigits(int32_t value)                      // Array lesen
{
  const int32_t threshold[] = { -10, -100, -1000, -10000, -100000, -1000000, -10000000, -100000000, -1000000000, -1000000000};
  const uint8_t indexes = sizeof(threshold) / sizeof(threshold[0]);
  uint8_t sign = 1;
  if (value >= 0)
  {
    value = value * -1;
    sign = 0;
  }
  for (int8_t i = 0; i < indexes; i++)
  {
    if (value > threshold[i]) return i + 1 + sign;
  }
  return 10 + sign;
}
#endif


#if (VARIANT == 12)                                      
uint8_t neededDigits(int32_t value) {                   // ok 64bit aber langsam   (korrigierte Multiplikation)
  if (value == -2147483648) return 11;
  uint8_t count = 0;
  uint64_t temp = 1;
  uint64_t absolut = (uint64_t)abs(value);
  if ( value == 0 ) return 1;
  while (absolut >= temp )
  {
    temp *= 10;
    count++;
  }
  return value > 0 ? count : count + 1;
}
#endif


#if (VARIANT == 13)                                      // ziemlich gut verschachtelter IF,  aufsteigend
uint8_t neededDigits(int32_t value)                      // Derivat von : https://www.mikrocontroller.net/topic/79744
{
  uint8_t sign = 1;
  if (value >= 0) {
    value = -value;
    sign = 0;
  }
  if (value > -1000)
  {
    if (value > -10)  return 1 + sign;
    if (value > -100)  return 2 + sign;
    return 3 + sign;
  }
  if (value >   -1000000)
  {
    if (value > -10000)    return 4 + sign;
    if (value > -100000)   return 5 + sign;
    return 6 + sign;
  }
  if (value >   -100000000)
  {
    if (value > -10000000)   return 7 + sign;
    if (value > -100000000)  return 8 + sign;
  }
  if (value >   -1000000000) return 9 + sign;
  return 10 + sign;
}
#endif


#if (VARIANT == 14)                                      // Spielwiese
uint8_t neededDigits(int32_t value)    // Derivat von : https://www.mikrocontroller.net/topic/79744
{
  uint8_t sign = 1;
  if (value >= 0) {
    value = -value;
    sign = 0;
  }
  if (value > -10000)
  {
    if (value > -10)   return 1 + sign;
    if (value > -100)  return 2 + sign;
    if (value > -1000) return 3 + sign;
    return 4 + sign;
  }
  if (value >   -10000000)
  {
    if (value > -100000)   return 5 + sign;
    if (value > -1000000)  return 6 + sign;
    return 7 + sign;
  }
  if (value > -100000000)   return 8 + sign;
  if (value > -1000000000)  return 9 + sign;
  return 10 + sign;
}
#endif


#if (AUSGABE == 9)
uint8_t checker(int32_t var) {                             // Reference Function: Annahme - das ist eine korrekte (und schnelle) Variante)
  byte digit = 1;
  if (var >= 0) {
    var = -var;
    digit--;
  }
  switch (var) {
    case -2147483648 ... -1000000000: digit++;
    case -999999999 ... -100000000: digit++;
    case -99999999 ... -10000000:  digit++;
    case -9999999 ... -1000000:  digit++;
    case -999999 ... -100000:  digit++;
    case -99999 ... -10000:  digit++;
    case -9999 ... -1000:  digit++;
    case -999 ... -100:  digit++;
    case -99 ... -10:  digit++;
    case -9 ... 0:   digit++;
    default:  break;
  }
  return digit;
}


byte doCheck(int32_t i)                      //Die eigentliche Vergleichsfunktion
{
  //Serial.println(i);
  byte evaluate = neededDigits(i);
  byte verified = checker(i);
  if (verified != evaluate)
  {
    Serial.print(i);
    Serial.print(" needs ");
    Serial.print(verified);
    Serial.print("    wrong ");
    Serial.println(evaluate);
    return 1;
  }
  else
    return 0;
}
#endif

void setup()
{
  Serial.begin(115200);
  Serial.println("Stellen zaehlen ");
#if (AUSGABE == 1)
  uint32_t start = micros();
  Serial.println(neededDigits(0));
  Serial.println(neededDigits(1));
  Serial.println(neededDigits(10));
  Serial.println(neededDigits(333));
  Serial.println(neededDigits(-4444));
  uint32_t total = micros() - start;
  Serial.print("Durchlaufzeit "); Serial.println(total);
#endif


#if (AUSGABE >= 8) || (AUSGABE == 2)
  //int32_t werte[] = { 0, 1, 10, 333, -444, -2147483648, -100234 };
  //int32_t werte[] = { 0, 1, 10, 333, -444, 12345, -100234 };                          // alte Prüfwerte für Übersichtstabelle
  int32_t werte[] = { 0, 1, 10, 333, -444, 12345, -100234, 2147483647, -2147483648 };   // offizielles Testset per 4.9.2019
  const byte WERTEZAHL = sizeof(werte) / sizeof(werte[0]);
#endif

#if (AUSGABE == 2)
  uint8_t stellen[WERTEZAHL];
  uint32_t start = micros();
  for (uint8_t i = 0; i < WERTEZAHL; i++ ) {
    stellen[i] = neededDigits( werte[i] );
  }
  uint32_t total = micros() - start;
  Serial.print("Durchlaufzeit "); Serial.println(total);

  for (uint8_t i = 0; i < WERTEZAHL; i++  ) {
    Serial.print( werte[i] );
    Serial.print(" needs ");
    Serial.println(stellen[i]);
  }
#endif


#if (AUSGABE == 8)
  Serial.println("Masstest");
  uint16_t testsPerDigit = 10000;
  uint32_t errorcounter = 0;
  uint32_t testcounter = 0;
  int32_t from = 10;
  int32_t to =   101;
  uint32_t startMs = millis();
  for (uint16_t y = 0; y < testsPerDigit; y++)
  {
    int32_t i = random(from, to); //(min incl, max excl);
    errorcounter = neededDigits(i);
    testcounter++;
  }
  uint32_t totalMs = millis() - startMs;
  Serial.print(F("last result:     ")); Serial.println(errorcounter);
  Serial.print(F("Runtime in ms:   ")); Serial.println(totalMs);
#endif

#if (AUSGABE == 9)  // vergleicht die Ergebnisse der ausgewälten Variante gegen den checker
  Serial.println("random checker ...");
  uint8_t testsPerDigit = 100;
  uint32_t errorcounter = 0;
  uint32_t testcounter = 0;
  randomSeed(analogRead(A0));
  // replacement for pow()
  int32_t fuckPow [] = { -2147483648,
                         -1000000000, -100000000, -10000000, -1000000, -100000, -10000, -1000, -100, -10,
                         -1,
                         0,
                         9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999,
                         2147483647
                       };
  const uint8_t values = sizeof(fuckPow) / sizeof(fuckPow[0]);
  uint32_t startMs = millis();
  for (uint8_t z = 1; z < values; z++)                               // Check random values  (n Random numbers for each digit)
  {
    int32_t from = fuckPow[z - 1] + 1;
    int32_t to =   fuckPow[z];
    //Serial.print(from); Serial.print(" to "); Serial.println(to);  // debug only
    for (uint8_t y = 0; y < testsPerDigit; y++)
    {
      int32_t i = random(from, to); //(min incl, max excl);
      errorcounter += doCheck(i);
      testcounter++;
    }
  }
  for (uint8_t z = 1; z < values; z++)                               //
  {
    errorcounter += doCheck(fuckPow[z] - 1);
    errorcounter += doCheck(fuckPow[z]);
    errorcounter += doCheck(fuckPow[z] + 1);
    testcounter += 3;
  }
  for (uint8_t i = 0; i < WERTEZAHL; i++ )             // The testset itself
  {
    errorcounter += doCheck(werte[i]);
    testcounter++;
  }
  uint32_t totalMs = millis() - startMs;
  Serial.print(F("Tests performed: ")); Serial.println(testcounter);
  Serial.print(F("Total erros:     ")); Serial.print(errorcounter); if (!errorcounter) Serial.print(F(" - Test passed!")); Serial.println();
  Serial.print(F("Runtime in ms:   ")); Serial.println(totalMs);
#endif
}


void loop()
{
}

learning:
"switch case" variants are fast.
signed numbers can be tricky, as we have 2147483647 but -2147483648 in a int32_t

if you only need uint8_t you can speed up it even more.

#define VARIANT 14  // will set one of the given examples

#define AUSGABE 9   // translates to "output" ... use 1, 2 or 9

I don't know why I'm a little suprised..internet has everything on it. Thanks for sharing, the switch-cases look neat. :slight_smile: