What are Floating Point Constants in C- Free Embedded C Programming Course

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

Elements of C · Lecture 2

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.

4
Runnable programs
8
Quiz questions
6
Interview questions
Free
Forever

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., .5 and 1e3
  • Convert any number to mantissa-and-exponent notation and back
  • Pick the right suffix: none for double, f for float, L for 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.

FormExamplesNotes
Fixed notation0.5, 4000.0, 0.0073Digits on both sides of the point
Point with one side empty5., .5Legal, but easy to misread, so avoid them
Exponent form1.8e6, 7.6e-6, 2E9Point optional; exponent must be an integer
Hexadecimal float (C99)0x1.8p1Exponent 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.

Reading 1.8e6 step by step
Mantissa1.8
▼
Letter e means times ten to the powere
▼
Exponent6
▼
Move the point 6 places right1.8 becomes 1800000

Negative exponents move the point left: 7.6e-6 means 0.0000076.

Plain numberScientific formC constant
6020000000000000000000006.02 × 10236.02e23
0.000424.2 × 10-44.2e-4
-9000-9 × 103-9e3
12500001.25 × 1061.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.

ConstantTypeTypical size (x86-64 Linux)Approx. decimal digits
3.52ffloat4 bytes6-7
3.52double8 bytes15-16
3.52Llong double16 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.

IEEE 754 layout: where the bits go
float (32 bits)1 sign bit8 exponent bits23 fraction bits
double (64 bits)1 sign bit11 exponent bits52 fraction bits

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
  • %f prints fixed notation with six decimals by default, so 7.6e-6 rounds to 0.000008. Use %e to see the exponent form.
  • %g chooses the shorter of the two forms, which is handy for logging.
  • The last line proves that 5. and .5 are 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 / 2 has 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.0 evaluates left to right: 7/2 is 3 (integer), then 3 * 2.0 is 6.0. Reordering to 7 / 2.0 * 2 gives 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.

CodeWhat the compiler doesBetter
float y = x * 1.5;Promotes x to double, multiplies in double, narrows to floatfloat y = x * 1.5f;
float t = 0.1;Creates a double constant, then convertsfloat t = 0.1f;
printf("%f", x) with a floatFloat is promoted to double for variadic functionsExpected 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

MistakeWhat happensFix
Comparing floats with ==Rounding errors make equal-looking values differCompare with a tolerance
Forgetting f on float constantsSilent promotion to double, slower and larger on small MCUsWrite 1.5f
float x = 16777217;Float cannot represent every integer above 224, so the value roundsUse double or a 32-bit integer
Dividing two ints and expecting a fractionInteger division truncatesMake one operand floating: 5 / 2.0
Writing 1e or e5Compile error or an undeclared identifierExponent needs digits and a mantissa in front
Using %f with a long doubleGarbage outputUse %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.0f are 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, f means float, L means long double.
  • The exponent after e or E must 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 f to 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

Leave a Reply

Your email address will not be published. Required fields are marked *