2

I'm having a very specific problem and haven't found a solution elsewhere. I'm working on a small project and i want to make it more robust by allowing users to input prices with both a comma or dot. So i made a little function that allows me to do that:

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

int main () {
    setlocale(LC_ALL, "Portuguese");
    float val;
    char str[20];

    scanf("%s", str);
    for (int i = 0; str[i] != '\0'; ++i)
        if (str[i] == ',') 
            str[i] = '.';
        val = atof(str);
    printf("String value = %s, Float value = %f\n", str, val);
    return(0);
}

This would work as intended, were I not Portuguese. Since we mostly use a comma in decimal numbers, using the atof function doesn't work because it converts to floats with a dot, and then when I try to printf the float it will show 0.0 but if you delete the line setlocale(LC_ALL, "Portuguese"); it will work just fine. Any ideas?

dbc
  • 104,963
  • 20
  • 228
  • 340
Real
  • 91
  • 6

2 Answers2

3

There's two problems:

  • If you encounter the decimal point which is not the correct one for the current locale, then and only then, change it.
  • You are using atof which is an unsafe function that should never be used - it has no error handling. Use strtof instead.

You can use the standard function localeconv to get all kind of useful info about the current locale.

Example:

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

int main (void) 
{
  setlocale(LC_ALL, "Portuguese");
  char ok_decimal_point  = localeconv()->decimal_point[0];
  char bad_decimal_point = (ok_decimal_point=='.') ? ',' : '.';

  float val;
  char str[20] = "123.456";

  for (int i = 0; str[i] != '\0'; ++i)
  {
    if (str[i] == bad_decimal_point)
    {
      str[i] = ok_decimal_point;
    }
  }

  val = strtof(str, NULL);
  printf("String value = %s, Float value = %f\n", str, val);
  return(0);
}

(Although, coming from another country that uses ,, I prefer to educate users to use the . form instead, since this is more of an international standard. Having two different standards for decimal points around the world isn't helping mankind. Those with the least used version should adapt.)

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • This does work. But out of curiosity, why is `atof` unsafe? For example, if it receives a string that it cannot convert, it simply returns 0.0, or are there other issues aswell? – Real Apr 12 '18 at 15:22
  • @Real If it receives a string it cannot convert, the behavior is undefined. It might return 0, it might return something else, it might crash the program. The `strto...` functions don't have this problem, so they are always preferred. – Lundin Apr 13 '18 at 06:31
2

Your code works as expected:

The for loop transforms all , into . and therefore atof fails converting a number with . as decimal point because you have called setlocale(LC_ALL, "Portuguese"); beforehand.

You need this:

if (str[i] == '.') str[i] = ',';

instead of this:

if (str[i] == ',') str[i] = '.';

This sample makes it clear:

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>

int main() {

  float val;

  // converting with default locale
  char str[20] = "1.234";
  val = atof(str);
  printf("Default locale: String value = %s, Float value = %f\n", str, val);

  // converting with Portugese locale    
  setlocale(LC_ALL, "Portuguese");

  char strport[20] = "1,234";
  val = atof(strport);
  printf("Portugese locale: String value = %s, Float value = %f\n", strport, val);
  return(0);
}
Jabberwocky
  • 48,281
  • 17
  • 65
  • 115
  • I actually feel so dumb right now, basically just move the setlocale to after the conversion and it works....yikes, makes sense. Thanks for the help mate, i was about to rip some hair off – Real Apr 12 '18 at 14:55
  • "`if (str[i] == '.') str[i] = ',';`" Until changing locale again. Better to write fully portable code. – Lundin Apr 12 '18 at 15:02