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(""));

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

1 ответ

Решение

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

Другие вопросы по тегам