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
105: Exponential and Logarithmic Functions
When you first encounter growth or decay problems in C, your instinct might be to reach for a for loop. If you're calculating compound interest over five years or the population growth of a colony over ten generations, a loop is intuitive. You multiply by a growth factor, store the result, and repeat. It works, and it's easy to debug.
The loop trap and the precision gap
The problem starts when your "time" isn't a whole number. If you need to know the value of an investment at exactly 4.72 years, or the remaining mass of a radioactive isotope after 12.5 days, a loop is useless. You can't iterate "0.72 times."
The naive approach here is often to try and approximate the math using a very small step size in a loop—say, iterating by 0.001 increments. I've seen this in production code before, and it's a nightmare. Not only is it computationally expensive, but you're introducing floating-point accumulation errors at every single step. By the time you reach your target, your answer has drifted because of how double precision works. This is where you stop trying to "simulate" the math and start using the math.h library.
#include <math.h>
// The "simulation" way (Wrong/Naive)
double naive_growth(double initial, double rate, double time) {
double result = initial;
for (double t = 0; t < time; t += 0.001) {
result += result * (rate * 0.001);
}
return result;
}
// The professional way
double professional_growth(double initial, double rate, double time) {
return initial * exp(rate * time);
}
Why pow() isn't always your best friend
Once you move to math.h, the most common mistake I see is the over-reliance on pow(). pow(base, exp) is a Swiss Army knife; it's designed to handle everything from negative bases to fractional exponents. Because it's so general, it's relatively slow.
If you are calculating e raised to a power, never use pow(M_E, x). Use exp(x). It is specifically optimized for the natural base and is significantly faster and more accurate. Similarly, if you just need to square a number, x * x beats pow(x, 2) every single time. I usually tell my juniors: only use pow() when the exponent is a variable that could be anything. If it's a constant or a specific base like e, there's almost always a better tool.
Inverting the growth with logarithms
The real power of this library comes when you need to work backward. Suppose you know the starting amount and the current amount, and you need to find out how much time has passed. In algebra, this is where you'd use a logarithm to "get the variable out of the exponent."
In C, log() is the natural logarithm (base e), not base 10. This trips people up constantly. If your formula requires a common log (base 10), you use log10(). If you're working with the natural growth constant, stick to log().
The trade-off here is mainly about domain errors. Logarithmic functions are only defined for positive numbers. If you pass a zero or a negative number into log(), you'll get a NaN (Not a Number) or a pole error. Unlike a loop that just gives you a weird number, math.h functions will fail loudly if you aren't validating your inputs. Always check that your value is greater than zero before passing it to a log function.
📋 Practical Task
Exercise: Carbon-14 Decay Dating Calculator
You are tasked with writing a program that calculates the age of an organic sample based on its remaining Carbon-14. The formula for the age of a sample is:
t = [ln(N/N0) / -k]
tis the age of the sample.Nis the remaining amount of C-14.N0is the initial amount (100%).kis the decay constant for C-14 (approximately 0.00012097 per year).lnis the natural logarithm.
Requirements:
- Include
<math.h>and link with-lm. - Prompt the user to enter the percentage of Carbon-14 remaining (e.g., 50.0 for 50%).
- Validate that the input is between 0 and 100 (exclusive). If the user enters 0 or a negative number, print an error message explaining that logarithms cannot process non-positive values.
- Calculate the age of the sample and print it as a double with two decimal places.
There are no comments for now.