📜 ⬆️ ⬇️

Two-sided locale in conversion from string to fractional



Each C ++ developer sooner or later encounters the features of converting a fractional number from a string representation (std :: string) into a directly floating point number (float) associated with the locale installed. As a rule, a problem arises with a different representation of the separator of the integer and fractional parts in the decimal notation of the number ("," or ".").

This article focuses on the duality of C ++ locales. If you are wondering why the conversion of the same std :: string ("0.1") using std :: stof () and std :: istringstream to float can lead to different results, please under the cat.

Problem


As in many Habr's articles, everything started with an error in the code, a fragment of which can be reduced to the following:
')
float valf = std::stof(str); //  str = std::string("0.1") std::cout << valf << std::endl; //  0,   0.1 

“It’s a matter of locale,” I think, so for debugging purposes, before converting, I’ll append a line to display the real separator of the integer and fractional parts on the screen, expecting to see there ",":

 std::locale lcl; //      const auto & facet = std::use_facet<std::numpunct<char>>(lcl); std::cout << facet.decimal_point() << std::endl; //  ! 

A few words about the submitted code
For the sake of a beautiful code, it is worth noting that it would be more appropriate to add a check for the existence of a facet:

 std::locale lcl; if (std::has_facet<std::numpunct<char>>(lcl)) { //... } 

More details about working with facets and locales in C ++ can be found here: on Habré , in the documentation .

It turns out that the locale is set correctly, and the string "0.1" should be converted correctly. Check the conversion via std :: istringstream:

 float valf = std::stof(str); //  str = std::string("0.1") std::cout << valf << std::endl; //  0,   0.1 std::istringstream iss(str); iss >> valf; std::cout << valf << std::endl; //  0.1,  ! 

We get that conversion through std :: istringstream works as expected, while std :: stof () returns an invalid value.

The essence


In C ++, there are two global locales:


At the same time, changing the global locale using the std :: locale :: global () function changes both the STL locale and the C-library locale, while the setlocale () function only affects the second one.

Thus, the mismatch is possible:

 auto * le = localeconv(); std::cout << le->decimal_point << std::endl; //   std::locale lcl; //      const auto & facet = std::use_facet<std::numpunct<char>>(lcl); std::cout << facet.decimal_point() << std::endl; //  ! 

The catch is that the function from C ++ 11 std :: stof () (like std :: stod ()) is based on the function strtod () (or wcstod ()) from library C, which, in turn, focuses on the C-library locale. It turns out that the behavior of the C ++ function is based on the C-library locale, and not on the STL locale, as expected.

Conclusion


C ++ STL functions can use C-library functions in their work, which can lead to unexpected results, in particular, in case of a mismatch between the global STL locales and the C-library. Need to keep that in mind.

In my particular case, the Qt library's QCoreApplication class was “guilty” under * nix, which, when initialized, causes setlocale (), thereby leading to a possible mismatch of the described locales.

PS As many will rightly say, the Qt library has its own means of converting a string to a number, just like its own global locale (QLocale). The described situation arose when integrating code from a project using only STL into a Qt project.

Source: https://habr.com/ru/post/344108/


All Articles