Skip to Content
Course content

87: Numeric Conversion: atoi, atof, strtol, strtod

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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 using errno` and ERANGE and 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.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.