4

If I want to parse the first 3 characters from the char array as a double, ignoring the following characters, do I really need to do this?

int main() {
    const char a[] = "1.23";
    char *b = malloc(sizeof(char) * 4);

    memcpy(b, a, sizeof(char) * 3);
    b[3] = '\0';

    printf("%f\n", strtod(b, NULL)); // Prints 1.20000, which is what I want

    free(b);
}

Isn't there a function like strtod that allows you to specify the maximum string length it should be searching for digits?

Edit: I want it to print 1.2 (which it currently does), not 1.23!

Tyilo
  • 28,998
  • 40
  • 113
  • 198

4 Answers4

6

While strtod() doesn't allow you to limit the string length, you can use sscanf() with a maximum field width and an optional check for the number of characters consumed, like so:

#include <stdio.h>

double parseDouble(const char *str){
    double val = 0;
    int numCharsRead;

    // Handle errors by setting or returning an error flag.
    if(sscanf(str, "%3lf%n", &val, &numCharsRead) != 1){
        puts("Failed to parse double!");
    }
    else if(numCharsRead != 3){
        puts("Read less than three characters!");
    }

    return val;
}

int main(){
    printf("%lf\n", parseDouble("1.3")); // 1.300000
    printf("%lf\n", parseDouble("1.5999")); // 1.500000
    printf("%lf\n", parseDouble(".391")); // 0.390000
    printf("%lf\n", parseDouble(".3")); // Read less than three characters!\n0.300000
    return 0;
}

sscanf(str, "%3lf%n", &val, &numCharsRead is the important part: you specify a maximum width of 3, which means that sscanf() will read up to 3 characters for that particular field, and also store the number of characters consumed by the end of the parse in numCharsRead. You can then check that value if you care about reading exactly 3 characters every time; if you're fine with 3 or less, you can just use sscanf(str, "%3lf", &val). For reference, here's the documentation for width specifiers:

An optional decimal integer which specifies the maximum field width. Reading of characters stops either when this maximum is reached or when a nonmatching character is found, whichever happens first. Most conversions discard ini‐ tial white space characters (the exceptions are noted below), and these discarded characters don't count toward the maximum field width. String input conversions store a terminating null byte ('\0') to mark the end of the input; the maximum field width does not include this terminator.

sevko
  • 1,402
  • 1
  • 18
  • 37
  • Nice, except 2 points. 1) With `str == " 1.3"`, --> `numCharsRead == 4` and `val == 123`. The `3` in `"%3lf%n"` limits up to 3 the number of charters scanned _and used in the conversion_. `%n` reports the number scanned, used in conversions or not. 2) `val` is not defined on the fail path. Try `printf("%lf\n", parseDouble("xyz"));` first. – chux - Reinstate Monica Jul 14 '15 at 22:22
  • Yeah, the first one's a very good point. I didn't bring it up but it caught my eye in the documentation; the only solution there is to probably manually skip over any whitespace in the string. As for `val` being undefined, you're right, but that was just meant to be a simple example. In reality you'd probably want to structure that function differently, setting or returning an error flag if it fails to parse. – sevko Jul 14 '15 at 23:06
  • 1
    Not that determining `val` count is important to OP's goal, but code could use `" %n%3lf%n", &before, &val, &after` – chux - Reinstate Monica Jul 15 '15 at 01:12
3

If you always want to only consider the three first characters from a given string, you can use the following code:

#include <stdio.h>
#include <string.h>

double parse_double(const char *str) {
  char *tmp = 0;
  double result = 0;

  asprintf(&tmp, "%.3s", str);
  result = strtod(tmp, 0);
  free(tmp);

  return result;
}

int main(void) {
  printf("%f\n", parse_double("1.23")); // 1.2
  printf("%f\n", parse_double("1234")); // 123
  printf("%f\n", parse_double("0.09")); // 0.0

  return 0;
}
aymericbeaumet
  • 6,853
  • 2
  • 37
  • 50
  • I don't have a desired precision, I want it to be limited by string size, so `"1.23" => 1.2`, `"1234" => 123.0` & `"0.09" => 0.0` – Tyilo May 07 '13 at 07:11
  • @alk You take first three **characters** of the string: `"1234" => "123"`, then you convert it to a double `"123" => 123.0`. – Tyilo May 07 '13 at 10:51
  • @Tyilo: Ok, I see ... - whatever the idea behind this is, you might like to see my answer for a possible solution to this. – alk May 07 '13 at 11:05
  • @Tyilo: Welcome, but are you sure you accepted the right answer though... ;->>? – alk May 07 '13 at 11:13
  • 2
    @Tyilo, this is just a roundabout way of doing what you showed in the code in your question, except `asprintf()` isn't very portable and `memcpy()` is better suited for just copying bytes around. The right way to go is `sscanf()`, which I added an answer about. – sevko Jul 14 '15 at 22:02
  • Yes, this (accepted!) answer is worse than the original question... – linuxfan says Reinstate Monica Jun 04 '23 at 05:09
1

The signature of strtod is like this

   double strtod(const char *nptr, char **endptr);

The function will return the initial portion of the string pointed to by nptr. If endptr is not NULL, a pointer to the character after the last character used in the conversion is stored in the location referenced by endptr.

So it does not let you specify the number of characters that need to be converted. Hence you have to modify your input itself and pass it to strtod.

GeekFactory
  • 399
  • 2
  • 13
  • I already knew that. I'm asking if there is another function, which provides the functionality I want. – Tyilo May 07 '13 at 07:03
1

No, there isn't such a function in the standard library.

But it's fun to roll one's own:

/*
 * Same as strtod() but only takes the first n characters into account.
 * Additionally returns 0. and sets errno to EINVAL if 'nptr' is NULL.
 */
double strntod(const char *nptr, char **endptr, size_t n)
{
  double result;

  /* perform input validation */
  if (!nptr)
  {
    errno = EINVAL;

    result = 0.;
    if (endptr)
    {
      *endptr = nptr;
    }

    goto lblExit;
  }

  if (strlen(nptr) <= n)
  {
    /* Nothing to truncate: fall back to standard 'strtod()' */        
    result = strtod(nptr, endptr);
  }
  else
  {
    /* create working copy of string */
    char * ptmp = strdup(nptr);

    /* Test whether 'strdup()' failed */
    if (!ptmp)
    {
      result = 0.;
      if (endptr)
      {
        *endptr = nptr;
      }

      goto lblExit;
    }        

    /* truncate working copy to n characters */
    ptmp[n] = '\0'; 

    /* do original 'strtod()' on truncated working copy */
    result = strtod(ptmp, endptr);

    /* adjust '*endptr' to point to original character array, but to working copy */
    if (endptr)
    {
      *endptr = nptr + (*endptr - ptmp); 
    }

    /* free working copy */
    free(ptmp);
  }

  lblExit:

  return result;
}
S.S. Anne
  • 15,171
  • 8
  • 38
  • 76
alk
  • 69,737
  • 10
  • 105
  • 255