Janis Lesinskis' Blog

Assorted ramblings

  • All entries
  • About me
  • Projects
  • Economics
  • Misc
  • Software-engineering
  • Sports

std::cout and uint8_t


A friend of mine contacted me with this slight head scratcher:

#include <iostream>
#include <cmath>
/*
 * Program to calculate the current flowing through an AC circuit composed of a
 * resister (resistance R measured in Ohms), a capacitor (capacitance C measured
 * in Farads) and an inductor (inductance L measured in Henrys).
 *
 * I = Current in Amps
 * E = Electromotive Force in Volts
 * R = Resistance in Ohms
 * F = Frequency of the Current in Hertz
 * L = Inductance in Henrys
 * C = Capacitance in Farads
 */

int main() {
    const double power = 2.0;

    uint8_t F;
    uint8_t R;
    float C;
    float L;
    uint8_t E;
    double I;

    std::cin >> F >> R >> C >> L >> E;
    std::cout << F << " " << R << " " << C << " " << L << " " << E;

    I = E / sqrt(pow(R, power) + pow(((2 * M_PI * F * L) - ( 1 / (2 * M_PI * F * C))), power));

    std::cout << "I = " <<  I;

    return 0;
}

Specifically when run like so it gives the following output:

(base) janis$ g++ -Wall henry.cpp -o henry
(base) janis$ ./henry 
200 15 0.0001 0.01476 15
2 0 0 15 0
I = 0

Now the question here is "what happened to the input for F"

This is entirely counterintuitive if you don't realize what's actually happening here.

Towards the end of this previous post I showed how you could see what a type was at runtime, this might be useful here if you wanted to try determine what was happening.

I think there's 2 interesting ways to highlight what's gone wrong here, the first way is to use scanf instead of std::cin:

//uint_8_scanf_printf.cpp
#include <iostream>
#include <cmath>
#include <cstdio>
#include <stdint.h>
#include <inttypes.h>

int main() {
    uint8_t F;
    uint8_t R;
    float C;
    float L;
    uint8_t E;

    std::scanf("%hhu %hhu %f %f %hhu", &F, &R, &C, &L, &E);
    std::printf("%hhu %hhu %f %f %hhu \n", F, R, C, L, E);
    return 0;
}

Now getting this right is a bit of a pain because just using %d as the scan type here will assume a 4 byte wide integer. We are using the %hhu format code to specify that we are wanting exactly an unsigned integer to be read in here. Now there's a more portable way to do this with some macros but it's a bit of a pain, more about that in a bit.

(base) janis$ g++ -Wall uint_8_scanf_printf.cpp -o uint_8_scanf_printf
(base) janis$ ./uint_8_scanf_printf 
200 15 0.0001 0.01476 15
200 15 0.000100 0.014760 15 

If you now see what's happened with the original code you have done well, if not keep reading on, the grand reveal is coming soon!

Now we might want to get rid of this std::printf in favor of the original std::cout for output and look at what's happening with the output, this gives a good hint at what's happening earlier:

//uint_8_scanf_cout.cpp
#include <iostream>
#include <cmath>
#include <cstdio>
#include <stdint.h>
#include <inttypes.h>

int main() {
    uint8_t F;
    uint8_t R;
    float C;
    float L;
    uint8_t E;

    std::scanf("%hhu %hhu %f %f %hhu", &F, &R, &C, &L, &E);
    std::cout << F << " " << R << " " << C << " " << L << " " << E << std::endl;
    return 0;
}

Now when we run this:

(base) janis$ g++ -Wall uint_8_scanf_cout.cpp -o uint_8_scanf_cout
(base) janis$ ./uint_8_scanf_cout 
200 15 0.0001 0.01476 15
�  0.0001 0.01476 

This unprintable character in the output is an interesting sign of something? Similar deal with the nothingness for 15.

The issue here comes down to the way in which the types are being handled, I'll cook up something using some not-so-random integer values for inputs and all will be more clear:

(base) janis$ ./uint_8_scanf_cout 
41 42 0.0001 0.01476 43
) * 0.0001 0.01476 +

Seeing the pattern?

(base) janis$ ./uint_8_scanf_cout 
65 66 0.001 0.01476 67
A B 0.001 0.01476 C

As you can see the issue comes down to the fact that uint8_t is actually a typedef for unsigned char so when you use std::cout it takes that type and tries to output it as a character.

Coming back to the original code, when std::cin thinks it's taking chars you can see that the input from 200 15 0.0001 0.01476 15 with the structure earlier gets turned into (char) 2 (char) 0 (float) 0 (float) 15 (char) 0 then there's no more input so this ends the input scanning step.

This has always really irked me, std::uint8_t should have been a different type and not just a typedef to unsigned char. Given the semantics are so different I think this is was a substantial language design mistake. Having properly distinct types would have allowed for the proper overload of the types for the input and output in this situation for example. As as result of this language mistake I'm not sure there's a nice way to get std::cin to work here.

There's a few ways in which you can work around this output formatting issue, one simple way is to add a unitary plus operator to force the integer representation:

//uint_8_scanf_cout_unitary_plus.cpp
#include <iostream>
#include <cmath>
#include <cstdio>
#include <stdint.h>
#include <inttypes.h>

int main() {
    uint8_t F;
    uint8_t R;
    float C;
    float L;
    uint8_t E;

    std::scanf("%hhu %hhu %f %f %hhu", &F, &R, &C, &L, &E);
    std::cout << +F << " " << +R << " " << C << " " << L << " " << +E << std::endl;
    return 0;
}

Running this:

(base) janis$ g++ -Wall uint_8_scanf_cout_unitary_plus.cpp -o uint_8_scanf_cout_unitary_plus
(base) janis$ ./uint_8_scanf_cout_unitary_plus
100 15 0.0001 0.01476 15
100 15 0.0001 0.01476 15

Another way I saw in this StackOverflow answer is to make a namespace that handles the conversion the way you want. This would work like so:

//namespace_numerical_chars.cpp
#include <cstdint>
#include <iostream>
#include <typeinfo>
#include <cmath>
#include <cstdio>
#include <inttypes.h>


namespace numerical_chars {
inline std::ostream &operator<<(std::ostream &os, char c) {
    return std::is_signed<char>::value ? os << static_cast<int>(c)
                                       : os << static_cast<unsigned int>(c);
}

inline std::ostream &operator<<(std::ostream &os, signed char c) {
    return os << static_cast<int>(c);
}

inline std::ostream &operator<<(std::ostream &os, unsigned char c) {
    return os << static_cast<unsigned int>(c);
}
}


int main() {
    uint8_t F;
    uint8_t R;
    float C;
    float L;
    uint8_t E;

    std::scanf("%hhu %hhu %f %f %hhu", &F, &R, &C, &L, &E);
    {
        using namespace numerical_chars;
        std::cout << F << " " << R << " " << C << " " << L << " " << E << std::endl;
    }
    return 0;
}

I quite like this, note how a block scope is introduced here for the namespace usage. This allows us to apply this just to the lines where we need it.

Published: Mon 29 March 2021
By Janis Lesinskis
In Software-engineering
Tags: c++ iostreams string-formatting types

links

  • JaggedVerge

social

  • My GitHub page
  • LinkedIn

Proudly powered by Pelican, which takes great advantage of Python.