[comp.lang.perl] Numeric fields in formats

worley@compass.com (Dale Worley) (10/04/90)

I was writing a script and wanted to have fixed-field numeric output
in a report, but you can only do that by putting sprintf() calls into
the format, which is inelegant.  So I wrote a small modification to
Perl to allow fields with numeric format specifications like:

	@####
	@##.##
	@###.
	@.###

The @ and # are digit placeholders, the . is the decimal point
placeholder.  The output is done with C's sprintf using a format like
%6.2f or %#6.2f, so the number should be correctly rounded to the
number of places you specify.

The field value you give is interpreted as a number in the usual Perl
way, except that if the value is undefined and the field is started
with ^ rather than @, then the field prints as blanks.

The code is given below as diffs from PL 28.  I've only tested it on a
few cases, but it seems to be working.  Any comments are welcome!

Dale Worley		Compass, Inc.			worley@compass.com
--
My favorite was an example in Dijkstra's classic "A Discipline of
Programming" where he claimed that he hadn't submitted his program
to operational testing since it had been created using a discipline
that guaranteed it would be correct.  Naturally, there were a couple
of bugs in it! -- Doug Gwyn

*** base-toke.c	Tue Aug 14 10:31:09 1990
--- toke.c	Wed Oct  3 10:40:54 1990
***************
*** 2254,2260 ****
--- 2254,2288 ----
  		while (*s == '|')
  		    s++;
  		break;
+ 	    case '#':
+ 	    case '.':
+ 		/* Catch the special case @... and handle it as a string
+ 		   field. */
+ 		if (*s == '.' && s[1] == '.') {
+ 		    goto default_format;
+ 		}
+ 		fcmd->f_type = F_DECIMAL;
+ 		{
+ 		    char *p;
+ 
+ 		    /* Read a format in the form @####.####, where either group
+ 		       of ### may be empty, or the final .### may be missing. */
+ 		    while (*s == '#')
+ 			s++;
+ 		    if (*s == '.') {
+ 			s++;
+ 			p = s;
+ 			while (*s == '#')
+ 			    s++;
+ 			fcmd->f_decimals = s-p;
+ 			fcmd->f_flags |= FC_DP;
+ 		    } else {
+ 			fcmd->f_decimals = 0;
+ 		    }
+ 		}
+ 		break;
  	    default:
+ 	    default_format:
  		fcmd->f_type = F_LEFT;
  		break;
  	    }
*** base-form.h	Thu Oct 19 17:02:57 1989
--- form.h	Wed Oct  3 10:38:44 1990
***************
*** 16,21 ****
--- 16,22 ----
  #define F_RIGHT 2
  #define F_CENTER 3
  #define F_LINES 4
+ #define F_DECIMAL 5
  
  struct formcmd {
      struct formcmd *f_next;
***************
*** 25,30 ****
--- 26,32 ----
      char *f_pre;
      short f_presize;
      short f_size;
+     short f_decimals;
      char f_type;
      char f_flags;
  };
***************
*** 33,38 ****
--- 35,41 ----
  #define FC_NOBLANK 2
  #define FC_MORE 4
  #define FC_REPEAT 8
+ #define FC_DP 16
  
  #define Nullfcmd Null(FCMD*)
  
*** base-form.c	Mon Aug 13 18:53:36 1990
--- form.c	Wed Oct  3 11:18:00 1990
***************
*** 281,286 ****
--- 281,311 ----
  	    d += size;
  	    linebeg = fcmd->f_next;
  	    break;
+ 	case F_DECIMAL: {
+ 	    double value;
+ 
+ 	    (void)eval(fcmd->f_expr,G_SCALAR,sp);
+ 	    str = stack->ary_array[sp+1];
+ 	    /* If the field is marked with ^ and the value is undefined,
+ 	       blank it out. */
+ 	    if ((fcmd->f_flags & FC_CHOP) && !str->str_pok && !str->str_nok) {
+ 		while (size) {
+ 		    size--;
+ 		    *d++ = ' ';
+ 		}
+ 		break;
+ 	    }
+ 	    value = str_gnum(str);
+ 	    size = fcmd->f_size;
+ 	    CHKLEN(size);
+ 	    if (fcmd->f_flags & FC_DP) {
+ 		sprintf(d, "%#*.*f", size, fcmd->f_decimals, value);
+ 	    } else {
+ 		sprintf(d, "%*.0f", size, value);
+ 	    }
+ 	    d += size;
+ 	    break;
+ 	}
  	}
      }
      CHKLEN(1);