Monday, October 28, 2013

Human friendly printing numbers in command line

When I am on Unix system running things of from command line (either SQL or shell of any sort) with long number, I find these numbers hard to read.  For example a printout of netstat packets shows
236477161 packets
Which is really 236.4  million packets, a human friendly representation will be more like 
236,477,161 packets




Same with SQL I run a count on number of rows in a table, I get something that is sort of unreadable:

mysql> select count(*) from feedz.quova;

+----------+

| count(*) |
+----------+
| 14339159 | 
+----------+

Solution is to write your wrapper that does 1000's seperator with a comma.  In MySQL now my numbers look a little more friendly


mysql> select functions.humanf(count(*)) from feedz.quova;

+------------------+
| humanf(count(*)) |
+------------------+
| 14,433,915       | 
+------------------+

14.4 million, now that is reasonable.  To enable this I made a flexible C-program that prints large numbers in human friendly way and a MySQL function that will support this human friendly print out of large numbers.

More in the demo below:

#Print me a number in comma separated (or locale defined separator like space) large number
sh-4.1$ ./humanf 485311298
485,311,298
#Print me number of Bytes in human friendly KibiBytes/MebiBytes disk+human friendly format basse^2
sh-4.1$ ./humanf -b Auto 485311298
462.82 MBytes
#Print me number of Bytes in human friendly KibiBytes format specifically
sh-4.1$ ./humanf -b KBytes 485311298
473,936.81 KBytes
#Print me number of Bytes in human friendly GibiBytes format specifically
sh-4.1$ ./humanf -b GBytes 485311298
0.45 GBytes

Code: / C-program  compile with -lm flag for math support/

#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdbool.h>
#include <inttypes.h>
#include <locale.h>
#include <math.h>

char *huprint(uint64_t n, const char *unit);
void _exitt(char *err);

const char *units[] = {"Auto","KBytes","MBytes","GBytes","TBytes","PBytes","EBytes","ZBytes"};

//Default Units 
#define defunits ((const char*)"Bytes")

int main (int argc, char *argv[]) {
  char *unit = NULL;
  int opt;
  uint64_t val;
  if((opt=getopt(argc,argv,"b:")) != -1) {
    if(opt == 'b') 
      unit=optarg;
    else
      _exitt("Incorrect usage .\n");
  }
  if(argc <= optind)
      _exitt("Incorrect usage\n");
  val = strtoull((const char *)argv[optind++], NULL, 10);
  //    printf("You entered a number like %s %"PRIu64"\n",unit,val);
      if (errno == EINVAL)
{
  _exitt("Error unable to parse the number");
}
      else if (errno == ERANGE)
{
  // does not fit in an unsigned long long
  _exitt("Error unable to parse the number too big");
}
      
      printf("%s\n", huprint(val,unit));
      return 0;
}

void _exitt(char *err) {
  printf("%s",err);
  printf("Usage humanf [-b Auto|KBytes|MBytes] number\n");
  exit(255);
}

char *huprint(uint64_t n,const char *unit)
{
  static int comma = '\0';
  static char retbuf[30];
  char dec[5];
  char *p = &retbuf[sizeof(retbuf)-1];
  int i = 0;
  int ak=0; 
  int aj=0;
  double pr;
  int NUNITS;
  uint64_t j;

  if(unit) {
    // Find what unit people want if it is specified as Bytes / etc.
    NUNITS=sizeof(units)/sizeof(*units);
    for(i=0;i<NUNITS;i++) 
      if(strncmp(units[i],unit,sizeof(units[i])) ==0) 
aj=i;
    j=n;
    //    printf("%s %d %d \n",unit, aj , ak);
    if(aj == 0) {
      //User wants to Auto
      aj=NUNITS-1;
      while (n > 1024) {
n=n/1024;
ak++;
// if(ak >= aj) break;
      }
    }
    else {
      // Use the units provided by the user
      while (ak < aj) {
n=n/1024;
ak++;
      }
    }
    *p='\0';
    if(ak > 0) {
      i=(int)strlen(units[ak]);
      while (i >= 0)  {
*--p=units[ak][i];
i--;
      }
    } else {
      i=(int)strlen(defunits);
      while (i >= 0)  {
*--p=defunits[i];
i--;
      }
    }
    *--p=' ';
    pr=j/pow(1024,ak)-n;
    if(pr > 0) {
      snprintf(dec,sizeof(dec),"%.3f",pr);
      *--p=dec[3];
      *--p=dec[2];
      *--p=dec[1];
      //      printf("Decimal value %s",dec);
    }
    i=0;
  }
  else 
      *p = '\0';
  if(comma == '\0') {
    struct lconv *lcp = localeconv();
    if(lcp != NULL) {
      if(lcp->thousands_sep != NULL &&
*lcp->thousands_sep != '\0')
comma = *lcp->thousands_sep;
      else comma = ',';
    }
  }

  do {
    if(i%3 == 0 && i != 0)
      *--p = comma;
    *--p = '0' + n % 10;
    n /= 10;
    i++;
  } while(n != 0);

  return p;
}

Code: MySQL function

delimiter $$
CREATE FUNCTION HUMANF(number INT) RETURNS TEXT BEGIN  
DECLARE x TEXT; 
     DECLARE l int;
     DECLARE k int;
     DECLARE i int;
     DECLARE f text;
     SET x = CONCAT('',number);                             
     SET l=length(x);
     SET k=l%3;
     if k > 0 then
      SET f=CONCAT(SUBSTRING(x,1,k),",");
      SET i=2;
     else  
        SET f="";
SET i=k+1;
     END IF;
     mk_loop: LOOP
          if i >= l then
          LEAVE mk_loop;
       END IF;
       SET f=CONCAT(f,substring(x,i,3),",");
       SET i=i+3;
     END LOOP;
     RETURN substring(f,1,length(f)-1);
     END
$$




Enjoy - come to the human friendly world even in command line...

No comments:

Post a Comment