Stod не работает правильно с boost :: locale

Я пытаюсь использовать 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(""));

Я понятия не имею, почему это ведет себя так, хотя!

4

Решение

Короче: 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 может быть не самым быстрым, но имеет свои достоинства …

4

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]