Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
Floating Point Constants in C
Decimal-point and exponent notation, f/L suffixes, precision limits and the embedded-systems cost of an innocent 0.5. Part of our free embedded systems course.
Not every number is whole. Voltages, temperatures, sensor scales and filter coefficients all need fractions, and in source code they are written as floating point constants in C. This lecture of our free embedded systems course explains the two ways to write them, the suffixes that choose their type, why 0.1 + 0.2 is not 0.3, and why one careless 1.5 can bloat firmware on a microcontroller without an FPU.
These ideas carry straight into the free Linux kernel development course, where floating point is deliberately forbidden in kernel code, and into the free embedded Linux course, where user-space sensor code relies on exactly the rules below.
What You Will Learn
By the end of this lecture you can:
- Recognise valid and invalid floating point constants in C, including forms like
5.,.5and1e3 - Convert any number to mantissa-and-exponent notation and back
- Pick the right suffix: none for double,
ffor float,Lfor long double - Explain why floating point values are approximations and compare them safely
- Avoid accidental double promotion on embedded targets
- Predict the result of mixed integer and floating point arithmetic
Prerequisites
Complete Lecture 1 on integer constants first, because we reuse the same compile-and-run workflow. You need gcc and a terminal. No maths beyond powers of ten is required.
What Counts as a Floating Point Constant in C?
A floating constant is a real number written in one of two shapes: with a decimal point (fixed notation), with an exponent (scientific notation), or both. The key point many tutorials miss is that a decimal point is not mandatory. 1e3 has no point at all, yet it is a floating constant equal to 1000.0 and has type double.
| Form | Examples | Notes |
|---|---|---|
| Fixed notation | 0.5, 4000.0, 0.0073 | Digits on both sides of the point |
| Point with one side empty | 5., .5 | Legal, but easy to misread, so avoid them |
| Exponent form | 1.8e6, 7.6e-6, 2E9 | Point optional; exponent must be an integer |
| Hexadecimal float (C99) | 0x1.8p1 | Exponent introduced by p is mandatory; this one equals 3.0 |
Rules the lexer enforces: no spaces or commas inside the constant, at least one digit in the number part, and an integer exponent (optionally signed) after e or E. So 1e, 1.2e3.4 and e5 are all invalid; the last one is just an identifier. As with integers, a leading minus is the negation operator, not part of the constant.
Exponential Notation for Floating Point Constants
Very large and very small numbers become unreadable in fixed notation, so scientific notation splits a value into a mantissa (also called the significand) and an exponent of ten. Think of the exponent as the number of places the decimal point moves: positive moves it right, negative moves it left.
Negative exponents move the point left: 7.6e-6 means 0.0000076.
| Plain number | Scientific form | C constant |
|---|---|---|
| 602000000000000000000000 | 6.02 × 1023 | 6.02e23 |
| 0.00042 | 4.2 × 10-4 | 4.2e-4 |
| -9000 | -9 × 103 | -9e3 |
| 1250000 | 1.25 × 106 | 1.25e6 |
Floating Point Constants in C: Types and Suffixes
Unlike integers, floating constants have a simple rule: with no suffix the type is double. Add f or F for float, or l or L for long double. As with integer constants, prefer the upper-case L.
| Constant | Type | Typical size (x86-64 Linux) | Approx. decimal digits |
|---|---|---|---|
3.52f | float | 4 bytes | 6-7 |
3.52 | double | 8 bytes | 15-16 |
3.52L | long double | 16 bytes (80-bit extended in storage) | 18-19 |
Underneath, float and double follow the IEEE 754 binary format: one sign bit, a block of exponent bits, and a block of fraction bits. The more fraction bits, the more digits you can trust.
Fractions in base ten, like 0.1, usually have no exact binary form, which is why stored values are slightly off.
Why 0.1 cannot be stored exactly
Just as one third is 0.3333… forever in decimal, one tenth is an endless repeating pattern in binary. The hardware keeps the nearest representable value, and Example 2 prints that hidden error.
Hands-On: Four Programs on Floating Point Constants
Example 1: Reading exponent notation
Goal
Print several constants in fixed and scientific format to connect the notation with what appears on screen.
Save this as float_forms.c:
#include <stdio.h>
int main(void)
{
printf("1.8e6 as %%f : %f\n", 1.8e6);
printf("7.6e-6 as %%f : %f\n", 7.6e-6);
printf("7.6e-6 as %%e : %e\n", 7.6e-6);
printf("-6.7E5 as %%.1f : %.1f\n", -6.7E5);
printf("2.5e9 as %%g : %g\n", 2.5e9);
printf("0.0073 as %%g : %g\n", 0.0073);
printf("5. and .5 give %.1f and %.1f\n", 5., .5);
return 0;
}
Build and run:
$ gcc -Wall -Wextra -std=c11 -o float_forms float_forms.c
$ ./float_forms
Interactive: Predict the output first, then click to check yourself.
Expected output:
1.8e6 as %f : 1800000.000000
7.6e-6 as %f : 0.000008
7.6e-6 as %e : 7.600000e-06
-6.7E5 as %.1f : -670000.0
2.5e9 as %g : 2.5e+09
0.0073 as %g : 0.0073
5. and .5 give 5.0 and 0.5
%fprints fixed notation with six decimals by default, so 7.6e-6 rounds to0.000008. Use%eto see the exponent form.%gchooses the shorter of the two forms, which is handy for logging.- The last line proves that
5.and.5are legal, even though they look odd.
Example 2: Suffixes and hidden precision
Goal
Compare sizes of float, double and long double constants, then print the stored value of 0.1 in both float and double.
Save this as float_types.c:
#include <stdio.h>
int main(void)
{
printf("sizeof(3.52f) = %zu\n", sizeof(3.52f));
printf("sizeof(3.52) = %zu\n", sizeof(3.52));
printf("sizeof(3.52L) = %zu\n", sizeof(3.52L));
printf("0.1f stored as %.20f\n", 0.1f);
printf("0.1 stored as %.20f\n", 0.1);
return 0;
}
Build and run:
$ gcc -Wall -Wextra -std=c11 -o float_types float_types.c
$ ./float_types
Interactive: Predict the output first, then click to check yourself.
Expected output:
sizeof(3.52f) = 4
sizeof(3.52) = 8
sizeof(3.52L) = 16
0.1f stored as 0.10000000149011611938
0.1 stored as 0.10000000000000000555
The first three lines confirm the size table. The last two are the revealing ones: 0.1f is stored as 0.100000001490116…, while 0.1 as a double is 0.100000000000000005551…. Both are close, neither is exact, and the float error is about a billion times larger. When you need more than seven reliable digits, a float is simply the wrong tool.
Example 3: Never compare with ==
Goal
Show that 0.1 + 0.2 is not equal to 0.3 and fix the comparison with a tolerance.
Save this as float_compare.c:
#include <stdio.h>
#include <math.h>
int main(void)
{
double x = 0.1 + 0.2;
printf("x == 0.3 ? %s\n", (x == 0.3) ? "yes" : "no");
printf("x - 0.3 = %.17g\n", x - 0.3);
if (fabs(x - 0.3) < 1e-9)
printf("close enough: treated as equal\n");
return 0;
}
Build and run:
$ gcc -Wall -Wextra -std=c11 -o float_compare float_compare.c
$ ./float_compare
Interactive: Predict the output first, then click to check yourself.
Expected output:
x == 0.3 ? no
x - 0.3 = 5.5511151231257827e-17
close enough: treated as equal
The two stored errors add up to a tiny difference of about 5.55e-17, enough to make == fail. The remedy is to ask “are they close enough?” with fabs(a - b) < tolerance. Pick a tolerance that matches your data: 1e-9 is fine for values near 1, but for large magnitudes use a relative tolerance such as DBL_EPSILON scaled by the values.
Example 4: Integer versus real division
Goal
See how mixing integer and floating point constants changes results.
Save this as mixed_math.c:
#include <stdio.h>
int main(void)
{
printf("5 / 2 = %d\n", 5 / 2);
printf("5 / 2.0 = %f\n", 5 / 2.0);
printf("5.0f / 2 = %f\n", 5.0f / 2);
printf("7 / 2 * 2.0 = %f\n", 7 / 2 * 2.0);
printf("7 / 2.0 * 2 = %f\n", 7 / 2.0 * 2);
return 0;
}
Build and run:
$ gcc -Wall -Wextra -std=c11 -o mixed_math mixed_math.c
$ ./mixed_math
Interactive: Predict the output first, then click to check yourself.
Expected output:
5 / 2 = 2
5 / 2.0 = 2.500000
5.0f / 2 = 2.500000
7 / 2 * 2.0 = 6.000000
7 / 2.0 * 2 = 7.000000
5 / 2has two integer operands, so the division is done on integers and the fraction is thrown away: 2.- As soon as one operand is floating, the other is converted and you get 2.5.
7 / 2 * 2.0evaluates left to right: 7/2 is 3 (integer), then 3 * 2.0 is 6.0. Reordering to7 / 2.0 * 2gives 7.0.- Interview tip: always trace the type of each sub-expression, not just the final result.
Floating Point Constants in Embedded Systems
This is where theory meets product code. Many microcontrollers have no floating point unit at all, and popular ones such as the Cortex-M4F have a unit that handles only single precision. Because an unsuffixed constant is a double, an expression like speed * 1.5 forces the float variable to be promoted to double, the multiplication to run in double, and the result to be converted back. On a part with only a single-precision FPU, that double arithmetic is done by slow software routines and pulls extra library code into flash.
| Code | What the compiler does | Better |
|---|---|---|
float y = x * 1.5; | Promotes x to double, multiplies in double, narrows to float | float y = x * 1.5f; |
float t = 0.1; | Creates a double constant, then converts | float t = 0.1f; |
printf("%f", x) with a float | Float is promoted to double for variadic functions | Expected and unavoidable; keep printing out of hot loops |
Useful GCC flags
Compile with -Wdouble-promotion to be warned about silent float-to-double promotion, and consider -fsingle-precision-constant when a whole project is single-precision.
On the Linux kernel side the rule is even stricter: kernel code must not use floating point at all, because the kernel does not save and restore FPU registers on every entry. Drivers that need fractions use scaled integers, for example storing millivolts in an integer instead of volts in a float.
Common Mistakes and Troubleshooting
| Mistake | What happens | Fix |
|---|---|---|
Comparing floats with == | Rounding errors make equal-looking values differ | Compare with a tolerance |
Forgetting f on float constants | Silent promotion to double, slower and larger on small MCUs | Write 1.5f |
float x = 16777217; | Float cannot represent every integer above 224, so the value rounds | Use double or a 32-bit integer |
| Dividing two ints and expecting a fraction | Integer division truncates | Make one operand floating: 5 / 2.0 |
Writing 1e or e5 | Compile error or an undeclared identifier | Exponent needs digits and a mantissa in front |
Using %f with a long double | Garbage output | Use %Lf |
Best Practices, Performance and Security
- Default to double on desktops and Linux, float on small MCUs. Choose deliberately and suffix every constant to match.
- Prefer scaled integers for sensor maths in tiny or real-time systems; they are exact and fast.
- Never use floats for money. Use integer cents so that sums are exact.
- Performance: constant expressions such as
3.14159f / 180.0fare evaluated at compile time, so keep them readable instead of pre-computing digits by hand. - Security and safety: NaN and infinity from divide-by-zero or overflow can propagate silently through control loops. Validate sensor inputs and check with
isfinite()before using a result.
Quiz: Floating Point Constants in C
Try each question on paper first. Click a question to reveal the answer.
Q1. Is 1e3 a floating constant even though it has no decimal point?
Answer: Yes. The exponent alone makes it floating, with type double and value 1000.0.
Q2. What is the type of 2.4e-9, of 2.4e-9f and of 2.4e-9L?
Answer: double, float and long double respectively.
Q3. Write 0.00042 and 602000000000000000000000 in C exponent form.
Answer: 4.2e-4 and 6.02e23.
Q4. Which of these is invalid: .5, 5., 1e, 2E9?
Answer: 1e is invalid because the exponent has no digits. The other three are legal.
Q5. What does printf("%f", 7.6e-6) print?
Answer: 0.000008, because %f keeps six digits after the point and rounds.
Q6. What does 7 / 2 * 2.0 evaluate to?
Answer: 6.0. The division 7 / 2 is done in integers (3), then multiplied by 2.0.
Q7. Why is 0.1 + 0.2 == 0.3 false?
Answer: 0.1 and 0.2 have no exact binary representation. Their stored approximations add to a value that differs from the stored 0.3 by about 5.55e-17.
Q8. What is sizeof(3.52f), sizeof(3.52) and sizeof(3.52L) on x86-64 Linux?
Answer: 4, 8 and 16 bytes.
Interview Questions: Floating Point Constants in C
These come up in embedded and product-company interviews. Answer out loud, then click.
Q1. Why should you write 0.5f instead of 0.5 on a Cortex-M4F?
Answer: Because 0.5 is a double. The multiplication would be promoted to double, which the single-precision FPU cannot do, so the compiler calls slow software routines and adds code size.
Q2. How do you safely compare two floating point numbers?
Answer: Check that the absolute difference is below a tolerance, or use a relative tolerance based on the magnitudes, rather than using ==.
Q3. Can float hold every 32-bit integer exactly?
Answer: No. A float has a 24-bit significand, so integers above 16777216 may round. Double can hold every 32-bit integer exactly.
Q4. Why does the Linux kernel avoid floating point?
Answer: The kernel does not save and restore FPU state on every entry, and doing so would slow system calls and interrupts. Drivers use integer or fixed-point maths instead.
Q5. What is the result of int x = 3.99;?
Answer: 3. Conversion from floating to integer truncates toward zero; it does not round.
Q6. What is mantissa and exponent?
Answer: A number is stored as significand times a power of ten (or two in hardware). The significand carries the digits, the exponent places the point, and together they give a huge range with limited precision.
Summary and Key Takeaways
- Floating point constants in C have a point, an exponent, or both; a point is not required if an exponent is present.
- No suffix means double,
fmeans float,Lmeans long double. - The exponent after
eorEmust be an integer and moves the decimal point. - Floating values are approximations; never test them with
==. - Integer division discards the fraction, so check operand types in mixed expressions.
- On small embedded targets, suffix every float constant with
fto avoid costly double promotion.
Conclusion
Floating point constants in C are easy to type and surprisingly easy to misuse. You now know the legal forms, how mantissa and exponent work, what each suffix does, and why stored values are approximations. You also saw the practical side: a missing f can cost real performance in firmware, and comparing with == will eventually fail a test.
Next up are character and string constants, where a quoted letter turns out to be an integer and a quoted word hides an invisible terminator. Experiment first: try %.10f with other fractions like 0.7 and 0.25 and notice which ones are exact.
Frequently Asked Questions
1. Is this free embedded systems course suitable for beginners?
Answer: Yes. Each lecture starts from first principles, then adds interview-level depth, and every example can be compiled with plain gcc.
2. Do I need an FPU to run these examples?
Answer: No. These floating point constants in C run on any desktop Linux machine. The FPU discussion only matters when you port the code to a microcontroller.
3. Why is the default type of a floating constant double?
Answer: The C language chose double as the default real type for accuracy on general-purpose machines. Use the f suffix to opt into float.
4. What is the difference between %f, %e and %g?
Answer: %f prints fixed notation, %e prints scientific notation, and %g chooses the shorter of the two.
5. Why does 0.1f print as 0.10000000149?
Answer: A float stores the nearest 24-bit binary value to 0.1, which is slightly above it. Printing more digits reveals the error.
6. Can I use floats inside a Linux kernel driver?
Answer: Not in normal kernel code. Use integers or fixed-point arithmetic. This is covered in our free Linux device drivers course.
7. When should I use long double?
Answer: Rarely: only for scientific code needing extra precision. It is slower, bigger and not portable in size.
Next: Characters and Strings
Continue this free embedded systems course with character and string constants, then keep going toward the free Linux kernel development course. Try the examples with your own numbers first.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments