Я пытаюсь использовать boost :: locale и std :: stod вместе в немецкой локали, где запятая является десятичным разделителем. Рассмотрим этот код:
boost::locale::generator gen;
std::locale loc(""); // (1)
//std::locale loc = gen(""); // (2)
std::locale::global(loc);
std::cout.imbue(loc);
std::string s = "1,1"; //float string in german locale!
double d1 = std::stod(s);
std::cout << "d1: " << d1 << std::endl;
double d2 = 2.2;
std::cout << "d2: " << d2 << std::endl;
std :: locale loc («») создает правильную локаль и вывод
d1: 1,1
d2: 2,2
как я и ожидал. Когда я закомментирую строку (1) и раскомментируем строку (2), вывод
d1: 1
d2: 2.2
Результат для d2 следует ожидать. Насколько я понимаю, boost :: locale хочет, чтобы я явно указал, что d2 должен быть отформатирован как число
std::cout << "d2: " << boost::locale::as::number << d2 << std::endl;
снова фиксирует вывод на 2,2 Проблема в том, что std :: stod больше не считает 1,1 действительным числом с плавающей запятой и усекает его до 1.
Мой вопрос:
почему std :: stod перестает работать, когда я генерирую свою локаль с помощью boost :: locale?
Дополнительная информация: я использую VC ++ 2015, Boost 1.60, нет ICU, Windows 10
Обновить:
Я заметил, что проблема исправлена, когда я дважды устанавливал глобальную локаль, сначала с помощью std :: locale («»), а затем с boost:
std::locale::global(std::locale(""));
bl::generator gen;
std::locale::global(gen(""));
Я понятия не имею, почему это ведет себя так, хотя!
Короче: boost::locale
изменяет только глобальный объект c ++ — locale, но не C-locale. stod
использует C-locale, а не глобальный c ++ — locale объект. std::locale
изменяет оба: глобальный объект языка c ++ — и язык Си.
Вся история: std::locale
это тонкая вещь и ответственная за много отладки!
Давайте начнем с класса c ++ std :: locale:
std::locale loc("de_DE.utf8");
std::cout<<loc.name()<<"\n\n\n";
создает немецкий языковой стандарт (если он доступен на вашем компьютере, в противном случае он выбрасывает), что приводит к de_DE.utf8
на консоли.
Однако это не меняет Глобальный Объект языка C ++, который создается при запуске вашей программы и является классическим («C»). Конструктор std::locale
без аргументов возвращает копию глобального состояния:
...
std::locale loc2;
std::cout<<loc2.name()<<"\n\n\n";
Теперь вы должны увидеть C
если ничто не испортило ваш язык раньше. std :: locale («») сделает что-то волшебное, выяснит настройки пользователя и вернет его как объект, без изменение глобального состояния.
Вы можете изменить местное государство с помощью std::local::global
:
std::locale::global(loc);
std::locale loc3;
std::cout<<loc3.name()<<"\n\n\n";
Конструктор по умолчанию на этот раз de_DE.utf8
на консоли.
Мы можем восстановить глобальное состояние до классического, вызвав:
std::locale::global(std::locale::classic());
std::locale loc4;
std::cout<<loc4.name()<<"\n\n\n";
который должен дать вам C
снова.
Теперь, когда std :: cout создан, он клонирует свою локаль из глобального состояния c ++ (здесь мы делаем это с помощью stringstream, но это то же самое). Более поздние изменения глобального состояния не влияют на поток:
//classical formating
std::stringstream c_stream;
//german formating:
std::locale::global(std::locale("de_DE.utf8"));
std::stringstream de_stream;
//same global locale, different results:
c_stream<<1.1;
de_stream<<1.1;
std::cout<<c_stream.str()<<" vs. "<<de_stream.str()<<"\n";
Дает тебе 1.1 vs. 1,1
— первый классический классический второй немецкий
Вы можете изменить локальную локаль-объект потока с помощью imbue(std::locale::classic())
само собой разумеется, что это не меняет глобального состояния:
de_stream.imbue(std::locale::classic());
de_stream<<" vs. "<<1.1;
std::cout<<de_stream.str()<<"\n";
std::cout<<"global c++ state: "<<std::locale().name()<<"\n";
и вы видите:
1,1 vs. 1.1
global c++ state: de_DE.utf8
Теперь мы подходим к std::stod
, Как вы можете себе представить, он использует глобальное состояние языка С ++ (не совсем верно, терпите меня), а не (частное) состояние cout
-поток:
std::cout<<std::stod("1.1")<<" vs. "<<std::stod("1,1")<<"\n";
дает тебе 1 vs. 1.1
потому что глобальное государство все еще "de_DE.utf8"
первый разбор останавливается на '.'
но местное состояние std::cout
все еще "C"
, После восстановления глобального состояния мы получаем классическое поведение:
std::locale::global(std::locale::classic());
std::cout<<std::stod("1.1")<<" vs. "<<std::stod("1,1")<<"\n";
Теперь немец "1,1"
не анализируется должным образом: 1.1 vs. 1
Теперь вы можете подумать, что мы сделали, но это еще не все — я обещал рассказать вам о std::stod
,
Рядом с глобальной локалью c ++ существует так называемая (глобальная) локаль C (происходит от языка C и не следует путать с классической локалью C). Каждый раз, когда мы меняли глобальный языковой стандарт C ++, языковой стандарт C тоже менялся.
Получение / настройка локали C может быть сделано с std::setlocale(...)
, Чтобы запросить текущее значение, выполните:
std::cout<<"(global) C locale is "<<std::setlocale(LC_ALL,NULL)<<"\n";
видеть (global) C locale is C
.Для установки языка C запустите:
assert(std::setlocale(LC_ALL,"de_DE.utf8")!=NULL);
std::cout<<"(global) C locale is "<<std::setlocale(LC_ALL,NULL)<<"\n";
который дает (global) C locale is de_DE.utf8
, Но какова сейчас глобальная локаль с ++?
std::cout<<"global c++ state: "<<std::locale().name()<<"\n";
Как вы можете ожидать, C ничего не знает о глобальной локали c ++ и оставляет ее без изменений: global c++ state: C
,
Теперь мы больше не в Канзасе! Старые c-функции использовали бы C-locale, а новую функцию c ++ — глобальный c ++. Готовьтесь к забавной отладке!
Что бы вы ожидали
std::cout<<"C: "<<std::stod("1.1")<<" vs. DE :"<<std::stod("1,1")<<"\n";
сделать? std::stod
В конце концов, это совершенно новая функция C ++ 11, и она должна использовать глобальную локализацию C ++! Подумай еще раз …
1 vs. 1.1
Он правильно понимает немецкий формат, потому что C-locale установлен на ‘de_DE.utf8’ и использует старые функции в стиле C под капотом.
Просто для полноты std::streams
используйте глобальную локализацию c ++:
std::stringstream stream;//creating with global c++ locale
stream<<1.1;
std::cout<<"I'm still in 'C' format: "<<stream.str()<<"\n";
дает тебе: I'm still in 'C' format: 1.1
,
Редактировать: Альтернативный метод для разбора строки без вмешательства в глобальную локаль или для беспокойства:
bool s2d(const std::string &str, double &val, const std::locale &loc=std::locale::classic()){
std::stringstream ss(str);
ss.imbue(loc);
ss>>val;
return ss.eof() && //all characters interpreted
!ss.fail(); //nothing went wrong
}
Следующие тесты показывают:
double d=0;
std::cout<<"1,1 parsed with German locale successfully :"<<s2d("1,1", d, std::locale("de_DE.utf8"))<<"\n";
std::cout<<"value retrieved: "<<d<<"\n\n";
d=0;
std::cout<<"1,1 parsed with Classical locale successfully :"<<s2d("1,1", d, std::locale::classic())<<"\n";
std::cout<<"value retrieved: "<<d<<"\n\n";
d=0;
std::cout<<"1.1 parsed with German locale successfully :"<<s2d("1.1", d, std::locale("de_DE.utf8"))<<"\n";
std::cout<<"value retrieved: "<<d<<"\n\n";
d=0;
std::cout<<"1.1 parsed with Classical locale successfully :"<<s2d("1.1", d, std::locale::classic())<<"\n";
std::cout<<"value retrieved: "<<d<<"\n\n";
Только первые и последние конверсии успешны:
1,1 parsed with German locale successfully :1
value retrieved: 1.1
1,1 parsed with Classical locale successfully :0
value retrieved: 1
1.1 parsed with German locale successfully :0
value retrieved: 11
1.1 parsed with Classical locale successfully :1
value retrieved: 1.1
std :: stringstream может быть не самым быстрым, но имеет свои достоинства …
Других решений пока нет …