Skip to content

ExplicitLiteralTypes

David Jewsbury edited this page Jan 27, 2015 · 2 revisions

#Explicit literal types

Consider using explicit literal types wherever possible.

##Example

namespace Examples
{
    // many different types of "0"
  const auto valueUnsigned = 0u;
  const auto valueChar = '\0';
  const auto valueWChar = L'\0';
  const auto valueUInt64 = 0ull;
  const auto valuePtr = nullptr;
  const auto valueFloat = 0.f;
  const auto valueDouble = 0.;
}

##Rationale

C++11 contains many ways to clarify the types of literals. It helps to be explicit.

Personally, I started to be very conscious of this while working on the PS2. The PS2 CPU didn't have any hardware instruction for double precision floats. Fortunately, it did have support for single precision floats. (Early console games might have looked pretty cool at the time, but the truth is they were built with very primitive hardware.)

We compiled for the PS2 using a general purpose compiler that could generate code for doubles. It did this by generating function calls to a library that did double precision math in software (using hardware integer instructions).

So, if the compiler encountered single precision floating point math, it was fine. For example, if there was a single precision floating point multiply, it just generated the normal instructions using the single precision hardware instructions.

But if the compiler encountered the same multiply operation, but this time using double precision numbers... Well, it will fall back to calling the library functions and performing an emulated double precision multiply using integer hardware instructions.

And the worst case was if there was a mix of single and double precision operations. Here, the compiler would have to convert back and forth between representations, moving data from integer registers into floating point registers and back again.

There was no compiler warning for this. The only way we could know it was happening was by looking at the profile readouts, and noticing the functions for this double precision library. And believe me, they were expensive! Even trivial work would eat up massive amounts of processor time.

We had to be careful to never use double precision math. But that's not as easy as it sounds.

Is the following a single precision multiply or a double precision?

float result = complex.RealPart() * 3.1415;

Even if we know that complex.RealPart() returns a single precision number, the compile will promote the operation to double because the literal is double. Have you ever tried scanning code to see if all of the little 'f's are there at the end of floating point numbers? It's not easy!

The compiler is can promote the type of operands used in an operation in many cases. If two operands are of different types, it will tend to choose the largest (and so most expensive) type. That's not always what we want.

Some compilers will give you warnings to help catch some of these cases.

But it's particularly difficult when we use auto a lot, and when we use functions that have overloads for different types (eg, std::sin). In these cases, we can end up very valid code, even though the precision is not really what we expect.

Maybe Intel CPUs are very good about being reasonably efficient at every precision. But if we want to re-target our code for a less sophisticated processor (eg, ARM based mobile processor), it might start to become an issue.

So, it's always nice to be explicit about the precision we want for each literal.