Formatting numbers - solved

matherp

Senior Member
If you have used LCDs, whether serial or parallel, then you will be used to the issue of getting numbers correctly formatted. You use bintoascii and then you have to strip of the leading zeroes before output. This all looks OK and then you get a number a decade smaller and the display leaves the last character of the larger number there so you then have to put in some code to overwrite it with a space. Then you decide you want a decimal point (e.g. for a DS18B20) so that needs coding. Finally, you need negative numbers so that needs special code as well.

I've finally got bored with doing this repeatedly so have written a general purpose formatting routine.

The single routine takes three inputs: the data to be output, the width and the format. The width parameter defines how big a field is to be created, this will be completely filled with the number or spaces (or * characters if it is not big enough for the number). The format parameter allows you to define by setting the appropriate bits:
byte or word data
signed or unsigned
left or right justified
scale by 10 (i.e. insert decimal point before last digit)
scale by 100(i.e. insert decimal point before second last digit)
It also allows for a termination string to be optionally specified e.g cr,lf

The number output uses a separately callable routine to output a text string left or right justified with the field width specified by width

The program uses a call to a single character output routine "outchar" which can be modified to drive the specific device required. In the example I use a call to sertxd. I've used it on serial LCDs and SPI Nokia 5110 displays

The example code shows most of the features and tests the special case handling.

I've included at the bottom compatible routines for multiplying and dividing signed words. These make it very easy to deal with, for example, temperature measurements that can go negative. To use a DS18B20 for the full range of temperatures +ve or -ve to 1 decimal place is done with just four calls as follows:
readtemp12
signed multiply by 10
signed divide by 16
signed word output

Hope this is useful

Code:
#Picaxe   20X2
#Terminal 9600
' Variable Definitions
symbol sign =bit0
symbol signed = bit1
symbol rightjustify = bit2
symbol scale100= bit3
symbol scale10 = bit4
symbol byteout = bit5
symbol overflow=bit6
symbol termination=bit7
symbol char = b1
symbol width = b2
symbol format=b3 
symbol i=b4
symbol OutDat = w3 '-32768 to 32767
symbol ODl = b6 '-128 to 127
symbol ODh = b7
symbol word2 = w4 
symbol W2l = b8 
symbol W2h = b9
symbol wordsave=w5
'
symbol n_array=0x30
symbol s_array=0x40
symbol t_array=0x50
symbol f_scale100=0x02
symbol f_scale10=0x01
symbol f_err= f_scale100 | f_scale10
symbol f_signed=0x04
symbol f_rightjustify=0x08
symbol f_byteout=0x10
symbol f_default=0x00
symbol f_termination=0x80
symbol positive=0
symbol negative=1
symbol yes=1
symbol no=0
'
Start:
  SetFreq M8                 
  OutDat=-110
  poke s_array,"F","o","r","m","a","t"," ","t","e","s","t",0
  poke t_array,cr,lf,0
  pause 2000
'
Main:
  bptr=s_array
  ' zero terminated string right justified in a field of length 14
  width=14:format=f_rightjustify | f_termination: gosub outstring
  ' unsigned integer left justified in 11 char field
  width=11: format=f_default: gosub FormNum
  ' osigned integer, right justified field of only 3 characters, note data will be replaced with * characters if too big
  width=3: format=f_signed | f_rightjustify | f_termination: gosub FormNum
  ' unsigned integer left justified in 7 character field with 1 decimal place
  width=7: format=f_scale10 : gosub FormNum
  ' signed integer right justified in a 7 character field with one decimal place
  width=7: format=f_signed | f_scale10 | f_rightjustify | f_termination: gosub FormNum
  ' unsigned integer left justified in a 7 character field with 2 decimal places
  width=7: format=f_scale100   gosub FormNum
  ' signed integer right justified in a 7 character field with two decimal places
  width=7: format=f_scale100 | f_rightjustify | f_signed | f_termination: gosub FormNum
  'output with conflicting format
  width=7: format=f_scale10 | f_scale100  gosub FormNum
  'signed byte right justified in a 7 character field
  width=7: format=f_byteout |F_rightjustify | f_termination | F_signed: gosub FormNum
  OutDat=OutDat+3
  pause 4000
  goto main
end

outchar:
  sertxd(char) 'this can be replaced with a write to a LCD or other output device
