C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
87: Numeric Conversion: atoi, atof, strtol, strtod
When you're reading data from a file or getting input from a user, it always comes to you as a string. But you can't do math on a string. You need a number. C gives us a few ways to handle this, but there's a huge divide between the "quick and dirty" functions and the professional-grade ones. I want to show you why that divide exists by building a simple parser for a game character's save-state string.
The temptation of atoi
Let's say we have a string representing a player's level and their gold: "Level: 42, Gold: 1500". To keep this example focused, I'll assume I've already split the string and I'm left with just the numeric parts: "42" and "1500". My first instinct, especially early in my career, was always to use atoi (ASCII to Integer) because it's a one-liner.
char *level_str = "42";
int level = atoi(level_str);
printf("Player level is %d\n", level);
It's clean, it's fast, and it works... as long as the input is perfect. But in the real world, input is never perfect.
Where atoi fails us
Here is where I messed up in a project a few years back. I was using atoi to parse a configuration file. I had a setting for max_players, and one of my users accidentally typed "twenty" instead of "20". I expected the program to crash or tell me there was an error. Instead, atoi just returned 0.
That's the danger. If atoi returns 0, you have no way of knowing if the input was actually the character '0' or if the input was complete gibberish. Even worse, if the number is too large to fit in an integer, the behavior is undefined. In a production environment, "undefined" is a word that keeps engineers awake at night.
Taking control with strtol and strtod
To do this properly, we use strtol (string to long) and strtod (string to double). These functions are slightly more verbose, but they give us a pointer (usually called endptr) that tells us exactly where the conversion stopped. If the pointer still points to the start of the string, we know the conversion failed.
Let's rewrite our player parser using the robust approach. I'll handle the level (integer) and the player's experience multiplier (floating point).
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
void parse_stats(const char *lvl_s, const char *mult_s) {
char *endptr;
// Convert level
errno = 0; // Reset errno to catch overflows
long level = strtol(lvl_s, &endptr, 10);
if (lvl_s == endptr) {
printf("Error: Level input was not a number.\n");
} else if (errno == ERANGE) {
printf("Error: Level value out of range.\n");
} else {
printf("Level set to: %ld\n", level);
}
// Convert multiplier
double multiplier = strtod(mult_s, &endptr);
if (mult_s == endptr) {
printf("Error: Multiplier input was not a number.\n");
} else {
printf("Multiplier set to: %.2f\n", multiplier);
}
}
int main() {
// Test with a mix of good and bad data
parse_stats("42", "1.5"); // Success
parse_stats("abc", "2.0"); // Level fails
parse_stats("10", "xyz"); // Multiplier fails
return 0;
}
Notice how strtol takes a third argument: the base. I used 10 for decimal, but if you were parsing hex values from a memory dump, you'd use 16. strtod doesn't need a base because floating-point numbers are standardized. By checking if (lvl_s == endptr), we've completely eliminated the "silent zero" problem that atoi creates.
📋 Practical Task
Build a Robust Expense Entry Validator
Write a program that prompts a user to enter a spending amount (a decimal value) and a category ID (an integer). Your program must use strtol and strtod to validate the input.
- If the user enters non-numeric text for either value, print a specific error message explaining which field was invalid.
- If the user enters a number that is too large to be stored in a
long, detect this usingerrno` andERANGEand print an "Overflow" error. - If both inputs are valid, print a summary: "Expense of $[amount] recorded under Category #[id]".
Avoid using atoi or atof entirely.
There are no comments for now.