strtod with limited string length
Asked Answered
E

5

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!

Earwitness answered 7/5, 2013 at 6:46 Comment(0)
H
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;
}
Halidom answered 7/5, 2013 at 7:5 Comment(6)
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.0Earwitness
@alk You take first three characters of the string: "1234" => "123", then you convert it to a double "123" => 123.0.Earwitness
@Tyilo: Ok, I see ... - whatever the idea behind this is, you might like to see my answer for a possible solution to this.Envenom
@Tyilo: Welcome, but are you sure you accepted the right answer though... ;->>?Envenom
@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.Cupro
Yes, this (accepted!) answer is worse than the original question...Knapweed
C
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.

Cupro answered 14/7, 2015 at 21:58 Comment(3)
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.Rina
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.Cupro
Not that determining val count is important to OP's goal, but code could use " %n%3lf%n", &before, &val, &afterRina
H
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;
}
Halidom answered 7/5, 2013 at 7:5 Comment(6)
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.0Earwitness
@alk You take first three characters of the string: "1234" => "123", then you convert it to a double "123" => 123.0.Earwitness
@Tyilo: Ok, I see ... - whatever the idea behind this is, you might like to see my answer for a possible solution to this.Envenom
@Tyilo: Welcome, but are you sure you accepted the right answer though... ;->>?Envenom
@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.Cupro
Yes, this (accepted!) answer is worse than the original question...Knapweed
E
2

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;
}
Envenom answered 7/5, 2013 at 9:52 Comment(3)
What do the backticks do?Natality
@JL2210 which backticks?Envenom
@JL2210: Ah. They were a typo. Thank you for fixing this.Envenom
W
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.

Wheelsman answered 7/5, 2013 at 7:2 Comment(1)
I already knew that. I'm asking if there is another function, which provides the functionality I want.Earwitness
F
0

This is a simple implementation that i got working without malloc or strdup and other memory allocation functions whatsoever:

double strntod(const char* str, size_t len) {
    double d = 0.0, j = 0.0;
    
    size_t i = 0;
    for (; i < len; i++) {
        char c = str[i];
        if (c == '.') { j = 1.0; continue; }
        
        d = d * 10.0 + (double) (c - '0');
        if (j > 0.0) j *= 10.0;
    }
    return d / j;
}

Note that it assumes that all characters in str up to len are numbers or a dot, and that there is at most one dot in str.

Fulgent answered 14/9, 2023 at 16:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.