Kill the locale dependency bug class by not allowing Qt to mess with LC_NUMERIC.
Context: #18124 (Recommended reading if you are unsure about how C and C++ locales work in C++)
Guaranteed locale settings before this PR:
| Program | C locale category LC_NUMERIC | Global C++ locale ( std::locale) | 
|---|---|---|
| bitcoind | C(classic) | C(classic) | 
| bitcoin-qt | No guarantee - user-specified | C(classic) | 
Guaranteed locale settings after this PR:
| Program | C locale category LC_NUMERIC | Global C++ locale ( std::locale) | 
|---|---|---|
| bitcoind | C(classic) | C(classic) | 
| bitcoin-qt | C(classic) | C(classic) | 
Background:
Perhaps surprisingly Qt runs setlocale(LC_ALL, "") on initialization. This installs the locale specified by the user’s LC_ALL (or LC_*) environment variable as the new C locale.
In contrast bitcoind does not opt-in to localization – no call to setlocale(LC_ALL, "") (or std::locale::global(std::locale(""))) is made and the environment variables LC_* are thus ignored.
This results in an unfortunate situation where the bitcoind is guaranteed to be running with the classic locale ("C") whereas the locale of bitcoin-qt will vary depending on the user’s environment variables.
An example: Assuming the environment variable LC_ALL=de_DE then the call std::to_string(1.23) will return "1.230000" in bitcoind but "1,230000" in bitcoin-qt.
From the Qt documentation:
“On Unix/Linux Qt is configured to use the system locale settings by default. This can cause a conflict when using POSIX functions, for instance, when converting between data types such as floats and strings, since the notation may differ between locales. To get around this problem, call the POSIX function
setlocale(LC_NUMERIC,"C")right after initializingQApplication,QGuiApplicationorQCoreApplicationto reset the locale that is used for number formatting to “C”-locale.”
C and C++ locales 101:
 0#include <clocale>
 1#include <cstdlib>
 2#include <iostream>
 3#include <locale>
 4#include <sstream>
 5#include <string>
 6
 7int main(void)
 8{
 9    std::cout << "C locale at beginning of main: " << std::string{setlocale(LC_ALL, nullptr)} << "\n";
10    std::cout << "C++ locale at beginning of main: " << std::locale{}.name() << "\n\n";
11#if OPT_IN_TO_LOCALIZATION_USING_SET_LOCALE
12    setlocale(LC_ALL, "");
13#endif
14#if OPT_IN_TO_LOCALIZATION_USING_STD_LOCALE_GLOBAL
15    std::locale::global(std::locale(""));
16#endif
17    std::ostringstream oss;
18    oss << 1000000;
19    std::cout << "std::ostringstream oss; oss << 1000000; oss.str() equals " << oss.str() << "\n";
20    std::cout << "std::to_string(1.23) equals " << std::to_string(1.23) << "\n\n";
21    std::cout << "C locale at end of main: " << std::string{setlocale(LC_ALL, nullptr)} << "\n";
22    std::cout << "C++ locale at end of main: " << std::locale{}.name() << "\n\n";
23    std::cout << "setlocale(LC_COLLATE,  nullptr) (read-only operation) = " << std::string{setlocale(LC_COLLATE, nullptr)} << "\n";
24    std::cout << "setlocale(LC_CTYPE,    nullptr) (read-only operation) = " << std::string{setlocale(LC_CTYPE, nullptr)} << "\n";
25    std::cout << "setlocale(LC_MESSAGES, nullptr) (read-only operation) = " << std::string{setlocale(LC_MESSAGES, nullptr)} << "\n";
26    std::cout << "setlocale(LC_MONETARY, nullptr) (read-only operation) = " << std::string{setlocale(LC_MONETARY, nullptr)} << "\n";
27    std::cout << "setlocale(LC_NUMERIC,  nullptr) (read-only operation) = " << std::string{setlocale(LC_NUMERIC, nullptr)} << "\n";
28    std::cout << "setlocale(LC_TIME,     nullptr) (read-only operation) = " << std::string{setlocale(LC_TIME, nullptr)} << "\n";
29}
Let’s try with LC_ALL=de_DE ./l8n without any opt-in to locale dependence:
 0$ clang++ -o l8n l8n.cpp
 1$ LC_ALL=de_DE ./l8n
 2C locale at beginning of main: C
 3C++ locale at beginning of main: C
 4
 5std::ostringstream oss; oss << 1000000; oss.str() equals 1000000
 6std::to_string(1.23) equals 1.230000
 7
 8C locale at end of main: C
 9C++ locale at end of main: C
