From 52114dae41236acee954feefd4373e85396840a4 Mon Sep 17 00:00:00 2001 From: deeaitch Date: Sat, 28 Mar 2026 08:55:31 -0400 Subject: [PATCH] makefile based build --- .../example/Makefile | 31 ++ .../example/diff.txt | 78 ++++ .../example/main.cpp | 356 ++++++++++++++++++ .../example/output_debug.txt | 190 ++++++++++ .../example/output_release.txt | 190 ++++++++++ 5 files changed, 845 insertions(+) create mode 100644 analysis/02-Floating_Point_Equality_Is_a_Lie/example/Makefile create mode 100644 analysis/02-Floating_Point_Equality_Is_a_Lie/example/diff.txt create mode 100644 analysis/02-Floating_Point_Equality_Is_a_Lie/example/main.cpp create mode 100644 analysis/02-Floating_Point_Equality_Is_a_Lie/example/output_debug.txt create mode 100644 analysis/02-Floating_Point_Equality_Is_a_Lie/example/output_release.txt diff --git a/analysis/02-Floating_Point_Equality_Is_a_Lie/example/Makefile b/analysis/02-Floating_Point_Equality_Is_a_Lie/example/Makefile new file mode 100644 index 0000000..c61c826 --- /dev/null +++ b/analysis/02-Floating_Point_Equality_Is_a_Lie/example/Makefile @@ -0,0 +1,31 @@ +# Compiler +CXX := g++ + +# Common settings +STD := -std=c++14 +TARGET := floating_demo +SRC := main.cpp + +# Debug flags +CXXFLAGS_DEBUG := $(STD) -O0 -Wall -Wextra -pedantic + +# Release flags +CXXFLAGS_RELEASE := $(STD) -O2 + +# Default target +all: debug + +# Debug build +debug: + $(CXX) $(CXXFLAGS_DEBUG) $(SRC) -o $(TARGET) + +# Release build +release: + $(CXX) $(CXXFLAGS_RELEASE) $(SRC) -o $(TARGET) + +# Clean +clean: + rm -f $(TARGET) + +# Phony targets +.PHONY: all debug release clean diff --git a/analysis/02-Floating_Point_Equality_Is_a_Lie/example/diff.txt b/analysis/02-Floating_Point_Equality_Is_a_Lie/example/diff.txt new file mode 100644 index 0000000..1b6c44a --- /dev/null +++ b/analysis/02-Floating_Point_Equality_Is_a_Lie/example/diff.txt @@ -0,0 +1,78 @@ +90,101c90,101 +< 0 24.97779766 false true true +< 1 25.01916948 false true true +< 2 25.02972687 false true true +< 3 25.01870257 false true true +< 4 24.96365349 false true true +< 5 25.02417787 false true true +< 6 25.01521257 false true true +< 7 25.01746188 false true true +< 8 25.01469876 false true true +< 9 24.95849519 false true true +< 10 25.00975313 false true true +< 11 24.96665774 false true true +--- +> 0 24.97471804 false true true +> 1 25.0413491 false true true +> 2 24.99852989 false true true +> 3 24.99317494 false true true +> 4 25.00292729 false true true +> 5 25.03122934 false true true +> 6 24.97063462 false true true +> 7 24.9619692 false true true +> 8 24.99279928 false true true +> 9 24.97964776 false true true +> 10 25.04053616 false true true +> 11 25.0353959 false true true +105,116c105,116 +< 0 24.99675513 false true true +< 1 24.9971971 false true true +< 2 25.00143947 false true true +< 3 24.99902736 false true true +< 4 24.99946988 false true true +< 5 24.99607726 false true true +< 6 24.99667581 false true true +< 7 25.00177208 false true true +< 8 24.99822762 false true true +< 9 25.00066511 false true true +< 10 24.997119 false true true +< 11 25.0030632 false true true +--- +> 0 25.00020134 false true true +> 1 25.00382071 false true true +> 2 25.00383611 false true true +> 3 24.99614752 false true true +> 4 25.00265027 false true true +> 5 25.00074494 false true true +> 6 24.99856782 false true true +> 7 25.00364029 false true true +> 8 25.00150722 false true true +> 9 25.00310794 false true true +> 10 25.00211411 false true true +> 11 25.00197054 false true true +120,131c120,131 +< 0 24.99998171 false true true +< 1 25.00043692 false false false +< 2 24.99991995 false true true +< 3 24.99953881 false false false +< 4 24.99997226 false true true +< 5 25.00001312 false true true +< 6 24.99969943 false false false +< 7 25.00025024 false false false +< 8 24.99970481 false false false +< 9 25.0004967 false false false +< 10 24.99993727 false true true +< 11 24.99984134 false false false +--- +> 0 25.00034068 false false false +> 1 24.99978583 false false false +> 2 24.99965013 false false false +> 3 25.00030996 false false false +> 4 25.00044598 false false false +> 5 25.00007675 false true true +> 6 25.00043085 false false false +> 7 24.99969316 false false false +> 8 24.99999024 false true true +> 9 24.99991615 false true true +> 10 25.00012491 false false false +> 11 24.99951951 false false false diff --git a/analysis/02-Floating_Point_Equality_Is_a_Lie/example/main.cpp b/analysis/02-Floating_Point_Equality_Is_a_Lie/example/main.cpp new file mode 100644 index 0000000..1d8a2ec --- /dev/null +++ b/analysis/02-Floating_Point_Equality_Is_a_Lie/example/main.cpp @@ -0,0 +1,356 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +template +bool equal_naive (T a, T b) { + return a == b; +} + +template +bool equal_abs (T a, T b, T eps) { + return std::fabs (a - b) <= eps; +} + +template +bool equal_rel (T a, T b, T eps) { + const T scale = std::max (std::fabs (a), std::fabs (b)); + return std::fabs (a - b) <= eps * scale; +} + +template +bool equal_combined (T a, T b, T abs_eps, T rel_eps) { + const T scale = std::max (std::fabs (a), std::fabs (b)); + return std::fabs (a - b) <= std::max (abs_eps, rel_eps * scale); +} + +template +void print_bool_result (const std::string &label, bool value) { + std::cout << " " << std::left << std::setw (28) << label + << ": " << (value ? "true" : "false") << "\n"; +} + +template +void print_value_line (const std::string &label, T value) { + std::cout << " " << std::left << std::setw (28) << label + << ": " << std::setprecision (std::numeric_limits::digits10 + 2) + << value << "\n"; +} + +void print_section (const std::string &title) { + std::cout << "\n============================================================\n"; + std::cout << title << "\n"; + std::cout << "============================================================\n"; +} + +void example_basic_01_plus_02() { + print_section ("1. Basic classic: 0.1 + 0.2 != 0.3"); + + const double a = 0.1 + 0.2; + const double b = 0.3; + + print_value_line ("a", a); + print_value_line ("b", b); + print_value_line ("abs(a - b)", std::fabs (a - b)); + + print_bool_result ("a == b", equal_naive (a, b)); + print_bool_result ("abs eps = 1e-12", equal_abs (a, b, 1e-12)); + print_bool_result ("abs eps = 1e-9", equal_abs (a, b, 1e-9)); + print_bool_result ("combined", equal_combined (a, b, 1e-12, 1e-12)); +} + +void example_large_numbers() { + print_section ("2. Large numbers: fixed absolute epsilon becomes useless"); + + const double a = 1e9; + const double b = a + 0.1; + + print_value_line ("a", a); + print_value_line ("b", b); + print_value_line ("abs(a - b)", std::fabs (a - b)); + + print_bool_result ("a == b", equal_naive (a, b)); + print_bool_result ("abs eps = 1e-12", equal_abs (a, b, 1e-12)); + print_bool_result ("abs eps = 1e-9", equal_abs (a, b, 1e-9)); + print_bool_result ("abs eps = 1e-3", equal_abs (a, b, 1e-3)); + + print_bool_result ("rel eps = 1e-12", equal_rel (a, b, 1e-12)); + print_bool_result ("rel eps = 1e-9", equal_rel (a, b, 1e-9)); + print_bool_result ("combined", equal_combined (a, b, 1e-9, 1e-9)); +} + +void example_small_numbers() { + print_section ("3. Very small numbers: fixed absolute epsilon can be too large"); + + const double a = 1e-12; + const double b = 2e-12; + + print_value_line ("a", a); + print_value_line ("b", b); + print_value_line ("abs(a - b)", std::fabs (a - b)); + + print_bool_result ("a == b", equal_naive (a, b)); + print_bool_result ("abs eps = 1e-9", equal_abs (a, b, 1e-9)); + print_bool_result ("abs eps = 1e-12", equal_abs (a, b, 1e-12)); + print_bool_result ("rel eps = 1e-9", equal_rel (a, b, 1e-9)); + print_bool_result ("rel eps = 0.5", equal_rel (a, b, 0.5)); + print_bool_result ("combined", equal_combined (a, b, 1e-15, 1e-6)); +} + +void example_near_zero_relative_problem() { + print_section ("4. Near zero: relative comparison alone is weak"); + + const double a = 0.0; + const double b = 1e-15; + + print_value_line ("a", a); + print_value_line ("b", b); + print_value_line ("abs(a - b)", std::fabs (a - b)); + + print_bool_result ("a == b", equal_naive (a, b)); + print_bool_result ("rel eps = 1e-6", equal_rel (a, b, 1e-6)); + print_bool_result ("abs eps = 1e-12", equal_abs (a, b, 1e-12)); + print_bool_result ("combined", equal_combined (a, b, 1e-12, 1e-6)); +} + +void example_associativity() { + print_section ("5. Same math on paper, different result in floating point"); + + const double a = 1e16; + const double b = -1e16; + const double c = 1.0; + + const double x = (a + b) + c; + const double y = a + (b + c); + + print_value_line ("x = (a + b) + c", x); + print_value_line ("y = a + (b + c)", y); + print_value_line ("abs(x - y)", std::fabs (x - y)); + + print_bool_result ("x == y", equal_naive (x, y)); + print_bool_result ("combined", equal_combined (x, y, 1e-12, 1e-12)); +} + +void example_accumulation() { + print_section ("6. Accumulation of error: repeated addition"); + + double x = 0.0; + for (int i = 0; i < 1000000; ++i) + x += 0.1; + + const double y = 100000.0; + + print_value_line ("x", x); + print_value_line ("y", y); + print_value_line ("abs(x - y)", std::fabs (x - y)); + + print_bool_result ("x == y", equal_naive (x, y)); + print_bool_result ("abs eps = 1e-9", equal_abs (x, y, 1e-9)); + print_bool_result ("abs eps = 1e-6", equal_abs (x, y, 1e-6)); + print_bool_result ("combined", equal_combined (x, y, 1e-6, 1e-12)); +} + +void example_float_vs_double() { + print_section ("7. float vs double: same idea, different precision"); + + float af = 0.1f + 0.2f; + float bf = 0.3f; + + double ad = 0.1 + 0.2; + double bd = 0.3; + + print_value_line ("float a", af); + print_value_line ("float b", bf); + print_value_line ("float abs diff", std::fabs (af - bf)); + print_bool_result ("float a == b", equal_naive (af, bf)); + + std::cout << "\n"; + + print_value_line ("double a", ad); + print_value_line ("double b", bd); + print_value_line ("double abs diff", std::fabs (ad - bd)); + print_bool_result ("double a == b", equal_naive (ad, bd)); +} + +double random_noise (double amplitude) { + const double unit = static_cast (std::rand()) / static_cast (RAND_MAX); + return (unit * 2.0 - 1.0) * amplitude; +} + +void run_sensor_simulation (double noise_amplitude, double abs_eps) { + const double target = 25.0; + const double rel_eps = 1e-6; + + std::cout << "\nNoise amplitude: +/-" << noise_amplitude + << ", abs_eps: " << abs_eps << "\n"; + + std::cout << " " << std::left + << std::setw (10) << "sample" + << std::setw (18) << "value" + << std::setw (10) << "==" + << std::setw (10) << "abs" + << std::setw (10) << "comb" + << "\n"; + + for (int i = 0; i < 12; ++i) { + const double value = target + random_noise (noise_amplitude); + + const bool naive = equal_naive (value, target); + const bool abs_ok = equal_abs (value, target, abs_eps); + const bool comb_ok = equal_combined (value, target, abs_eps, rel_eps); + + std::cout << " " << std::left + << std::setw (10) << i + << std::setw (18) << std::setprecision (10) << value + << std::setw (10) << (naive ? "true" : "false") + << std::setw (10) << (abs_ok ? "true" : "false") + << std::setw (10) << (comb_ok ? "true" : "false") + << "\n"; + } +} + +void example_sensor_noise() { + print_section ("8. Sensor-like values: 'equal' is often the wrong question"); + + run_sensor_simulation (0.05, 0.1); + run_sensor_simulation (0.005, 0.01); + run_sensor_simulation (0.0005, 0.0001); +} + +void example_scaled_integer_vs_double() { + print_section ("9. Scaled integer values: often better to compare raw domain"); + + const int raw_value = 1234; // imagine 123.4 in deci-units + const int raw_target = 1234; + + const double scaled_value = raw_value * 0.1; + const double scaled_target = 123.4; + + print_value_line ("raw_value", raw_value); + print_value_line ("raw_target", raw_target); + print_bool_result ("raw_value == raw_target", raw_value == raw_target); + + std::cout << "\n"; + + print_value_line ("scaled_value", scaled_value); + print_value_line ("scaled_target", scaled_target); + print_value_line ("abs diff", std::fabs (scaled_value - scaled_target)); + print_bool_result ("scaled_value == scaled_target", + equal_naive (scaled_value, scaled_target)); + print_bool_result ("combined", + equal_combined (scaled_value, scaled_target, 1e-12, 1e-12)); + + std::cout << "\n Note: if your system is naturally discrete, comparing raw units can be simpler\n"; + std::cout << " and more honest than converting everything to floating point too early.\n"; +} + +void example_rounding_is_not_magic() { + print_section ("10. Rounding is not a universal fix"); + + const double a = 1.0049; + const double b = 1.0051; + + const double rounded_a_2 = std::round (a * 100.0) / 100.0; + const double rounded_b_2 = std::round (b * 100.0) / 100.0; + + const double rounded_a_3 = std::round (a * 1000.0) / 1000.0; + const double rounded_b_3 = std::round (b * 1000.0) / 1000.0; + + print_value_line ("a", a); + print_value_line ("b", b); + + std::cout << "\n"; + print_value_line ("round(a, 2)", rounded_a_2); + print_value_line ("round(b, 2)", rounded_b_2); + print_bool_result ("equal after round(2)", rounded_a_2 == rounded_b_2); + + std::cout << "\n"; + print_value_line ("round(a, 3)", rounded_a_3); + print_value_line ("round(b, 3)", rounded_b_3); + print_bool_result ("equal after round(3)", rounded_a_3 == rounded_b_3); + + std::cout << "\n Rounding may hide differences or invent equality depending on chosen precision.\n"; +} + +void example_hysteresis() { + print_section ("11. Hysteresis: in real systems you often want state logic, not equality"); + + const double target = 25.0; + const double on_threshold = target - 0.1; + const double off_threshold = target + 0.1; + + const double signal[] = { + 24.92, 24.97, 25.01, 25.05, 24.99, 25.08, 25.11, 25.06, + 24.98, 24.91, 24.89, 24.95, 25.02, 25.12, 25.07, 24.93 + }; + + bool heater_simple = true; + bool heater_hysteresis = true; + + std::cout << " " << std::left + << std::setw (8) << "step" + << std::setw (12) << "value" + << std::setw (16) << "simple_ctrl" + << std::setw (16) << "hyst_ctrl" + << "\n"; + + for (size_t i = 0; i < (sizeof (signal) / sizeof (signal[0])); ++i) { + const double value = signal[i]; + + /* + * Simple control: + * heater ON below target + * heater OFF at or above target + * This tends to chatter near the threshold. + */ + heater_simple = value < target; + + /* + * Hysteresis control: + * heater turns ON only below lower threshold + * heater turns OFF only above upper threshold + * This reduces chatter around the target. + */ + if (heater_hysteresis && value > off_threshold) + heater_hysteresis = false; + else if (!heater_hysteresis && value < on_threshold) + heater_hysteresis = true; + + std::cout << " " << std::left + << std::setw (8) << i + << std::setw (12) << std::setprecision (6) << value + << std::setw (16) << (heater_simple ? "HEATER ON" : "HEATER OFF") + << std::setw (16) << (heater_hysteresis ? "HEATER ON" : "HEATER OFF") + << "\n"; + } + + std::cout << "\n This is the key engineering point:\n"; + std::cout << " sometimes the answer is not a better equality test,\n"; + std::cout << " but a better control model.\n"; +} + +int main() { + std::srand (static_cast (std::time (NULL))); + + std::cout << std::boolalpha; + + example_basic_01_plus_02(); + example_large_numbers(); + example_small_numbers(); + example_near_zero_relative_problem(); + example_associativity(); + example_accumulation(); + example_float_vs_double(); + example_sensor_noise(); + example_scaled_integer_vs_double(); + example_rounding_is_not_magic(); + example_hysteresis(); + + std::cout << "\nDone.\n"; + return 0; +} diff --git a/analysis/02-Floating_Point_Equality_Is_a_Lie/example/output_debug.txt b/analysis/02-Floating_Point_Equality_Is_a_Lie/example/output_debug.txt new file mode 100644 index 0000000..8629b27 --- /dev/null +++ b/analysis/02-Floating_Point_Equality_Is_a_Lie/example/output_debug.txt @@ -0,0 +1,190 @@ + +============================================================ +1. Basic classic: 0.1 + 0.2 != 0.3 +============================================================ + a : 0.30000000000000004 + b : 0.29999999999999999 + abs(a - b) : 5.5511151231257827e-17 + a == b : false + abs eps = 1e-12 : true + abs eps = 1e-9 : true + combined : true + +============================================================ +2. Large numbers: fixed absolute epsilon becomes useless +============================================================ + a : 1000000000 + b : 1000000000.1 + abs(a - b) : 0.10000002384185791 + a == b : false + abs eps = 1e-12 : false + abs eps = 1e-9 : false + abs eps = 1e-3 : false + rel eps = 1e-12 : false + rel eps = 1e-9 : true + combined : true + +============================================================ +3. Very small numbers: fixed absolute epsilon can be too large +============================================================ + a : 9.9999999999999998e-13 + b : 2e-12 + abs(a - b) : 9.9999999999999998e-13 + a == b : false + abs eps = 1e-9 : true + abs eps = 1e-12 : true + rel eps = 1e-9 : false + rel eps = 0.5 : true + combined : false + +============================================================ +4. Near zero: relative comparison alone is weak +============================================================ + a : 0 + b : 1.0000000000000001e-15 + abs(a - b) : 1.0000000000000001e-15 + a == b : false + rel eps = 1e-6 : false + abs eps = 1e-12 : true + combined : true + +============================================================ +5. Same math on paper, different result in floating point +============================================================ + x = (a + b) + c : 1 + y = a + (b + c) : 0 + abs(x - y) : 1 + x == y : false + combined : false + +============================================================ +6. Accumulation of error: repeated addition +============================================================ + x : 100000.00000133288 + y : 100000 + abs(x - y) : 1.3328826753422618e-06 + x == y : false + abs eps = 1e-9 : false + abs eps = 1e-6 : false + combined : false + +============================================================ +7. float vs double: same idea, different precision +============================================================ + float a : 0.30000001 + float b : 0.30000001 + float abs diff : 0 + float a == b : true + + double a : 0.30000000000000004 + double b : 0.29999999999999999 + double abs diff : 5.5511151231257827e-17 + double a == b : false + +============================================================ +8. Sensor-like values: 'equal' is often the wrong question +============================================================ + +Noise amplitude: +/-0.050000000000000003, abs_eps: 0.10000000000000001 + sample value == abs comb + 0 24.97779766 false true true + 1 25.01916948 false true true + 2 25.02972687 false true true + 3 25.01870257 false true true + 4 24.96365349 false true true + 5 25.02417787 false true true + 6 25.01521257 false true true + 7 25.01746188 false true true + 8 25.01469876 false true true + 9 24.95849519 false true true + 10 25.00975313 false true true + 11 24.96665774 false true true + +Noise amplitude: +/-0.005, abs_eps: 0.01 + sample value == abs comb + 0 24.99675513 false true true + 1 24.9971971 false true true + 2 25.00143947 false true true + 3 24.99902736 false true true + 4 24.99946988 false true true + 5 24.99607726 false true true + 6 24.99667581 false true true + 7 25.00177208 false true true + 8 24.99822762 false true true + 9 25.00066511 false true true + 10 24.997119 false true true + 11 25.0030632 false true true + +Noise amplitude: +/-0.0005, abs_eps: 0.0001 + sample value == abs comb + 0 24.99998171 false true true + 1 25.00043692 false false false + 2 24.99991995 false true true + 3 24.99953881 false false false + 4 24.99997226 false true true + 5 25.00001312 false true true + 6 24.99969943 false false false + 7 25.00025024 false false false + 8 24.99970481 false false false + 9 25.0004967 false false false + 10 24.99993727 false true true + 11 24.99984134 false false false + +============================================================ +9. Scaled integer values: often better to compare raw domain +============================================================ + raw_value : 1234 + raw_target : 1234 + raw_value == raw_target : true + + scaled_value : 123.40000000000001 + scaled_target : 123.40000000000001 + abs diff : 0 + scaled_value == scaled_target: true + combined : true + + Note: if your system is naturally discrete, comparing raw units can be simpler + and more honest than converting everything to floating point too early. + +============================================================ +10. Rounding is not a universal fix +============================================================ + a : 1.0048999999999999 + b : 1.0051000000000001 + + round(a, 2) : 1 + round(b, 2) : 1.01 + equal after round(2) : false + + round(a, 3) : 1.0049999999999999 + round(b, 3) : 1.0049999999999999 + equal after round(3) : true + + Rounding may hide differences or invent equality depending on chosen precision. + +============================================================ +11. Hysteresis: in real systems you often want state logic, not equality +============================================================ + step value simple_ctrl hyst_ctrl + 0 24.92 HEATER ON HEATER ON + 1 24.97 HEATER ON HEATER ON + 2 25.01 HEATER OFF HEATER ON + 3 25.05 HEATER OFF HEATER ON + 4 24.99 HEATER ON HEATER ON + 5 25.08 HEATER OFF HEATER ON + 6 25.11 HEATER OFF HEATER OFF + 7 25.06 HEATER OFF HEATER OFF + 8 24.98 HEATER ON HEATER OFF + 9 24.91 HEATER ON HEATER OFF + 10 24.89 HEATER ON HEATER ON + 11 24.95 HEATER ON HEATER ON + 12 25.02 HEATER OFF HEATER ON + 13 25.12 HEATER OFF HEATER OFF + 14 25.07 HEATER OFF HEATER OFF + 15 24.93 HEATER ON HEATER OFF + + This is the key engineering point: + sometimes the answer is not a better equality test, + but a better control model. + +Done. diff --git a/analysis/02-Floating_Point_Equality_Is_a_Lie/example/output_release.txt b/analysis/02-Floating_Point_Equality_Is_a_Lie/example/output_release.txt new file mode 100644 index 0000000..f947d0e --- /dev/null +++ b/analysis/02-Floating_Point_Equality_Is_a_Lie/example/output_release.txt @@ -0,0 +1,190 @@ + +============================================================ +1. Basic classic: 0.1 + 0.2 != 0.3 +============================================================ + a : 0.30000000000000004 + b : 0.29999999999999999 + abs(a - b) : 5.5511151231257827e-17 + a == b : false + abs eps = 1e-12 : true + abs eps = 1e-9 : true + combined : true + +============================================================ +2. Large numbers: fixed absolute epsilon becomes useless +============================================================ + a : 1000000000 + b : 1000000000.1 + abs(a - b) : 0.10000002384185791 + a == b : false + abs eps = 1e-12 : false + abs eps = 1e-9 : false + abs eps = 1e-3 : false + rel eps = 1e-12 : false + rel eps = 1e-9 : true + combined : true + +============================================================ +3. Very small numbers: fixed absolute epsilon can be too large +============================================================ + a : 9.9999999999999998e-13 + b : 2e-12 + abs(a - b) : 9.9999999999999998e-13 + a == b : false + abs eps = 1e-9 : true + abs eps = 1e-12 : true + rel eps = 1e-9 : false + rel eps = 0.5 : true + combined : false + +============================================================ +4. Near zero: relative comparison alone is weak +============================================================ + a : 0 + b : 1.0000000000000001e-15 + abs(a - b) : 1.0000000000000001e-15 + a == b : false + rel eps = 1e-6 : false + abs eps = 1e-12 : true + combined : true + +============================================================ +5. Same math on paper, different result in floating point +============================================================ + x = (a + b) + c : 1 + y = a + (b + c) : 0 + abs(x - y) : 1 + x == y : false + combined : false + +============================================================ +6. Accumulation of error: repeated addition +============================================================ + x : 100000.00000133288 + y : 100000 + abs(x - y) : 1.3328826753422618e-06 + x == y : false + abs eps = 1e-9 : false + abs eps = 1e-6 : false + combined : false + +============================================================ +7. float vs double: same idea, different precision +============================================================ + float a : 0.30000001 + float b : 0.30000001 + float abs diff : 0 + float a == b : true + + double a : 0.30000000000000004 + double b : 0.29999999999999999 + double abs diff : 5.5511151231257827e-17 + double a == b : false + +============================================================ +8. Sensor-like values: 'equal' is often the wrong question +============================================================ + +Noise amplitude: +/-0.050000000000000003, abs_eps: 0.10000000000000001 + sample value == abs comb + 0 24.97471804 false true true + 1 25.0413491 false true true + 2 24.99852989 false true true + 3 24.99317494 false true true + 4 25.00292729 false true true + 5 25.03122934 false true true + 6 24.97063462 false true true + 7 24.9619692 false true true + 8 24.99279928 false true true + 9 24.97964776 false true true + 10 25.04053616 false true true + 11 25.0353959 false true true + +Noise amplitude: +/-0.005, abs_eps: 0.01 + sample value == abs comb + 0 25.00020134 false true true + 1 25.00382071 false true true + 2 25.00383611 false true true + 3 24.99614752 false true true + 4 25.00265027 false true true + 5 25.00074494 false true true + 6 24.99856782 false true true + 7 25.00364029 false true true + 8 25.00150722 false true true + 9 25.00310794 false true true + 10 25.00211411 false true true + 11 25.00197054 false true true + +Noise amplitude: +/-0.0005, abs_eps: 0.0001 + sample value == abs comb + 0 25.00034068 false false false + 1 24.99978583 false false false + 2 24.99965013 false false false + 3 25.00030996 false false false + 4 25.00044598 false false false + 5 25.00007675 false true true + 6 25.00043085 false false false + 7 24.99969316 false false false + 8 24.99999024 false true true + 9 24.99991615 false true true + 10 25.00012491 false false false + 11 24.99951951 false false false + +============================================================ +9. Scaled integer values: often better to compare raw domain +============================================================ + raw_value : 1234 + raw_target : 1234 + raw_value == raw_target : true + + scaled_value : 123.40000000000001 + scaled_target : 123.40000000000001 + abs diff : 0 + scaled_value == scaled_target: true + combined : true + + Note: if your system is naturally discrete, comparing raw units can be simpler + and more honest than converting everything to floating point too early. + +============================================================ +10. Rounding is not a universal fix +============================================================ + a : 1.0048999999999999 + b : 1.0051000000000001 + + round(a, 2) : 1 + round(b, 2) : 1.01 + equal after round(2) : false + + round(a, 3) : 1.0049999999999999 + round(b, 3) : 1.0049999999999999 + equal after round(3) : true + + Rounding may hide differences or invent equality depending on chosen precision. + +============================================================ +11. Hysteresis: in real systems you often want state logic, not equality +============================================================ + step value simple_ctrl hyst_ctrl + 0 24.92 HEATER ON HEATER ON + 1 24.97 HEATER ON HEATER ON + 2 25.01 HEATER OFF HEATER ON + 3 25.05 HEATER OFF HEATER ON + 4 24.99 HEATER ON HEATER ON + 5 25.08 HEATER OFF HEATER ON + 6 25.11 HEATER OFF HEATER OFF + 7 25.06 HEATER OFF HEATER OFF + 8 24.98 HEATER ON HEATER OFF + 9 24.91 HEATER ON HEATER OFF + 10 24.89 HEATER ON HEATER ON + 11 24.95 HEATER ON HEATER ON + 12 25.02 HEATER OFF HEATER ON + 13 25.12 HEATER OFF HEATER OFF + 14 25.07 HEATER OFF HEATER OFF + 15 24.93 HEATER ON HEATER OFF + + This is the key engineering point: + sometimes the answer is not a better equality test, + but a better control model. + +Done.