return
'
'  creates zero terminated string starting at n_array from OutDat based on the format and width inputs
'
FormNum:
  bptr = n_array
  b0=0 ' clear the status bits
  wordsave=OutDat
  sign=positive
  i= format & f_signed: if i<>0 then:signed=yes:endif
  i= format & f_byteout: if i<>0 then:byteout=yes:endif
  if byteout=yes then 'convert a byte input to a word copying sign bit if required
    OutDat=OutDat & 0xFF
    if signed = yes and OutDat>=$80 then
      OutDat =OutDat | 0xFF00
    endif
  endif
  If OutDat >= $8000 and signed=yes Then
    sign= negative
    OutDat = -OutDat
  endif
  i=format & f_err
  if i=f_err then  'check for conflicting formats
    @bptrinc="F":@bptrinc="_":@bptrinc="E":@bptrinc="r":@bptrinc="r":@bptr=0
  else 'process if formatting commands make sense
    if i=f_scale100 then :scale100=yes:endif
    if i=f_scale10 then :scale10=yes:endif
    bintoascii OutDat,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptr 'do the basic conversion
    W2l=@bptr
    if scale10 = yes then : @bptrinc="." :endif 'insert decimal point if scale10
    if scale100 = yes then 'insert decimal point if scale100
      W2l=@bptrdec
      W2h=@bptr
      @bptrinc="."
      @bptrinc=W2h
      @bptr=W2l
    endif
    @bptrinc=W2l
    @bptr=0 'denotes end of string with a null
    W2l=0
    bptr = n_array
    if @bptrinc="0" then ' at least one leading zero
	W2l=1
	if @bptrinc="0" then ' at least two leading zeros
   	  W2l=2
	  if @bptrinc="0" then ' at least three leading zeros
	    W2l=3
	  endif
	endif
    endif
    ODl=n_array+W2l 'move array left by the number of leading zeroes
    ODh=n_array+6
    W2h=n_array
    for i= ODl to ODh 
      bptr=i
	W2l=@bptr
	bptr=W2h
	@bptr=W2l
	W2h=W2h+1
    next i
    if signed = yes then 'move array right 1 space to allow for sign character if required
      ODl=n_array
      ODh=n_array+6
      for i= ODh to ODl step -1 
        bptr=i
        W2l=@bptrinc
        @bptr=W2l
      next i
      bptr=n_array 'insert the sign bit at the beginning of the string
      if sign=negative then
        @bptr="-"		
      else
        @bptr=" "		
      endif
    endif
    OutDat=wordsave
    bptr = n_array
    if scale10=no and @bptr="0" then 'special case of unsigned positive numbers less than 10
	inc bptr
	W2l=@bptr
	@bptrdec=0
      @bptr=W2l
    endif
    if signed=yes and scale10=no then 'special case of signed positive numbers between -10 and 10
      bptr=n_array+1
      if @bptr="0" then
	  W2l=bptr+1
	  peek W2l,W2h
        @bptrinc=W2h
        @bptr=0
      endif
    endif
    if scale100=yes and OutDat<100 and signed=no then  'special case of unsigned 2-decimal positive numbers less than 100
      bptr=n_array+1
      W2l=@bptrinc
      W2h=@bptr
      bptr=n_array
      @bptrinc="0":@bptrinc=".":@bptrinc=W2l:@bptrinc=W2h:@bptr=0
    endif
    if scale100=yes and signed=yes then 'special case of signed 2-decimal positive numbers between -100 and 100
      if OutDat>65436 or OutDat<100 then
        bptr=n_array+2
        W2l=@bptrinc
        W2h=@bptr
        bptr=n_array
        if sign=negative then
          @bptrinc="-"		
        else
          @bptrinc=" "		
        endif
        @bptrinc="0":@bptrinc=".":@bptrinc=W2l:@bptrinc=W2h:@bptr=0
      endif
    endif
  endif
  bptr = n_array  'reset bptr ready for output
'outputs a zero terminated string pointed to by bptr   
outstring: 
  i= format & f_rightjustify: if i<>0 then:rightjustify=yes:endif
  i= format & f_termination: if i<>0 then:termination=yes:endif
  W2l=0
  do while @bptrinc<>0 'count the valid characters
    inc W2l
  loop 
  if W2l>width then: overflow=yes: W2l=width: endif 'too big so replace with * characters 
  bptr=bptr-W2l-1
  if rightjustify=yes and W2l<>width then 'if right justified pad out beginning of the string with spaces
    char=" "
    W2l=W2l+1
    for W2h=width to W2l step -1
   	gosub outchar
    next W2h
    W2l=W2l-1
  endif
  for W2h=1 to W2l
    if overflow=no then: char=@bptrinc :else :char="*":endif
   	gosub outchar
  next W2h
  if rightjustify=no and W2l<>width then 'if left justified pad out end of the string with spaces
    char=" "
    W2l=W2l+1
    for W2h=width to W2l step -1
   	gosub outchar
    next W2h
  endif
  if termination=yes then
    bptr=t_array
    do 
      char=@bptrinc
      gosub outchar
    loop while @bptr<>0
  endif 
return

'Function multsignedword(OutDat As Word,  word2  As Word) As Word
'multiply OutDat by word2 and return the result in OutDat
'
multsignedword:
	sign = positive
	If OutDat >= $8000 Then
		sign = Not sign
		OutDat = -OutDat
	Endif
	If word2 >= $8000 Then
		sign = Not sign
		word2 = -word2
	Endif
	OutDat = OutDat * word2
	If sign = negative Then
		OutDat = -OutDat
	Endif
return                                      
'
'Function divsignedword(OutDat As Word, word2 As Word) As Word
'divide OutDat by word2 and return the result in OutDat
'
divsignedword:
	sign = positive
	If OutDat >= $8000 Then
		sign = Not sign
		OutDat = -OutDat
	Endif
	If word2 >= $8000 Then
		sign = Not sign
		word2 = -word2
	Endif
	OutDat = OutDat / word2
	If sign = negative Then
		OutDat = -OutDat
	Endif
return
 
Top