10
11setlocale(LC_COLLATE,  nullptr) (read-only operation) = C
12setlocale(LC_CTYPE,    nullptr) (read-only operation) = C
13setlocale(LC_MESSAGES, nullptr) (read-only operation) = C
14setlocale(LC_MONETARY, nullptr) (read-only operation) = C
15setlocale(LC_NUMERIC,  nullptr) (read-only operation) = C
16setlocale(LC_TIME,     nullptr) (read-only operation) = C
Let’s try with LC_NUMERIC=de_DE ./l8n without any opt-in to locale dependence:
 0$ clang++ -o l8n l8n.cpp
 1$ LC_NUMERIC=de_DE ./l8n
 2C locale at beginning of main: C
 3C++ locale at beginning of main: C
 4
 5std::ostringstream oss; oss << 1000000; oss.str() equals 1000000
 6std::to_string(1.23) equals 1.230000
 7
 8C locale at end of main: C
 9C++ locale at end of main: C
10
11setlocale(LC_COLLATE,  nullptr) (read-only operation) = C
12setlocale(LC_CTYPE,    nullptr) (read-only operation) = C
13setlocale(LC_MESSAGES, nullptr) (read-only operation) = C
14setlocale(LC_MONETARY, nullptr) (read-only operation) = C
15setlocale(LC_NUMERIC,  nullptr) (read-only operation) = C
16setlocale(LC_TIME,     nullptr) (read-only operation) = C
Let’s try LC_ALL=de_DE ./l8n with opt-in to locale dependence using set_locale:
 0$ clang++ -DOPT_IN_TO_LOCALIZATION_USING_SET_LOCALE -o l8n l8n.cpp
 1$ LC_ALL=de_DE ./l8n
 2C locale at beginning of main: C
 3C++ locale at beginning of main: C
 4
 5std::ostringstream oss; oss << 1000000; oss.str() equals 1000000
 6std::to_string(1.23) equals 1,230000
 7
 8C locale at end of main: de_DE
 9C++ locale at end of main: C
10
11setlocale(LC_COLLATE,  nullptr) (read-only operation) = de_DE
12setlocale(LC_CTYPE,    nullptr) (read-only operation) = de_DE
13setlocale(LC_MESSAGES, nullptr) (read-only operation) = de_DE
14setlocale(LC_MONETARY, nullptr) (read-only operation) = de_DE
15setlocale(LC_NUMERIC,  nullptr) (read-only operation) = de_DE
16setlocale(LC_TIME,     nullptr) (read-only operation) = de_DE
Let’s try LC_ALL=de_DE ./l8n with opt-in to locale dependence using std::locale::global:
 0$ clang++ -DOPT_IN_TO_LOCALIZATION_USING_STD_LOCALE_GLOBAL -o l8n l8n.cpp
 1$ LC_ALL=de_DE ./l8n
 2C locale at beginning of main: C
 3C++ locale at beginning of main: C
 4
 5std::ostringstream oss; oss << 1000000; oss.str() equals 1.000.000
 6std::to_string(1.23) equals 1,230000
 7
 8C locale at end of main: de_DE
 9C++ locale at end of main: de_DE
10
11setlocale(LC_COLLATE,  nullptr) (read-only operation) = de_DE
12setlocale(LC_CTYPE,    nullptr) (read-only operation) = de_DE
13setlocale(LC_MESSAGES, nullptr) (read-only operation) = de_DE
14setlocale(LC_MONETARY, nullptr) (read-only operation) = de_DE
15setlocale(LC_NUMERIC,  nullptr) (read-only operation) = de_DE
16setlocale(LC_TIME,     nullptr) (read-only operation) = de_DE