1

I am trying to make strings into doubles using the values that I obtained from a .txt file.

The doubles I am obtaining have no decimals. I believe that this is because, in the .txt, the decimals of the numbers are separated by a comma instead of a dot. But I don't know how to solve that.

This is a simplification of my code:

#include <iostream>
#include <fstream> // read text file
#include <stdlib.h> // strtod, atof

int main() {

std::ifstream readWindData("winddata.txt");

// check that document has been opened correctly
if (!readWindData.is_open()) {
    std::cout << "Wind data could not be opened." << std::endl;
    return 1;
}

// skip headers of the table
std::string firstLine;
std::getline(readWindData, firstLine);

int numberOfRows = 0; // variable to count rows of the table

// initialise strings that separate each value
std::string time, string u10st, u40st, u60st, u80st, u100st, 
            u116st, u160st, dir10st, dir60st, dir100st, timeDecst;

// initialise doubles
double u10, u40, u60, u80, u100, u116, u160, dir10, dir60, dir100, timeDec;

std::string nextLine;

// Read strings and turn it into doubles line by line until end
while (readWindData >> time >> u10st >> u40st >> u60st >> u80st >> u100st
        >> u116st >> u160st >> dir10st >> dir60st >> dir100st >> timeDecst) {

    // try two different functions to turn strings into doubles:
    u10 = strtod(u10st.c_str(), NULL);
    u40 = atof(u40st.c_str());

    // ensure numbers are displaying all their decimals
    std::cout.precision(std::numeric_limits<double>::digits10 + 1);

    // see what I am getting
    std::cout << time << " " << u10st << " " << u10 << " " << u40 << "\n";

    std::getline(readWindData, nextLine); // this line skips some crap on the side of some rows

    numberOfRows++; // counts rows
}

std::cout << "Number of rows = " << numberOfRows << "\n";
readWindData.close();

return 0;
}

these are three lines of the file:

time (hour) u10(m/s)u40(m/s)u60 (m/s)u80(m/s)u100(m/s)u116(Um/s)u160(m/s)dir10 dir60 dir100         time decimal hours      
00:00       4,25636 7,18414 8,56345 9,75567 10,9667 12,1298 13,8083 110,616 131,652 141,809         0       midnight
00:10       4,54607 7,40763 8,62832 9,91782 11,2024 12,2694 14,1229 114,551 133,624 142,565         0,166666667 

And these are those lines outputted with the above code: (reminder, I std::cout time (string), u10st (string), u10 (double), u40 (double)).

00:00 4,25636 4 7
00:10 4,54607 4 7

Any ideas on how to read that 4,25636 string into a double 4.25636? The file is too long to modify.

ao_martinv
  • 55
  • 6
  • 2
    Looks like a locale issue; the serialization happened when the locale was such that the decimal separator was a comma, and your locale uses a period. Look into setting the locale. – AndyG Jun 10 '20 at 11:16
  • @AndyG I have looked into it a little bit and see how you seem right. I fail to see how to change what the functions atof and strtod do though. Most examples I find are for std::cout, std::cin... Can I ask you for some more guiding to solve this specific problem? – ao_martinv Jun 10 '20 at 11:41
  • @AndyG It appears that `atof` and `strtod` always use the "C" locale (expecting a dot decimal). From [cppreference](http://www.cplusplus.com/reference/cstdlib/atof/): *A valid floating point number for atof using the "C" locale is formed...* – Adrian Mole Jun 10 '20 at 11:46

2 Answers2

1

The floating point numbers are using comma decimal separators, and you are expecting periods.

This is an indication that the data was serialized with a different locale than yours.

To address this, set your locale before parsing the numbers (and restore afterwards).

For example, here is some code that parses the number "12,34" with a default period separator, and then we set the locale to Denmark and try again:

const char* number = "12,34";
double parsed_number = 0;
parsed_number = std::strtod(number, nullptr);
std::cout << "parsed number in default locale: " << parsed_number << std::endl;
std::cout << "Setting locale to Denmark (comma decimal delimiter)" << std::endl;
std::locale::global(std::locale("en_DK.utf8"));
parsed_number = std::strtod(number, nullptr);
std::cout << "parsed number in Denmark locale: " << parsed_number << std::endl;
//restore default locale
std::locale::global(std::locale(""));

Output:

parsed number in default locale: 12
Setting locale to Denmark (comma decimal delimiter)
parsed number in Denmark locale: 12.34

Live Demo

Ask around to see who serialized this data, and get the correct locale. You can find available locales in *nix systems with locale -a

On Windows it appears to be a little more difficult

AndyG
  • 39,700
  • 8
  • 109
  • 143
1

To avoid messing around with the global locale you can just replace , with . before calling strtod()

    std::replace(u10st.begin(), u10st.end(), ',', '.');
    u10 = strtod(u10st.c_str(), nullptr);

But in the program shown above you can also read from ifstream directly into double using operator >>, if you use a locale where comma is a decimal separator.

    readWindData.imbue(std::locale("de_DE.UTF-8")); // or fr, nl, ru, etc.

    double u10, u40, u60, u80, u100, u116, u160, dir10, dir60, dir100, timeDec;

    while (readWindData >> time >> u10 >> u40 >> u60 >> u80 >> u100
            >> u116 >> u160 >> dir10 >> dir60 >> dir100 >> timeDec) {

Live demo

rustyx
  • 80,671
  • 25
  • 200
  • 267
  • Both solutions have worked. Thanks! (The second being the best one for simplicity). Though I have had to solve some stuff to make it work. Otherwise got the error: "terminate called after throwing an instance of 'std::runtime_error' what(): locale::facet::_S_create_c_locale name not valid." To avoid that error, working from Ubuntu: $ sudo apt-get install locales $ sudo locale-gen de_DE.UTF-8 – ao_martinv Jun 10 '20 at 14:00