makefile based build
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
356
analysis/02-Floating_Point_Equality_Is_a_Lie/example/main.cpp
Normal file
356
analysis/02-Floating_Point_Equality_Is_a_Lie/example/main.cpp
Normal file
@@ -0,0 +1,356 @@
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <cstdlib>
|
||||
#include <ctime>
|
||||
#include <string>
|
||||
|
||||
template<typename T>
|
||||
bool equal_naive (T a, T b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool equal_abs (T a, T b, T eps) {
|
||||
return std::fabs (a - b) <= eps;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
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<typename T>
|
||||
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<typename T>
|
||||
void print_bool_result (const std::string &label, bool value) {
|
||||
std::cout << " " << std::left << std::setw (28) << label
|
||||
<< ": " << (value ? "true" : "false") << "\n";
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void print_value_line (const std::string &label, T value) {
|
||||
std::cout << " " << std::left << std::setw (28) << label
|
||||
<< ": " << std::setprecision (std::numeric_limits<T>::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<double> ("a == b", equal_naive (a, b));
|
||||
print_bool_result<double> ("abs eps = 1e-12", equal_abs (a, b, 1e-12));
|
||||
print_bool_result<double> ("abs eps = 1e-9", equal_abs (a, b, 1e-9));
|
||||
print_bool_result<double> ("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<double> ("a == b", equal_naive (a, b));
|
||||
print_bool_result<double> ("abs eps = 1e-12", equal_abs (a, b, 1e-12));
|
||||
print_bool_result<double> ("abs eps = 1e-9", equal_abs (a, b, 1e-9));
|
||||
print_bool_result<double> ("abs eps = 1e-3", equal_abs (a, b, 1e-3));
|
||||
|
||||
print_bool_result<double> ("rel eps = 1e-12", equal_rel (a, b, 1e-12));
|
||||
print_bool_result<double> ("rel eps = 1e-9", equal_rel (a, b, 1e-9));
|
||||
print_bool_result<double> ("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<double> ("a == b", equal_naive (a, b));
|
||||
print_bool_result<double> ("abs eps = 1e-9", equal_abs (a, b, 1e-9));
|
||||
print_bool_result<double> ("abs eps = 1e-12", equal_abs (a, b, 1e-12));
|
||||
print_bool_result<double> ("rel eps = 1e-9", equal_rel (a, b, 1e-9));
|
||||
print_bool_result<double> ("rel eps = 0.5", equal_rel (a, b, 0.5));
|
||||
print_bool_result<double> ("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<double> ("a == b", equal_naive (a, b));
|
||||
print_bool_result<double> ("rel eps = 1e-6", equal_rel (a, b, 1e-6));
|
||||
print_bool_result<double> ("abs eps = 1e-12", equal_abs (a, b, 1e-12));
|
||||
print_bool_result<double> ("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<double> ("x == y", equal_naive (x, y));
|
||||
print_bool_result<double> ("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<double> ("x == y", equal_naive (x, y));
|
||||
print_bool_result<double> ("abs eps = 1e-9", equal_abs (x, y, 1e-9));
|
||||
print_bool_result<double> ("abs eps = 1e-6", equal_abs (x, y, 1e-6));
|
||||
print_bool_result<double> ("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> ("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> ("double a == b", equal_naive (ad, bd));
|
||||
}
|
||||
|
||||
double random_noise (double amplitude) {
|
||||
const double unit = static_cast<double> (std::rand()) / static_cast<double> (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<int> ("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<double> ("scaled_value == scaled_target",
|
||||
equal_naive (scaled_value, scaled_target));
|
||||
print_bool_result<double> ("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<double> ("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<double> ("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<unsigned int> (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;
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user