【问题标题】:How to tweak std::stod (string to double) for decimal separator and number of digits如何调整 std::stod (string to double) 的小数分隔符和位数
【发布时间】:2012-09-01 06:29:25
【问题描述】:

有没有办法调整 std::stod() 以增加(字符串到双精度)转换中的小数位数并强制它使用美国语言环境?

我有一个可以在控制台或 gui 模式下运行的 Qt 应用程序:

if (opt->getFlag( 'c' ) || opt->getFlag( "console" ) ){
  ThreadManager  modelMainThread;
  modelMainThread.runFromConsole(inputFileName,scenarioName);
}
else {
  QApplication app(argc, argv);
  MainWindow mainWin;
  mainWin.show();
  return app.exec();
}

在这个应用程序中,我有一个 string to double 方法,它包装了新的 C++11 stod:

double s2d ( const string &string_h) const {
  try {
    return stod(string_h);
  } catch (...) {
    if (string_h == "") return 0;
    else {
      cout << "error!" << endl;
    }
  }
  return 0;
}

奇怪的是,在控制台模式下,字符串到双重转换需要一个带点作为小数分隔符的字符串,而在 gui 模式下,它需要一个带逗号的字符串。 此外,正如我之前使用的 istringstream:

istringstream totalSString( valueAsString );
totalSString >> valueAsDouble;

我注意到 stod 将生成的 double 截断为仅 3 个十进制数字,比 istringstream 少得多。

那么有没有办法增加小数位数并强制 std::stod 使用美国语言环境进行转换?

谢谢:-)

已编辑

如果我尝试这个脚本:

// testing stod() ..
vector<string> numbers;
numbers.push_back("123.1234567890");
numbers.push_back("123.1234");
numbers.push_back("123,1234567890");
numbers.push_back("123,1234");
double outd;
for(uint i=0;i<numbers.size();i++){
    try {
        outd =  stod(numbers[i]);
        cout << "Conversion passed: " << numbers[i] << "  -  " << outd << endl;
    } catch (...) {
        cout << "Conversion DID NOT passed: " << numbers[i] << "  -  " <<endl;
    }
}

我得到了这些结果:

“控制台”模式:

Conversion passed: 123.1234567890  -  123.123
Conversion passed: 123.1234  -  123.123
Conversion passed: 123,1234567890  -  123
Conversion passed: 123,1234  -  123

“gui”模式:

Conversion passed: 123.1234567890  -  123
Conversion passed: 123.1234  -  123
Conversion passed: 123,1234567890  -  123.123
Conversion passed: 123,1234  -  123.123

很明显,有些东西会影响 stod() 的行为!

【问题讨论】:

  • stod 被定义为使用sprintf%f。它不可配置。
  • 如果您关心原始运行时速度并希望独立于语言环境,并且担心可能更糟糕的编译时间,Boost.Spirit.Qi 绝对是解决问题的方法去吧。
  • 偶然发现了这一点,并对 stod 截断值感到震惊。但问题是 std::cout 没有输出整个值。您必须设置更高的精度: std::cout

标签: c++ qt c++11 type-conversion locale


【解决方案1】:

std::stod 是根据继承自 C 标准库的std::strtod 定义的。 C 函数 strtod 在 C 语言环境下工作,可通过 &lt;locale.h&gt; 标头中的 setlocale 函数访问。

在 C++ 中,C 语言环境仍然可以通过 &lt;clocale&gt; 标头中的 std::setlocale 函数访问,它确实会影响 std::strtodstd::stod

Qt 的QApplication 使用std::setlocale 来设置用户选择的语言环境。因此,每当您在 GUI Qt 应用程序中使用依赖于 C 语言环境的函数时,您将拥有依赖于语言环境的小数点。

现在,要强制数字使用特定的语言环境,您可以使用std::setlocale,如下所示。但请注意,这可能会破坏多线程应用程序,因为 C 语言环境是线程全局状态。下例将程序的locale临时设置为LC_NUMERIC=C,调用std::stod后恢复设置。

#include <iostream>
#include <clocale>
#include <vector>
#include <string>

