Method to print fixed-point values.

This is a tested demo of printFixedDec(long value, long scale).

void setup() 
{
  long val = 12345678L;
  long scale = 1000L; // 3-decimal places scale

  Serial.begin(9600);
  Serial.print( "DEMO: start val = " );  
  Serial.print( val, DEC );
  Serial.print( " mm == " );
  //  Serial.print( val / scale );
  //  Serial.print( "." );
  //  Serial.print( val % scale );
  printFixedDec( val, scale );
  Serial.print( " m == " );
  printFixedDec( val, ( scale = 1000000 ));
  Serial.println( " Km" );
  Serial.println( );
  Serial.print( "22 / 7 = " );
  scale = 10000000;
  printFixedDec( 22 * scale / 7, scale );
  Serial.println( );
}

void printFixedDec( long val, long scale )  // we don't need no steenking loops!
{
  Serial.print( val / scale );  // printing the left-side digits
  Serial.print( "." );               // then the dec pt
  Serial.print( val % scale );  // then the remainder to scale
}

void loop()
{
}

Continuing the discussion from Method to print fixed-point values.:

val=-12345678:

DEMO: start val = -12345678 mm == -12345.-678 m == -12.-345678 Km

and val=-123L:

DEMO: start val = -123 mm == 0.-123 m == 0.-123 Km

I tried:

// printing fixed porint numbers with leading zeros
int testValV3[] = {1, 123, 1000, 1234, 2001, -1, -123, -1000, -1234, -2001};

void setup() {
  char buffer[50];
  Serial.begin(115200);
  for (auto i : testValV3) {
    snprintf(buffer,50,"%c%i.%03i ", i<0? '-':' ',abs(i / 1000), abs(i % 1000));
    Serial.print(buffer);
  }
}

void loop() {
}

to get:

 0.001  0.123  1.000  1.234  2.001 -0.001 -0.123 -1.000 -1.234 -2.001 

Which works, but seems overly complicated. Is there a smoother way to print fixed point numbers?

It's wiser to print the '-' prefix and change the sign when it its negative:

void printFixedDec( long val, long scale )  // we don't need no steenking loops!
{
  if(val < 0 ){
    val = -val;
    Serial.print('-');
  }
  Serial.print( val / scale );  // printing the left-side digits
  Serial.print( "." );               // then the dec pt
  Serial.print( val % scale );  // then the remainder to scale
}

but this routine still fails when zero-padding is needed. Example, val=-1003:

DEMO: start val = -1003 mm == -1.3 m == -0.1003 Km

How about this:

/*
  Forum: https://forum.arduino.cc/t/method-to-print-fixed-point-values/82093/3
  Wokwi: https://wokwi.com/projects/388290041518382081
*/

void setup() {
  Serial.begin(115200);
  printFixedDec(-1003,0);
  Serial.println(" mm");
  printFixedDec(-1003,1);
  Serial.println(" cm");
  printFixedDec(-1003,3);
  Serial.println(" m");
  printFixedDec(-1003,6);
  Serial.println(" km");
}

void loop() {
}


void printFixedDec( long val, int dotPosition)  // we don't need no loops ...
{
  String zeros = "0000000000";
  String out = "";
  char sign = ' ';
  if(val < 0 ){
    val = -val;
    sign = '-';
  };
   out = String(val);
  if (dotPosition > 0) {
    int len = out.length();
    if (len < dotPosition) {
      out = zeros.substring(0,dotPosition-len+1)+out;
      len = out.length();
    }
    out = out.substring(0,len-dotPosition)+"."+ out.substring(len-dotPosition);
  } 
  Serial.print( sign+out );  
}

dotPosition gives the position of the dot in the string from right to left; it is identical to the exponent of 10 for scaling:

dotPosition     scale
0                 1
1                10
2               100
3              1000
etc.

Results:

-1003 mm
-100.3 cm
-1.003 m
-0.001003 km

I am sure it can be converted to use C strings only ... But this was quicker (for me) to prove the concept.

As in real live: "Significant zeros" are always making things more difficult ... :wink:

Wokwi: https://wokwi.com/projects/388290041518382081

I don't think it's a serious method, I just wrote it for fun...

1 Like

That's the thing Dave, slice exceptions off and fill as needed before cranking out the absolute value. Didn't you want exponential notation?

Filling with zeros is a pain.

Here's a version avoiding the steenking loops by using nested snprintf()s:

void setup()
{
  long val = -12; //12345678L;
  long scale = 1000L; // 3-decimal places scale

  Serial.begin(9600);
  Serial.print( "DEMO: start val = " );
  Serial.print( val, DEC );
  Serial.print( " mm == " );
  printFixedDec( val, scale );
  Serial.print( " m == " );
  printFixedDec( val, ( scale = 1000000 ));
  Serial.println( " Km" );
  Serial.println( );
  Serial.print( "22 / 7 = " );
  scale = 10000000;
  printFixedDec( 22 * scale / 7, scale );
  Serial.println( );

long testValV3[] = {1, 123, 1000, 1234, 2001, -1, -123, -1000, -1234, -2001,1000000000/3,-2000000000/3};
  for (auto val : testValV3) {
    Serial.print(val);
    Serial.print("/1000=\t");
    printFixedDec(val,1000);
    Serial.println(); 
  }
}

void printFixedDec( long val, long scale )  // we don't need no steenking loops!
{
  if (val < 0) {
    Serial.print('-');
    val = -val;
  }
  char buff[50], format[20];
  //  snprintf(buff,sizeof(buff)-1,"%li %i %li",val/scale,numPlaces(scale)-1,val%scale);
  //  snprintf(buff,sizeof(buff)-1,"%li %0*li",val/scale,numPlaces(scale)-1,val%scale);

  //snprintf(format,sizeof(format)-1,"%%li.%%0%ili",(int)(log(scale)/log(10)));
  snprintf(format, sizeof(format) - 1, "%%li.%%0%ili", numPlaces(scale) - 1);
  snprintf(buff, sizeof(buff) - 1, format, val / scale, val % scale);
  Serial.print(buff);
}

int numPlaces (long n) {
  // https://stackoverflow.com/a/1068937/1653571
#include <limits.h>
  if (n < 0) n = (n == LONG_MIN) ? LONG_MAX : -n;
  if (n < 10) return 1;
  if (n < 100) return 2;
  if (n < 1000) return 3;
  if (n < 10000) return 4;
  if (n < 100000) return 5;
  if (n < 1000000) return 6;
  if (n < 10000000) return 7;
  if (n < 100000000) return 8;
  if (n < 1000000000) return 9;
  /*      2147483647 is 2^31-1 - add more ifs as needed
     and adjust this final return as well. */
  return 10;
}

void loop()
{
}
DEMO: start val = -12 mm == -0.012 m == -0.000012 Km

22 / 7 = 3.1428571
1/1000=	0.001
123/1000=	0.123
1000/1000=	1.000
1234/1000=	1.234
2001/1000=	2.001
-1/1000=	-0.001
-123/1000=	-0.123
-1000/1000=	-1.000
-1234/1000=	-1.234
-2001/1000=	-2.001
333333333/1000=	333333.333
-666666666/1000=	-666666.666

When fixed point seems like a good idea, and you have to use it for printing, it might be cleanest to work directly in integral milli-units, micro-units or SI-prefixed units and completely avoid printing decimals.

Print for human reading, work with extra places to lose to rounding.

Versions of sprintf add a chunk to your hex file. It's why for less than universal formatting I roll my own text. To print decimals with it, it uses floats which on AVR's are 32-bit 6-place mantissa wonders.