1

Given a decimal values (seconds and fractions thereof) as a string such as

std::string span = "ssss.nnnn"  // ssss is value in seconds, nnnn is fractional seconds

What is the best way to convert it to a timeval structure (val.ts_sec and val.ts_usec) or a timespec structure (tv_sec and tv_nsec).

Most of the answers discuss converting values or are not C++. Some answers get very complex or set up classes which really is too much for this usage.

Obviously sscanf or istringstream can be used if the two values are separated by white space. However, is there a simple way of doing this if they are separated by a "." without looping over the character buffer searching for the "."

sabbahillel
  • 3,969
  • 1
  • 17
  • 34

4 Answers4

2

EDIT: As Borgleader rightly mentioned, simply reading into a double can incur precision loss if the timestamp becomes sufficiently large (larger than a million, give or take). A numerically stable way is

timeval v;
time_t seconds;
double fraction;

std::istringstream parser(span);

if(parser >> seconds >> std::noskipws >> fraction) {
  v.tv_sec  = seconds;
  v.tv_usec = static_cast<suseconds_t>(fraction * 1e6);
}

Since now the fraction part is guaranteed to be small enough that the mantissa of an ieee-754 double will cover more than 9 decimal digits after the comma. One possible addition is

  v.tv_usec = static_cast<suseconds_t>(fraction * 1e6 + 0.5); // rounding to nearest instead of down

depending on your use case.

Wintermute
  • 39,646
  • 4
  • 64
  • 71
  • 1
    Wouldn't this incur a possible loss of precision? – Borgleader Jan 09 '15 at 15:41
  • Hmm...that's a decent point. If t becomes larger than roughly a million, it'll start to leak nanoseconds, which I suppose can happen with timestamps. I'll edit something in for cases where that's critical. – Wintermute Jan 09 '15 at 15:43
  • @Wintermute That is why I was hoping for a method that can split the strings into two and then cast them individually into the timeval or timespec structures. – sabbahillel Jan 09 '15 at 15:47
  • See the edit. This way there's more than enough room to swallow any errors. The basic approach is the same; there's no need to monkey around with strings here. – Wintermute Jan 09 '15 at 15:54
  • @Wintermute I just realized that a sscanf using a format of "%d.%d" could get the two parts of the value as integers without having to worry about the multiplication or the lack of white space. – sabbahillel Jan 09 '15 at 16:39
  • But you'd have to worry about leading zeroes after the decimal point. The multiplication is not critical if you remove the part before the decimal point first (which the edited code does); the problem with that was only that those digits could take up a part of the mantissa that you need for the fraction. – Wintermute Jan 09 '15 at 16:39
  • @Wintermute Thanks. This code seems to be the best solution. I have accepted it. I will also have to check if timeval or timespec structures are required. – sabbahillel Jan 09 '15 at 16:49
2

If you decide to use string class and its functions If the number is always decimal, then I would suggest the following solution:

  string span = "1234.123";
  span += "000000";
  size_t pos = span.find('.');

  struct timeval val;
  val.tv_sec = stol(span.substr(0,pos));
  val.tv_usec = stol(span.substr(pos+1,6));

If the string may also get integer value without the dot '.' character then use

  string span = "1234";
  size_t pos = span.find('.');

  struct timeval val;
  val.tv_sec = stol( (pos!=string::npos)? span.substr(0,pos):span );
  val.tv_usec = (pos!=string::npos)? stol((span+"000000").substr(pos+1,6)):0;

This solution also uses some c++11.

ztik
  • 3,122
  • 11
  • 32
  • This is basically what I would have done (+1), since it was already in std::string and I avoid using doubles for integer types. However you don't need to add zeros, and you do need to validate better. pos!=npos, pos+1 – Kenny Ostrom Jan 09 '15 at 17:01
  • @KennyOstrom you are correct about `pos!=npos`. I just tried to keep it simple, and I assumed that the dot '.' exists always. I will add your suggestion. Regarding the zeros, the tv_usec stores the number of microseconds. If we do not add zeros then we will have 123 microseconds and not 123000. The solution is to append enough zeros (6) at the end and truncate the string using first 6 characters. – ztik Jan 10 '15 at 09:53
0

I just found this as a possible answer. I would still like to find something else as well.

Parse (split) a string in C++ using string delimiter (standard C++)

strtok allows you to pass in multiple chars as delimiters. I bet if you passed in ">=" your example string would be split correctly (even though the > and = are counted as individual delimiters).

EDIT if you don't want to use c_str() to convert from string to char*, you can use substr and find_first_of to tokenize.

string token, mystring("scott>=tiger");
while(token != mystring){
  token = mystring.substr(0,mystring.find_first_of(">="));
  mystring = mystring.substr(mystring.find_first_of(">=") + 1);
  printf("%s ",token.c_str());
}

Update:

@Wintermute pointed out that the following code snippet would not work because of the possibility of leading zeros.

string span;
int sec;
int usec;
timeval inTime;

sscanf(span.c_str(), "%d.%d", &sec, &usec);
inTime.tv_sec = sec;
inTime.tv_usec = usec;
Community
  • 1
  • 1
sabbahillel
  • 3,969
  • 1
  • 17
  • 34
0

You can use strtok_s to split a string based off a delimiter. In your case would be "."

#include <iostream>
#include <string>

int main()
{
    std::string span = "ssss.nnnn";
    char * span1 = (char *)span.c_str();
    char * pch = NULL;
    char * context;

    pch = strtok_s(span1, " .", &context);

    while (pch != NULL)
    {
        printf("%s\n", pch);
        pch = strtok_s(NULL, " .", &context);
    }

    return 0;
}

Output:

ssss

nnnn

user2970916
  • 1,056
  • 3
  • 13
  • 33