How to tweak std::stod (string to double) for deci

2019-06-17 10:29发布

问题:

is there a way to tweak std::stod() in order to increase the number of decimal digits in the (string to double) conversion and to force it to use the US locale?

I have a Qt application that can be run in both console or gui mode:

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

Within this application I have a string to double method that wraps the new 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;
}

Odd enough, while in the console mode the string to double conversion expects a string with dot as decimal separator, in the gui mode it instead expects a string with comma. Furthermore, as I was previously using istringstream:

istringstream totalSString( valueAsString );
totalSString >> valueAsDouble;

I noticed that stod truncates the resulting double to just 3 decimal digits, much less than istringstream.

So is there a way to increase the number of decimal digits and to force std::stod to use the US locale for the conversion ?

Thanks :-)

EDITED:

If I try this script:

// 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;
    }
}

I got these results:

"console" mode:

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

"gui" mode:

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

So clearly there is something influencing stod() behaviour !

回答1:

std::stod and its kin were designed to provide a simple, quick conversion from a string to a numeric type. (full disclosure: it's my design) So, no, no locales; what you see is what you get.



回答2:

std::stod is a somehow generic way of converting a std::string to a double. If you want something more specific, you should implement it yourself.

For example:

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;
}


回答3:

std::stod is defined in terms of std::strtod, which is inherited from the C standard library. The C function strtod works in terms of the C locale, accessible via the setlocale function from the <locale.h> header.

In C++, the C locale is still accessible via std::setlocale function in the <clocale> header, and it does influence both std::strtod and std::stod.

Qt's QApplication uses std::setlocale to set the user-chosen locale. Thus whenever you use a C-locale-dependent function in a GUI Qt application, you'll have locale-dependent radix point.

Now, to force a particular locale for numbers, you can use std::setlocale as follows. Note though, that this can break multithreaded apps, since C locale is a thread-global state. The example below will set program's locale temporarily to LC_NUMERIC=C, and restore the setting after calling 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 auto 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);

    }
}

#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();
}

Output:

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