I want to know if there are any standard workarounds that are used often.
Remember, the type is call floating-point.
number's radix point can "float" anywhere to the left, right, or between the significant digits
Any form of of rating the error of a, b is subject to the particular needs of code. The below uses a general discussion.
Floating point values are generally overall distributed logarithmically. That implies the error between a, b is assessed as (a-b)/max_magnitude(a, b). Then the error of 1500, 3000 is like the error to 0.00015, 0.00030.
Yet within a power-of-2, floating point values are distributed linearly. That implies the error between a, b is assessed as (a-b)/floor(log2(max_magnitude(a, b))). Then the error of 2.100, 2.200 is like the error to 2.800, 2.900.
Both approaches have trouble near sub-normal numbers (due to reduced precision), zeros (division by 0), and infinities.
A 3rd, and recommended approach, is to consider each floating point value as an indexed value. Consider all possible positive float values (+0.0 ... Infinity) are ordered and indexed 0, 1, 2, ... N. Negative values are indexed -0, -1, -2, ... -N. Then the error between a, b is then index(a) - index(b). The difference is related to the unit in the last place (ULP).
This allows for a consistent error measurement across the entire ordered float range. See below code.
Math functions like sin(x), sqrt(x), log(x) are often rated as if the x was exact and the y = f(x) (with is finite precision) is rated against the mathematical f(x) with it infinite precision. This error is rated in a like manner as if the math result may exist as a fractional index between two float indexes. Combined with ULP, we can say the error of a good sqrt(x) versus √x is at most 0.5 ULP. Good implementations of transcendental functions line sin(), log() have an error < 1.0 ULP.
// Return the indexed absolute difference of 2 float values. // Overlay a float and access it as an integer to determine its "index". // Take advantage the + ascending float values, // when examined as integers are sequenced in ascending integer order. // Assumes floating-point endian is like integer endian. // Not valid for not-a-numbers. unsigned long ULP_diff(float x, float y) { assert(sizeof(float) == sizeof(uint32_t)); union { float f; int32_t ll; uint32_t ull; } ux, uy; ux.f = x; uy.f = y; if (ux.ll < 0) { ux.ll = INT32_MIN - ux.ll; } if (uy.ll < 0) { uy.ll = INT32_MIN - uy.ll; } unsigned long diff; if (ux.ll >= uy.ll) { diff = ux.ull - uy.ull; } else { diff = uy.ull - ux.ull; } return diff; }