void test()
{
    for(auto s : {"123.1234567890",
                  "123.1234",
                  "123,1234567890",
                  "123,1234"})
    {
        // Save locale setting
        const std::string oldLocale=std::setlocale(LC_NUMERIC,nullptr);
        // Force '.' as the radix point. If you comment this out,
        // you'll get output similar to the OP's GUI mode sample
        std::setlocale(LC_NUMERIC,"C");
        try
        {
            const auto outd=std::stod(s);
            std::cout << "Conversion succeeded: " << s << "  =>  "
                      << outd << '\n';
        }
        catch (...)
        {
            std::cout << "Conversion FAILED   : " << s << "  =>  ???\n";
        }
        // Restore locale setting
        std::setlocale(LC_NUMERIC,oldLocale.c_str());

    }
}

#include <QApplication>
int main(int argc, char** argv)
{
    std::cout << "Test in normal console mode\n";
    test();
    QApplication app(argc, argv);
    std::cout << "Test in GUI mode\n";
    test();
}

输出:

Test in normal console mode
Conversion succeeded: 123.1234567890  =>  123.123
Conversion succeeded: 123.1234  =>  123.123
Conversion succeeded: 123,1234567890  =>  123
Conversion succeeded: 123,1234  =>  123
Test in GUI mode
Conversion succeeded: 123.1234567890  =>  123.123
Conversion succeeded: 123.1234  =>  123.123
Conversion succeeded: 123,1234567890  =>  123
Conversion succeeded: 123,1234  =>  123

【讨论】:

  • 它的工作方式与 gtkmm 相同:在 main 添加 std::setlocale(LC_NUMERIC,"C"); after Gtk::Main app(argc, argv);
  • 我知道这个答案已有 2.5 年的历史,但是:您的 oldLocale 变量指向的内存可能会在下次调用 std::setlocale 时失效,只需下面的一个代码行!文档说:返回的字符串的 副本 以及此调用中使用的类别 std::setlocale 可以稍后在程序中使用,以将语言环境恢复到结束时的状态这个电话。你可能想要std::string oldLocale(std::setlocale(LC_NUMERIC,nullptr));代替(和std::setlocale(LC_NUMERIC,oldLocale.c_str());恢复)。
  • @MarcoFreudenberger 你说得对,我现在已经修好了。
【解决方案2】:

std::stod 及其近亲旨在提供从字符串到数字类型的简单快速转换。 (完全披露:这是我的设计)所以,不,没有语言环境;所见即所得。

【讨论】:

  • 那么,为了好奇,为什么我在 gui/console 模式下会有这种不同的行为? Qt 库设置的东西一定会影响 stod 行为。
  • @Antonello - 抱歉,我没有使用 Qt 的经验。
  • 为了澄清,stod() 受当前语言环境的影响,至少在 GCC 中是这样。因此,例如,为了确保它将使用点作为小数分隔符,应该使用 #include 和 setlocale(LC_ALL, "C") (这是默认设置,但显然 Qt 必须将其设置为正在运行的计算机区域设置,因此您需要覆盖 Qt 覆盖 ;-) )
  • @Antonello - 感谢您的提醒。我想起了一个建议,即向所有字符串转换函数添加显式语言环境参数,我坚决反对。 stod 是根据函数 strtod 的行为来指定的,这确实取决于全局语言环境。
  • 这个答案是错误的,您在 cmets 中承认了这一点。为什么不编辑它来修复?
【解决方案3】:

std::stod 是一种将std::string 转换为双精度的通用方式。如果你想要更具体的东西,你应该自己实现它。

例如:

double my_stod(const std::string &valueAsString) {
    istringstream totalSString( valueAsString );
    double valueAsDouble;
    // maybe use some manipulators
    totalSString >> valueAsDouble;
    if(!totalSString)
        throw std::runtime_error("Error converting to double");    
    return valueAsDouble;
}

【讨论】:

  • 是的,这是我实际使用的,但它很慢,而且我非常关心速度..
  • 嗯,问题是“如何提取 std::stod”。可悲的是,答案是“你不能”。如果你真的发现使用字符串流是你代码中的一个瓶颈,你应该优化它。但请记住,不要过早优化。
【解决方案4】:

为了避免在设置语言环境和使用strtod 时出现线程问题,我会使用istringstream

#include <optional>
#include <sstream>

[[nodiscard]] std::optional<double>
toDouble( const std::string& stringToConvert)
{
    std::istringstream streamToConvert( stringToConvert );
    streamToConvert.imbue( std::locale( "C" ) );
    double result = 0.0;
    streamToConvert >> result;

    if ( streamToConvert.fail() || streamToConvert.bad() || !streamToConvert.eof() ) {
        return std::nullopt;
    }
    return result;
}

如果你真的不想使用istringstream,你可以尝试调用std::num_get,这是istringstream内部调用的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多