SoDaFormat-1.0.0-main:f160581  1.0.0
SoDa::Format print stuff.

SoDa::Format is a class that allows intelligent formatting of integer, floating point, string, and character values into std::string objects or for output to a stream. The concept is inspired by the much more capable Qt::QString's formatting features. If you can afford the library dependency, use Qt. It's quite good.

SoDa::Format is meant to improve upon the tremendously awkward, antiquated, and bizarre stream output features of the standard template library. (C++ stream IO was a giant step backward from the comparatively flexible and intuitive FORTRAN "FORMAT" scheme. There isn't much in the computing world that is a giant step backward from FORTRAN circa 1975.)

One could use the BOOST format facility. (One could also eat brussels sprouts for breakfast, but it isn't my kind of thing. Is it yours?) boost::format is tremendously powerful and fabulously documented. It may be easily extended by beings living in some of the outer reaches of the Horse Head nebula. I just don't grok it myself. And carrying around a boost dependency is like growing an extra thumb – it is useful at times, but it becomes less and less so as time goes on, and you can never quite remember why you got it in the first place.

What really motivated me to write SoDa::Format was the lack of an "engineering notation" format for floating point numbers in any of the common formatting facilities. As a dyed in the wool MKS engineer, this drives me right up the wall. Exponents should be multiples of three. If Adam had done any floating point arithmetic, he would have written it down in engineering notation. (Perhaps he did, and then lost it all when the serpent screwed everything up.)

Enough of the ranting, let's look at an example.

We'll look at segments of the test program Format_Test.cxx. It is worth looking at the whole file, just not all at once. This text is the doxygen equivalent of touring the Louvre on a motorcycle.

To jump right in, let's imagine that we've got a set of variables that we want to write to std::cout.

  • i an integer
  • ef a double precision floating point number
  • c a character
  • fred a string

We could print all of those out like this:

std::cout << SoDa::Format("print 5 like this: %0\n"
"let's print 5 again %0\n"
"%1 looks much better in engineering notation %2\n"
"Sometimes I just want to print a %% sign like this %0%%\n"
"I think that shirt he's wearing fits %3 to a %4\n")
.addI(i)
.addF(ef, 'f')
.addF(ef, 'e')
.addS(fred)
.addC(c);

The result looks like this:

print 5 like this:  5
let's print 5 again 5
32157.500 looks much better in engineering notation   32.158e3
Sometimes I just want to print a % sign like this 5%
I think that shirt he's wearing fits Fred to a T

Let's tease things apart here.

The story starts with the creation of a format object like this

SoDa::Format sft("Avogadro's number: %0\n");

(In the first demo, we created the object and used it all on the same line. we can do that, or we can create a format and keep it around for a while.)

The format class has a bunch of methods that "fill in" parts of the format string. The methods: SoDa::Format::addI, SoDa::Format::addU, SoDa::Format::addF, SoDa::Format::addS, SoDa::Format::addC print integers, unsigned integers, floats or doubles, strings, and characters.

As each "addX" method is processed in turn, it fills in its placeholders. The first addX call fills in all the %0 placeholders no matter how many of them are in the string. The second fills in the %1 placeholders, and so on.

The floating point print method – SoDa::Format::addF – can print its value in one of four formats

  • f – corresponding to printf's "%f" specifier (more or less)
  • g – .... "%g"
  • s – in scientific notation with the integer part between 1 and 9 inclusive.
  • e – in engineering notation with the integer part between 1 and 999 inclusive, and the exponent a multiple of 3.

So we could print Avogadro's number in two ways: the normal way, and the right way.

// Print Avogadro's number the way we all remembered from high school.
double av = 6.02214076e23;
std::cout << sft.addF(av, 's');
// But all that 10^23 jazz is the result of the evil CGS system.
// Let's do this the way a respectable MKS user would have wanted it.
// first, let's reuse our format:
sft.reset();
// and now print
std::cout << "Here's how right thinking people write "
<< sft.addF(av, 'e');

which produces

Avogadro's number: 6.022e+23
Here's how right thinking people write Avogadro's number:  602.214e21

So once we've invoked the first addX method on a format, all the "%0" markers have been replaced. But we can "re-use" a format object by calling its SoDa::Format::reset method. as we did here

sft.reset();

Now the format string is restored to its original state with all the "%0" and "%1" and whatever markers back in place.

Each of the addX methods returns a reference to the format object they just fiddled with. The reset method does too. That's how this snippet works:

for(int p = 1; p < 4; p++) {
std::cout << format.reset().addC(fmt).addI(p).addF(ef, fmt, p+3, p);
}

The various methods provide arguments to set the field width and the number of digits after the decimal point. When I get really focused, I'll add the ability to specify the number of significant figures instead. This is really far more useful than the "%fN.P" scheme that we've been living with all these years. But don't get me started on significant digits.

Oh what the hell, we've got me started.

For my money, it is rarely important to know anything to more than one significant digit.

Perhaps even one binary digit.

Namespace

SoDa::Format is enclosed in the SoDa namespace because it is inevitiable that there are lots of classes out there called "Format." Perhaps you have written one of them. Naming a class "Format" is like naming a street "Oak:" It might make lots of sense, but you're going to have to reconcile yourself that there's a street with the same name one town over and sometimes your pizza is going to get mis-routed.

So Format is in the SoDa namespace. SoDa is from SoDaRadio though the SoDa::Format class is a completely independent chunk 'o code. Most of the code I'll release will be in the SoDa namespace, just to avoid the Oak Street problem.