You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: documents/en/vol1-fundamentals/c_tutorials/02B-float-char-const-cast.md
+46-33Lines changed: 46 additions & 33 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -69,55 +69,68 @@ long double ld = 3.14L; // 后缀 L 表示 long double
69
69
70
70
### Floating-point numbers are imprecise — this is not a bug
71
71
72
-
The most important concept to understand about floating-point numbers is this: **floating-point numbers are approximations, not exact values**. This is because computers use a finite number of binary bits to represent decimal fractions, just as you can only represent 1/3 using a finite number of decimal places — it will always be an approximation.
72
+
To understand floating-point numbers, accept one fact first: **they are approximations, not exact values**. It feels counterintuitive — how can the `0.1`you store not equal `0.1`? The answer is hiding inside the binary structure of a floating-point number.
73
73
74
-
```c
75
-
#include<stdio.h>
74
+
Start with integers as a warm-up. An `int` has only 32 binary bits, so the numbers it can represent have a fixed ceiling and floor; go past them and you overflow. Yet there are infinitely many integers, and the computer just picks one finite segment to represent. Floating-point numbers face the exact same problem: there are infinitely many real numbers, many with infinitely many fractional digits, and a `float` still only gets 32 bits. How does it cover such a vast range with so few bits? The same way you would on paper — scientific notation.
76
75
77
-
intmain(void)
78
-
{
79
-
float a = 0.1f;
80
-
float b = 0.2f;
81
-
if (a + b == 0.3f) {
82
-
printf("equal\n");
83
-
} else {
84
-
printf("not equal: %.9f\n", a + b);
85
-
}
86
-
return 0;
87
-
}
88
-
```
76
+
Decimal scientific notation writes `0.0123` as `1.23 × 10⁻²`, a mantissa times a power of 10. A computer does the same thing, only with base 2: `number = (±1) × mantissa × 2^exponent`. The 32 bits of a `float` are split into exactly these three parts:
89
77
90
-
Let's verify this by compiling and running the code:
The exponent bits control magnitude, the mantissa bits control fine detail, and both are finite. The exponent lets you reach as high as `10³⁸` and as low as `10⁻⁴⁵`; but with only 23 mantissa bits, you get roughly 7 decimal digits of precision — anything beyond that cannot be stored.
95
83
96
-
**Output:**
84
+
The real trouble lives in those 23 mantissa bits. The decimal `0.1`, written in binary, is `0.0001100110011...` — an infinitely repeating fraction. The mantissa field has only 23 bits, so it cannot hold the infinite repetition and must truncate. What gets stored is no longer `0.1`. The little program below prints out the raw 32 bits of `0.1f`; click "Try it yourself" to run it directly:
85
+
86
+
<OnlineCompilerDemo
87
+
title="What 0.1 actually looks like inside a float"
description="Print the 32 bits of 0.1f (sign | exponent | mantissa), then see why 0.1 + 0.2 does not equal 0.3 under double."
90
+
allow-run
91
+
run-compiler="cg132"
92
+
run-options="-O2 -std=c17"
93
+
/>
94
+
95
+
The key lines of output look like this:
97
96
98
97
```text
99
-
not equal: 0.300000012
98
+
32 bits of 0.1f in a float (sign | exponent | mantissa):
99
+
0 01111011 10011001100110011001101
100
+
101
+
Printed at different precisions — never exactly 0.1:
102
+
9 digits : 0.100000001
103
+
20 digits: 0.10000000149011611938
104
+
105
+
double: 0.1 + 0.2 == 0.3 ? no
106
+
a + b = 0.30000000000000004441
107
+
c = 0.29999999999999998890
100
108
```
101
109
102
-
See? `0.1 + 0.2` does not equal `0.3` in floating-point arithmetic. This is not a compiler bug; it is an inherent characteristic of the IEEE 754 floating-point standard. Therefore, **never use `==` to compare floating-point numbers**. The correct approach is to use a small epsilon value to determine "approximate equality":
110
+
Look at the first line: the sign bit is `0` (positive), the exponent is `01111011`, and the mantissa is `10011001100110011001101` — that run of `1001 1001 1001...` is the infinite repetition truncated to 23 bits, with the final `1` left over from rounding. That is why `0.1f` prints as `0.10000000149011611938`, not `0.1`.
111
+
112
+
The `double` part is even clearer: `0.1 + 0.2` comes out to `0.30000000000000004441`, while `0.3` stored is `0.29999999999999998890`. Two numbers that were never equal to begin with, compared with `==`, will of course not be equal. There is no mystery here: a finite mantissa cannot hold an infinitely repeating fraction, so this is inevitable.
113
+
114
+
::: warning
115
+
Never compare floating-point numbers with `==`. `0.1 + 0.2 != 0.3` is the norm in floating-point arithmetic, not a bug. Checking for "approximate equality" with an epsilon is the correct fix.
116
+
:::
117
+
118
+
One related trap: under `float`, `0.1f + 0.2f` happens to equal `0.3f`. Don't conclude that `float` is somehow more "accurate" — the 23-bit mantissa is simply too coarse, so the error gets rounded away. Switch to the higher-precision `double` and the error has nowhere to hide. Always use this kind of approximate comparison for equality checks:
103
119
104
120
```c
105
121
#include<math.h>
106
122
107
-
/// @brief 判断两个 float 是否近似相等
108
-
/// @param a 第一个浮点数
109
-
/// @param b 第二个浮点数
110
-
/// @return 1 表示近似相等,0 表示不相等
123
+
/// @brief Check whether two floats are approximately equal
124
+
/// @param a the first float
125
+
/// @param b the second float
126
+
/// @return 1 if approximately equal, 0 otherwise
111
127
intfloat_equal(float a, float b)
112
128
{
113
129
return fabsf(a - b) < 1e-6f;
114
130
}
115
131
```
116
132
117
-
> ⚠️ **Warning**
118
-
> Never compare floating-point numbers using `==`. In floating-point arithmetic, `0.1 + 0.2 != 0.3` is the norm, not a bug. Using epsilon to check for approximate equality is the correct approach.
119
-
120
-
There is one more detail: when we write `float f = 0.1;`, the literal `0.1` is first treated as a `double` and then truncated to `float`—this might introduce additional precision differences. If we intend to use `float`, we should get into the habit of adding the `f` suffix.
133
+
One more detail: when you write `float f = 0.1;`, the `0.1` is first treated as a `double` and then truncated to `float`, which introduces an extra precision difference. If you really mean to use `float`, get into the habit of adding the `f` suffix.
121
134
122
135
### Floating-Point in Embedded Systems
123
136
@@ -311,9 +324,9 @@ Predict the output of the following code, then compile and run it to verify your
311
324
312
325
intmain(void)
313
326
{
314
-
float a = 0.1f;
315
-
float b = 0.2f;
316
-
float c = 0.3f;
327
+
double a = 0.1;
328
+
double b = 0.2;
329
+
double c = 0.3;
317
330
318
331
printf("a + b == c? %s\n", (a + b == c) ? "yes" : "no");
Copy file name to clipboardExpand all lines: documents/en/vol1-fundamentals/c_tutorials/index.md
+6-1Lines changed: 6 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,11 @@
1
1
# Comprehensive C Language Tutorial
2
2
3
-
If you are planning to learn or revisit C for whatever reason, and find the C crash course in the parent directory too fast-paced, consider this tutorial instead. It is more detailed and gradual than the crash course, and you can still pick and choose topics based on your interests.
3
+
PS: This part of the tutorial is not aimed at complete beginners. It grew out of a set of C notes taken back when working in embedded development, by which point C was already well in hand. So if you actually need to learn C from scratch, head over to this repo instead:
4
+
5
+
> [GitHub: C Journey](https://github.com/Awesome-Embedded-Learning-Studio/C-Journey)
6
+
> [Website: C Journey](https://awesome-embedded-learning-studio.github.io/C-Journey/)
7
+
8
+
The C tutorials here are better suited for people who once learned C but have forgotten what it looks like.
0 commit comments