math_utils/math_utils.mqh

1000 lines
44 KiB
MQL5
Raw Permalink Normal View History

2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
//| math_utils.mqh |
//| Copyright © 2026, Amr Ali |
//| https://www.mql5.com/en/users/amrali |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2026, Amr Ali"
#property link "https://www.mql5.com/en/users/amrali"
2026-08-02 14:42:54 +03:00
#property version "4.50"
2026-07-26 11:42:21 +03:00
#property description "Handy functions for comparison, rounding, formatting, and debugging of doubles (prices, lots, and money)."
2026-07-25 16:49:20 +03:00
#ifdef __MQL4__
#property strict
#endif
#ifndef MATH_UTILS_UNIQUE_HEADER_ID_H
#define MATH_UTILS_UNIQUE_HEADER_ID_H
2026-08-02 14:42:54 +03:00
//+------------------------------------------------------------------+
//| IEEE-754 binary64 bit-layout constants (used in Section IV). |
//+------------------------------------------------------------------+
#define MU_MANTISSA_BITS 52 // fraction bits
#define MU_EXPONENT_BIAS 1023 // exponent bias
#define MU_EXPONENT_MASK 0x7FF // 11-bit exponent field
#define MU_MANTISSA_MASK 0xFFFFFFFFFFFFF // 52-bit fraction field
#define MU_SIGN_BIT 0x8000000000000000 // sign bit, as a long/ulong bit pattern
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
//| Functions in the library. |
//+------------------------------------------------------------------+
/*
I. Handy functions for comparison of doubles
--------------------------------------------
int Compare(const double a, const double b, const int digits);
bool EQ(const double a, const double b, const int digits = 8);
bool NE(const double a, const double b, const int digits = 8);
bool GT(const double a, const double b, const int digits = 8);
bool LT(const double a, const double b, const int digits = 8);
bool GTE(const double a, const double b, const int digits = 8);
bool LTE(const double a, const double b, const int digits = 8);
2026-08-02 14:42:54 +03:00
bool IsZero(const double value, const int digits = 8);
2026-07-25 16:49:20 +03:00
bool AlmostEqual(const double a, const double b);
bool EqualDoubles(const double a, const double b, const int significantDigits = 15);
bool IsClose(const double a, const double b, const int maxDifferentSignificantDigits = 2);
int GetEqualDigits(const double a, const double b);
int GetEqualSigDigits(const double a, const double b);
II. Handy functions for rounding of doubles
-------------------------------------------
double Ceil(const double v);
double Ceil(const double value, const int digits);
double Ceil(const double value, const double step);
double Floor(const double v);
double Floor(const double value, const int digits);
double Floor(const double value, const double step);
double Round(const double v);
double Round(const double value, const int digits);
double Round(const double value, const double step);
double Trunc(const double v);
double Trunc(const double value, const int digits);
double Trunc(const double value, const double step);
double RoundToSignificantDigits(const double value, int digits);
2026-07-26 11:42:21 +03:00
double RoundPrice(const double pPrice, const string pSymbol = NULL);
double RoundVolume(const double pVolume, const string pSymbol = NULL);
2026-07-25 16:49:20 +03:00
double StripError(const double value);
III. Miscellaneous handy functions for doubles
----------------------------------------------
bool IsInteger(const double value);
bool IsRound(const double value, const int digits);
bool IsRound(const double value, const double step);
double Frac(const double value);
int GetDigits(const double value);
int GetIntegerDigits(const double value);
int GetSignificantDigits(double value);
double Remap(const double value, const double min, const double max, const double destMin, const double destMax);
2026-07-26 11:42:21 +03:00
double Lerp(const double start, const double end, const double t);
2026-07-25 16:49:20 +03:00
double Normalize(const double value, const double min, const double max);
double Denormalize(const double value, const double destMin, const double destMax);
T Clamp(const T value, const T min, const T max);
T ClampMax(const T value, const T max);
T ClampMin(const T value, const T min);
T Wrap(const T value, const T min, const T max);
double safeDiv(const double a, const double b);
2026-07-26 11:42:21 +03:00
int FloorLog10(double value);
2026-07-25 16:49:20 +03:00
double GetPower10(const int power);
2026-07-26 01:42:17 +03:00
int SignDbl(const double value);
2026-07-25 16:49:20 +03:00
IV. Handy functions for low-level binary operations on doubles
--------------------------------------------------------------
long DoubleToLongBits(const double value);
double LongBitsToDouble(const long bits);
long DoubleToOrderedLong(const double value);
double OrderedLongToDouble(const long bits);
int RawExponent(const double value);
long RawSignificand(const double value);
int GetExponent(const double value);
double GetSignificand(const double value);
bool IsExactDouble(const double value);
double NextAwayFromZero(const double value);
double DoubleAdvance(const double value, const long distance);
double Ulp(const double value);
long UlpDiff(const double a, const double b);
V. Handy functions for formatting of doubles to string
------------------------------------------------------
string DoubleToHexadecimal(const double value);
string DoubleToHexFloatConstant(const double value);
string DoubleToStringExact(const double value);
string DoubleToExponential(const double value, int digits = -1);
string Repr(const double value);
string Repr(const float value);
2026-07-26 11:42:21 +03:00
string DoubleDebugInfo(const double value);
2026-07-25 16:49:20 +03:00
string FormatDouble(const double value, const int digits, const string separator=",");
*/
//+------------------------------------------------------------------+
//| I. Handy functions for comparison of doubles. |
//+------------------------------------------------------------------+
// GT
//---------------------> double a
// EQ (1/2 point zone)
//---------------------> double b
// LT
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Compares two doubles up to a specified decimal precision. |
//| Returns 0 if equal, 1 if a > b, -1 if a < b. |
2026-08-02 14:42:54 +03:00
//| digits: number of decimal places of tolerance. Unlike |
//| NormalizeDouble(), this is NOT limited to 0-8, so it is |
//| safe to pass larger precisions. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
int Compare(const double a, const double b, const int digits)
{
2026-08-02 14:42:54 +03:00
// if(NormalizeDouble(a - b, digits) == 0.0)
if(MathAbs(a - b) < GetPower10(-digits) * 0.5)
2026-07-25 16:49:20 +03:00
return 0;
2026-07-26 11:42:21 +03:00
return (a > b) ? 1 : -1;
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
//| Is Equal. |
//+------------------------------------------------------------------+
bool EQ(const double a, const double b, const int digits = 8)
{
return Compare(a, b, digits) == 0;
}
//+------------------------------------------------------------------+
//| Is Not Equal. |
//+------------------------------------------------------------------+
bool NE(const double a, const double b, const int digits = 8)
{
return Compare(a, b, digits) != 0;
}
//+------------------------------------------------------------------+
//| Is Greater Than. |
//+------------------------------------------------------------------+
bool GT(const double a, const double b, const int digits = 8)
{
return Compare(a, b, digits) > 0;
}
//+------------------------------------------------------------------+
//| Is Less Than. |
//+------------------------------------------------------------------+
bool LT(const double a, const double b, const int digits = 8)
{
return Compare(a, b, digits) < 0;
}
//+------------------------------------------------------------------+
//| Greater Than or Equal. |
//+------------------------------------------------------------------+
bool GTE(const double a, const double b, const int digits = 8)
{
return Compare(a, b, digits) >= 0;
}
//+------------------------------------------------------------------+
//| Is Less Than or Equal. |
//+------------------------------------------------------------------+
bool LTE(const double a, const double b, const int digits = 8)
{
return Compare(a, b, digits) <= 0;
}
//+------------------------------------------------------------------+
2026-08-02 14:42:54 +03:00
//| Is (nearly) Zero, at the given decimal precision. |
//+------------------------------------------------------------------+
bool IsZero(const double value, const int digits = 8)
{
return Compare(value, 0.0, digits) == 0;
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Check almost equality (ignore tiny roundoff errors <= 1 ULP). |
2026-07-25 16:49:20 +03:00
//| If binary representations of two doubles differ by more than |
//| one least significant bit (ulp), the function returns false. |
//| For example, AlmostEqual(0.1+0.2, 0.3) => true |
//+------------------------------------------------------------------+
bool AlmostEqual(const double a, const double b)
{
// return (a == b) || MathAbs(a - b) <= DBL_EPSILON * MathMax(MathAbs(a), MathAbs(b));
return (a == b) || MathAbs(UlpDiff(a, b)) <= 1;
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Check whether two numbers match up to "n" significant digits. |
2026-07-25 16:49:20 +03:00
//| For example, EqualDoubles(3.124, 3.122, 3) => true |
//+------------------------------------------------------------------+
bool EqualDoubles(const double a, const double b, const int significantDigits = 15)
{
//--- https://stackoverflow.com/a/17382806
return (a == b) || MathAbs(a - b) <= GetPower10(-significantDigits) * MathMax(MathAbs(a), MathAbs(b));
}
//+------------------------------------------------------------------+
//| Check whether two floating point numbers are close in value. |
//| n: the max number of least significant digits that do not agree. |
//| Something like 2 or 3 usually works in practice. |
//+------------------------------------------------------------------+
bool IsClose(const double a, const double b, const int maxDifferentSignificantDigits = 2)
{
//--- https://stackoverflow.com/a/17382806
return (a == b) || MathAbs(a - b) <= GetPower10(maxDifferentSignificantDigits) * DBL_EPSILON * MathMax(MathAbs(a), MathAbs(b));
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Get number of fractional digits that agree after decimal point. |
//| For example, GetEqualDigits(3.124, 3.122) => 2 |
2026-08-02 14:42:54 +03:00
//| Returns 0 if either input is NaN or Infinity. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
int GetEqualDigits(const double a, const double b)
{
2026-08-02 14:42:54 +03:00
if(!MathIsValidNumber(a) || !MathIsValidNumber(b))
return 0;
2026-07-25 16:49:20 +03:00
//--- https://stackoverflow.com/a/49242211
return (a == b) ? 30 : MathMax(-FloorLog10(MathAbs(a - b)) - 1, 0);
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Get number of significant digits that agree between two numbers. |
2026-07-25 16:49:20 +03:00
//| For example, GetEqualSigDigits(3.124, 3.122) => 3 |
2026-08-02 14:42:54 +03:00
//| Returns 0 if either input is NaN or Infinity. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
int GetEqualSigDigits(const double a, const double b)
{
2026-08-02 14:42:54 +03:00
if(!MathIsValidNumber(a) || !MathIsValidNumber(b))
return 0;
2026-07-25 16:49:20 +03:00
return (a == b) ? 17 : FloorLog10(MathMax(MathAbs(a), MathAbs(b))) - FloorLog10(MathAbs(a - b));
}
//+------------------------------------------------------------------+
//| II. Handy functions for rounding of doubles. |
//+------------------------------------------------------------------+
2026-08-02 14:42:54 +03:00
//+------------------------------------------------------------------+
//| Largest magnitude for which not every double is already an |
//| integer. Beyond 2^52, every representable double has no |
//| fractional part, so Ceil/Floor/Round/Trunc are no-ops there -- |
//| and casting a value that large to "long" would overflow anyway |
//| (long only spans up to ~9.2e18, and the cast is undefined |
//| behavior once the value falls outside that range). |
//+------------------------------------------------------------------+
#define MU_MAX_FRACTIONAL_DOUBLE 9007199254740992.0 // 2^52
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
//| Fast ceil, floor, and round using arithmetic operators. |
2026-08-02 14:42:54 +03:00
//| NaN, Infinity, and values with |v| >= 2^52 are returned |
//| unchanged (see MU_MAX_FRACTIONAL_DOUBLE above). |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
2026-08-02 14:42:54 +03:00
double Ceil(const double v)
{
if(!MathIsValidNumber(v) || MathAbs(v) >= MU_MAX_FRACTIONAL_DOUBLE)
return v;
const double k = (double)(long)v;
return k + (v > k);
}
double Floor(const double v)
{
if(!MathIsValidNumber(v) || MathAbs(v) >= MU_MAX_FRACTIONAL_DOUBLE)
return v;
const double k = (double)(long)v;
return k - (v < k);
}
double Round(const double v)
{
if(!MathIsValidNumber(v) || MathAbs(v) >= MU_MAX_FRACTIONAL_DOUBLE)
return v;
return (double)(long)((v < 0) ? v - 0.5 : v + 0.5);
}
double Trunc(const double v)
{
if(!MathIsValidNumber(v) || MathAbs(v) >= MU_MAX_FRACTIONAL_DOUBLE)
return v;
return (double)(long)v;
}
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
//| Round to a specified number of decimal digits. |
//+------------------------------------------------------------------+
/*
* https://stackoverflow.com/a/48764436/4208440
*
* A simple drop in solution that provides accurate decimal rounding.
* It treats doubles more like decimals by fixing the binary rounding
* issues to avoid unexpected results.
*/
2026-07-26 11:42:21 +03:00
double Ceil (const double value, const int digits) { double p = GetPower10(digits); return Ceil ((value * p) * (1.0 - DBL_EPSILON * SignDbl(value))) / p; }
double Floor(const double value, const int digits) { double p = GetPower10(digits); return Floor((value * p) * (1.0 + DBL_EPSILON * SignDbl(value))) / p; }
double Round(const double value, const int digits) { double p = GetPower10(digits); return Round((value * p) * (1.0 + DBL_EPSILON)) / p; }
double Trunc(const double value, const int digits) { double p = GetPower10(digits); return Trunc((value * p) * (1.0 + DBL_EPSILON)) / p; }
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Round to a whole multiple of some increment step. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
double Ceil (const double value, const double step) { return (step <= 0) ? value : StripError(Ceil (StripError(value / step)) * step); }
double Floor(const double value, const double step) { return (step <= 0) ? value : StripError(Floor(StripError(value / step)) * step); }
double Round(const double value, const double step) { return (step <= 0) ? value : StripError(Round(StripError(value / step)) * step); }
double Trunc(const double value, const double step) { return (step <= 0) ? value : StripError(Trunc(StripError(value / step)) * step); }
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
//| Round to a specified number of significant figures. |
//+------------------------------------------------------------------+
/**
* https://en.wikipedia.org/wiki/Significant_figures
* https://github.com/php/php-src/blob/PHP-7.4.10/ext/standard/math.c#L127-L206
* https://stackoverflow.com/a/374470/4208440
*/
double RoundToSignificantDigits(const double value, int digits)
{
if(!value || !MathIsValidNumber(value))
return value;
//--- Round to significant digits
digits -= FloorLog10(value) + 1;
2026-07-26 11:42:21 +03:00
const double p = GetPower10(MathAbs(digits));
2026-07-25 16:49:20 +03:00
return digits > 0 ? Round(value * p) / p : Round(value / p) * p;
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Strip intermediate binary floating-point round-off errors. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
double StripError(const double value)
{
//--- if the number is whole integer
if(Floor(value) == value)
{
return value;
}
//--- DBL_DIG: Number of significant decimal digits for double type = 15
return RoundToSignificantDigits(value, DBL_DIG);
}
//+------------------------------------------------------------------+
//| Round a calculated price to the nearest tick price. |
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
double RoundPrice(const double pPrice, const string pSymbol = NULL)
2026-07-25 16:49:20 +03:00
{
2026-07-26 11:42:21 +03:00
const string sym = (pSymbol == NULL) ? _Symbol : pSymbol;
const double ticksize = SymbolInfoDouble(sym, SYMBOL_TRADE_TICK_SIZE);
2026-07-25 16:49:20 +03:00
2026-07-26 11:42:21 +03:00
if(ticksize <= 0.0)
2026-07-25 16:49:20 +03:00
{
2026-07-26 11:42:21 +03:00
Print(__FUNCTION__, ": Warning, cannot retrieve ticksize for " + sym);
return pPrice;
2026-07-25 16:49:20 +03:00
}
return Round(pPrice, ticksize);
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Round a volume according to broker volume min/max/step limits. |
//| Note: Returns 0.0 if volume is strictly below SYMBOL_VOLUME_MIN. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
double RoundVolume(const double pVolume, const string pSymbol = NULL)
2026-07-25 16:49:20 +03:00
{
2026-07-26 11:42:21 +03:00
const string sym = (pSymbol == NULL) ? _Symbol : pSymbol;
2026-07-25 16:49:20 +03:00
2026-07-26 11:42:21 +03:00
const double minlot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
const double maxlot = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
const double lotstep = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
2026-07-25 16:49:20 +03:00
2026-07-26 11:42:21 +03:00
if(minlot == 0.0 || maxlot == 0.0 || lotstep == 0.0)
2026-07-25 16:49:20 +03:00
{
2026-07-26 11:42:21 +03:00
Print(__FUNCTION__, ": Warning, cannot retrieve volume info for " + sym);
return pVolume;
2026-07-25 16:49:20 +03:00
}
2026-08-02 14:42:54 +03:00
//--- tolerance scales with lotstep instead of a fixed constant, which
//--- would be too coarse for instruments whose volume step is smaller
//--- (e.g. some crypto CFDs quote SYMBOL_VOLUME_STEP as 0.00000001)
const double Epsilon = lotstep * 0.5;
2026-07-26 11:42:21 +03:00
double volume = pVolume - minlot;
2026-07-25 16:49:20 +03:00
2026-07-26 11:42:21 +03:00
if(volume < -Epsilon)
return 0.0;
2026-07-25 16:49:20 +03:00
2026-07-26 11:42:21 +03:00
if(volume < Epsilon)
return minlot;
2026-07-25 16:49:20 +03:00
2026-07-26 11:42:21 +03:00
volume = minlot + Round(volume, lotstep);
2026-07-25 16:49:20 +03:00
2026-07-26 11:42:21 +03:00
if(volume > maxlot)
volume = maxlot;
2026-07-25 16:49:20 +03:00
2026-07-26 11:42:21 +03:00
return volume;
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
//| III. Miscellaneous handy functions for doubles. |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Determines whether the passed value is an integer. |
//+------------------------------------------------------------------+
bool IsInteger(const double value)
{
// return (MathMod(value, 1.0) == 0)
return Round(value) == value;
}
//+------------------------------------------------------------------+
//| Checks if a number has a specified number of decimal digits. |
//+------------------------------------------------------------------+
bool IsRound(const double value, const int digits)
{
// return value == StringToDouble(DoubleToString(value, digits));
2026-07-26 11:42:21 +03:00
const double p = GetPower10(digits);
2026-07-25 16:49:20 +03:00
return MathRound(value * p) / p == value;
}
//+------------------------------------------------------------------+
//| Checks if a number is a whole multiple of some increment. |
//+------------------------------------------------------------------+
bool IsRound(const double value, const double step)
{
return Round(value, step) == value;
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Get the decimal fractional part (retains sign). |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
double Frac(const double value)
{
return MathMod(value, 1.0);
}
//+------------------------------------------------------------------+
//| Get number of decimal digits after the decimal point. |
//+------------------------------------------------------------------+
int GetDigits(const double value)
{
int d = 0;
// for(double p = 1; value != MathRound(value * p) / p && (d < 308); p *= 10)
while(!IsRound(value, d) && d < DBL_MAX_10_EXP)
d++;
return d;
}
//+------------------------------------------------------------------+
//| Get number of integer digits to the left of decimal point. |
2026-08-02 14:42:54 +03:00
//| Returns 0 for NaN or Infinity. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
int GetIntegerDigits(const double value)
{
2026-08-02 14:42:54 +03:00
if(!MathIsValidNumber(value) || value == 0.0 || MathAbs(value) < 1.0)
2026-07-25 16:49:20 +03:00
return 0;
return FloorLog10(value) + 1;
}
//+------------------------------------------------------------------+
//| Get number of significant digits. Significant digits or figures |
//| is the sum of integer and decimal digits (left and right of the |
//| decimal point), excluding leading and trailing zeros. |
//| For example, the number 1.23 has 2 decimal places and 3 s.f. |
//| Hint: Change number to scientific notation. It is easier to see. |
//+------------------------------------------------------------------+
int GetSignificantDigits(double value)
{
if(!value || !MathIsValidNumber(value))
return 0;
//--- sum of decimal and integral digits
int digits = GetDigits(value) + FloorLog10(value) + 1;
//--- excluding trailing zeros
2026-07-26 11:42:21 +03:00
while(MathMod(value, 10.0) == 0.0)
2026-07-25 16:49:20 +03:00
{
2026-07-26 11:42:21 +03:00
value /= 10.0;
2026-07-25 16:49:20 +03:00
digits--;
}
return digits;
}
//+------------------------------------------------------------------+
//| Linear Transformation (RESCALE) |
//| Convert x in range [min, max] to y in another range [destMin, |
//| destMax]. This can be used to rescale indicator values or |
//| graphic objects. Common scales are [0,1], [0,100] or [-1,1]. |
//+------------------------------------------------------------------+
double Remap(const double value, const double min, const double max, const double destMin, const double destMax)
{
//--- to maintain ratios
double y = safeDiv((value - min), (max - min)) * (destMax - destMin) + destMin;
return Clamp(y, destMin, destMax);
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Linear Interpolation (Lerp) between start and end by factor t. |
//+------------------------------------------------------------------+
double Lerp(const double start, const double end, const double t)
{
return start + t * (end - start);
}
//+------------------------------------------------------------------+
//| Remap value from scale [min, max] to normalized scale [0, 1]. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
double Normalize(const double value, const double min, const double max)
{
2026-07-26 11:42:21 +03:00
return Remap(value, min, max, 0.0, 1.0);
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
//| Remap normalized value [0, 1] to a scale [destMin, destMax]. |
//+------------------------------------------------------------------+
double Denormalize(const double value, const double destMin, const double destMax)
{
return Remap(value, 0, 1, destMin, destMax);
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Clamp a value into closed interval [min, max]. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
template<typename T>
T Clamp(const T value, const T min, const T max)
{
//if(value < min) return min;
//if(value > max) return max;
//return value;
2026-07-26 11:42:21 +03:00
T lo = MathMin(min, max);
T hi = MathMax(min, max);
return MathMin(MathMax(value, lo), hi);
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Clamp a value to a maximum upper threshold. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
template<typename T>
T ClampMax(const T value, const T max)
{
return MathMin(value, max);
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Clamp a value to a minimum lower threshold. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
template<typename T>
T ClampMin(const T value, const T min)
{
return MathMax(value, min);
}
//+------------------------------------------------------------------+
//| Wrap a value into the inclusive interval [min, max]. |
2026-08-02 14:42:54 +03:00
//| Requires max >= min; returns min unchanged otherwise. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
// https://stackoverflow.com/a/14416133
template<typename T>
T Wrap(const T value, const T min, const T max)
{
2026-08-02 14:42:54 +03:00
if(max < min)
return min;
2026-07-25 16:49:20 +03:00
const T range = max - min + 1;
//while(value < min) value += range;
//while(value > max) value -= range;
//return value;
if(value < min)
return max - (T)MathMod((max - value), range);
if(value > max)
return min + (T)MathMod((value - min), range);
return value;
}
2026-07-26 11:42:21 +03:00
// Overload for double
2026-07-25 16:49:20 +03:00
double Wrap(const double value, const double min, const double max)
{
const double range = max - min;
if(range <= 0.0)
return min;
return min + MathMod(MathMod((value - min), range) + range, range);
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Division helper preventing zero-division errors. |
2026-07-25 16:49:20 +03:00
//| Returns zero if denominator is zero. |
//+------------------------------------------------------------------+
double safeDiv(const double a, const double b)
{
if(b == 0.0)
return 0.0;
2026-07-26 11:42:21 +03:00
return a / b;
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
//| Fast lookup table for exact powers of 10. |
//+------------------------------------------------------------------+
const double PowersOf10[] =
{
1e00, 1e01, 1e02, 1e03, 1e04, 1e05, 1e06, 1e07,
1e08, 1e09, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15,
1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
};
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Fast base-10 exponent solver (floor(log10(value))). |
2026-07-25 16:49:20 +03:00
//| |
2026-08-02 14:42:54 +03:00
//| Returns INT_MIN as a sentinel for value == 0.0, NaN, or Infinity |
//| (log10 is undefined for these). |
//| |
2026-07-25 16:49:20 +03:00
//| Uses the relationship log10(x) = log2(x) * log10(2) |
//| http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 |
//+------------------------------------------------------------------+
int FloorLog10(double value)
{
2026-07-26 11:42:21 +03:00
if(value == 0.0 || !MathIsValidNumber(value))
return INT_MIN;
2026-07-25 16:49:20 +03:00
int result = 0;
2026-07-26 11:42:21 +03:00
value = MathAbs(value);
2026-07-25 16:49:20 +03:00
/* Not in lookup table */
2026-07-26 11:42:21 +03:00
if(value < 1e-22 || value > 1e22)
2026-07-25 16:49:20 +03:00
{
2026-07-26 11:42:21 +03:00
result = (int)MathFloor(MathLog10(value));
2026-07-25 16:49:20 +03:00
}
else
{
2026-07-26 11:42:21 +03:00
const int e = GetExponent(value); /* floor(log2(x)) */
const int t = ((e + 1) * 20201781) >> 26; /* log10(2) ~= 20201781/2^26 */
2026-07-25 16:49:20 +03:00
if(t >= 0)
result = t - (value < PowersOf10[t]); /* t may be off by one */
else
2026-07-26 11:42:21 +03:00
result = t - (value < 1.0 / PowersOf10[-t]);
2026-07-25 16:49:20 +03:00
}
return result;
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Fast integer powers of 10 lookup routine. |
2026-07-25 16:49:20 +03:00
//| https://github.com/php/php-src/blob/master/ext/standard/math |
//+------------------------------------------------------------------+
double GetPower10(const int power)
{
/* Not in lookup table */
2026-07-26 11:42:21 +03:00
if(power < -22 || power > 22)
return MathPow(10.0, (double)power);
2026-07-25 16:49:20 +03:00
if(power >= 0)
return PowersOf10[power];
2026-07-26 11:42:21 +03:00
return 1.0 / PowersOf10[-power];
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Computes the sign of a value as 1, 0, or -1. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
2026-07-26 01:42:17 +03:00
int SignDbl(const double value)
2026-07-25 16:49:20 +03:00
{
2026-07-26 11:42:21 +03:00
return (value > 0.0) - (value < 0.0);
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
//| IV. Handy functions for low-level binary operations on doubles. |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Returns the bit representation corresponding to a double value . |
//+------------------------------------------------------------------+
long DoubleToLongBits(const double value)
{
union _d {double value; long bits;} dbl;
dbl.value = value;
return dbl.bits;
}
//+------------------------------------------------------------------+
//| Returns the double value corresponding to a bit representation. |
//+------------------------------------------------------------------+
double LongBitsToDouble(const long bits)
{
union _d {double value; long bits;} dbl;
dbl.bits = bits;
return dbl.value;
}
//+------------------------------------------------------------------+
//| Converts the IEEE-754 bit representation of a double into an |
//| integer ordering that preserves the numeric ordering of doubles. |
//+------------------------------------------------------------------+
long DoubleToOrderedLong(const double value)
{
long bits = DoubleToLongBits(value);
if(bits < 0)
2026-08-02 14:42:54 +03:00
bits = MU_SIGN_BIT - bits;
2026-07-25 16:49:20 +03:00
return bits;
}
//+------------------------------------------------------------------+
//| Converts an ordered integer representation produced by |
//| DoubleToOrderedLong() back into the original IEEE-754 double. |
//+------------------------------------------------------------------+
double OrderedLongToDouble(long bits)
{
if(bits < 0)
2026-08-02 14:42:54 +03:00
bits = MU_SIGN_BIT - bits;
2026-07-25 16:49:20 +03:00
return LongBitsToDouble(bits);
}
//+------------------------------------------------------------------+
//| Returns the raw encoding of exponent in the bit representation. |
//| |
//| Valid only for finite values. |
//| Results for NaN and ±Infinity are unspecified. |
//+------------------------------------------------------------------+
int RawExponent(const double value)
{
2026-08-02 14:42:54 +03:00
return (int)((DoubleToLongBits(value) >> MU_MANTISSA_BITS) & MU_EXPONENT_MASK);
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
//| Returns raw encoding of significand in the bit representation. |
//| |
//| Valid only for finite values. |
//| Results for NaN and ±Infinity are unspecified. |
//+------------------------------------------------------------------+
long RawSignificand(const double value)
{
2026-08-02 14:42:54 +03:00
return (long)(DoubleToLongBits(value) & MU_MANTISSA_MASK);
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
//| Returns the unbiased (adjusted) exponent of a double value. |
//| |
2026-08-02 14:42:54 +03:00
//| Valid only for finite, normal values. |
//| Results for NaN, ±Infinity, and subnormal values are unspecified.|
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
int GetExponent(const double value)
{
// return (int) MathFloor(MathLog(MathAbs(value)) / M_LN2);
2026-08-02 14:42:54 +03:00
return RawExponent(value) - MU_EXPONENT_BIAS;
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
//| Returns the significand of a double value. For finite nonzero |
//| numbers, the significand is in the range 1.0 ..< 2.0. |
//| An IEEE-754 double number = (Sign) 2 ^ Exponent * Significand. |
//| |
2026-08-02 14:42:54 +03:00
//| Valid only for finite, normal values. |
//| Results for NaN, ±Infinity, and subnormal values are unspecified.|
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
double GetSignificand(const double value)
{
// return 1.0 + RawSignificand(value) / MathPow(2, 52);
2026-07-26 11:42:21 +03:00
return LongBitsToDouble(RawSignificand(value) | 0x3FF0000000000000); // 0x3FF0... = exponent bits for 2^0
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
//| Determine whether number is exactly representable in double. |
//| i.e., No rounding to an approximation during the conversion. |
//| Results are valid for numbers in the range [2^-24, 2^52]. |
2026-08-02 14:42:54 +03:00
//| Returns false for NaN and Infinity. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
//https://stackoverflow.com/a/67952907/4208440
bool IsExactDouble(const double value)
{
2026-08-02 14:42:54 +03:00
if(!MathIsValidNumber(value))
return false;
2026-07-26 11:42:21 +03:00
if(value == 0.0)
2026-07-25 16:49:20 +03:00
return true;
2026-07-26 11:42:21 +03:00
const int exp2 = GetExponent(value);
const int exp10 = FloorLog10(value);
2026-07-25 16:49:20 +03:00
//--- check for any mismatch between the exact decimal and
//--- the round-trip representation.
2026-07-26 11:42:21 +03:00
const int rightmost_bits = (52 - exp2) - (16 - exp10);
2026-07-26 04:58:47 +03:00
//--- guard against undefined shift behavior at the boundaries
if(rightmost_bits <= 0)
2026-07-26 11:42:21 +03:00
return true; // integer or exact power-of-two
2026-07-26 04:58:47 +03:00
if(rightmost_bits >= 63)
2026-07-26 11:42:21 +03:00
return false; // outside [2^-24, 2^52] range
2026-07-25 16:49:20 +03:00
//--- create bitmask for rightmost bits
2026-07-26 11:42:21 +03:00
const ulong mask = ((ulong)1 << rightmost_bits) - 1;
2026-07-25 16:49:20 +03:00
//--- test if all rightmost bits are 0's (i.e., no rounding)
return (DoubleToLongBits(value) & mask) == 0;
}
//+------------------------------------------------------------------+
//| Returns the next representable value after x away from zero. |
//| |
//| Valid for all finite values, including zero and subnormals. |
//| NaN is returned unchanged. |
//| Advancing beyond ±DBL_MAX returns ±Infinity. |
//+------------------------------------------------------------------+
double NextAwayFromZero(const double value)
{
2026-07-26 11:42:21 +03:00
return DoubleAdvance(value, value >= 0.0 ? 1 : -1);
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
//| Advances a floating-point number by a specified number of ULPs. |
//| distance may be negative. |
//| |
//| Valid for all finite values, including zero and subnormals. |
//| NaN is returned unchanged. |
//| Advancing beyond ±DBL_MAX returns ±Infinity. |
//+------------------------------------------------------------------+
double DoubleAdvance(const double value, const long distance)
{
if(!MathIsValidNumber(value))
return value;
2026-07-26 11:42:21 +03:00
const long bits = DoubleToOrderedLong(value);
2026-07-25 16:49:20 +03:00
return OrderedLongToDouble(bits + distance);
}
//+------------------------------------------------------------------+
//| Returns the size of a unit in the last place (machine epsilon) |
//| for a specified double value. This is the unit of the least |
//| significant digit in this value’s significand. For most numbers, |
//| this is the positive distance between this double value and the |
//| representable value next larger in magnitude. |
//| Note that, Ulp(1.0) == DBL_EPSILON. (i.e., epsilon at 1.0) |
//+------------------------------------------------------------------+
double Ulp(const double value)
{
if(!MathIsValidNumber(value))
return value;
return MathAbs(NextAwayFromZero(value) - value);
}
//+------------------------------------------------------------------+
//| Returns the distance between two doubles a and b expressed as |
//| the number of gaps/bits/ULP (Units in the Last Place) between a |
//| and b. Note that the function returns a signed value indicating |
//| whether a > b or not. |
//+------------------------------------------------------------------+
long UlpDiff(const double a, const double b)
{
2026-07-26 11:42:21 +03:00
return DoubleToOrderedLong(a) -
DoubleToOrderedLong(b);
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
//| V. Handy functions for formatting of doubles to string. |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Converting numeric value into the raw hexadecimal text string. |
//| https://baseconvert.com/ieee-754-floating-point |
//+------------------------------------------------------------------+
string DoubleToHexadecimal(const double value)
{
2026-07-26 11:42:21 +03:00
return StringFormat("0x%.16I64X", DoubleToLongBits(value));
2026-07-25 16:49:20 +03:00
}
//+------------------------------------------------------------------+
//| Converting numeric value into the hex float constant string. |
//| A real number in format [−]0xh.hhhh P±dd, where h.hhhh – mantissa|
//| in the form of hexadecimal digits, using "ABCDEF", dd - One or |
//| more digits of exponent. |
//+------------------------------------------------------------------+
string DoubleToHexFloatConstant(const double value)
{
return StringFormat("%.13a", value);
}
//+------------------------------------------------------------------+
//| Converting numeric value into the exact decimal text string of |
//| the binary approximation stored by the machine. |
//| https://baseconvert.com/ieee-754-floating-point |
//+------------------------------------------------------------------+
string DoubleToStringExact(const double value)
{
if(!value || !MathIsValidNumber(value))
return (string)value;
//--- maximum number of decimal places = number of fraction bits
2026-07-26 11:42:21 +03:00
const int exponent = (int)MathFloor(MathLog(MathAbs(value)) / M_LN2);
const int digits = MathMax(0, 52 - exponent);
2026-07-25 16:49:20 +03:00
//--- https://www.exploringbinary.com/number-of-decimal-digits-in-a-binary-fraction/
#ifdef __MQL4__
return StringFormat("%." + (string)digits + "f", value);
#else
return StringFormat("%.*f", digits, value);
#endif
}
//+------------------------------------------------------------------+
//| Converting numeric value into a string in scientific notation |
//| with one digit before the decimal point (e.g., 6.22e-23). |
//| Digits : Optional. The number of digits after the decimal point. |
//| Defaults to as many digits as necessary to represent the value. |
//+------------------------------------------------------------------+
string DoubleToExponential(const double value, int digits = -1)
{
if(!value || !MathIsValidNumber(value) || digits > 20)
return (string)value;
//--- as many digits as necessary to represent the value
//--- https://www.w3schools.com/jsref/jsref_toexponential.asp
if(digits < 0)
digits = GetSignificantDigits(value) - 1;
//---
#ifdef __MQL4__
return StringFormat("%." + (string)digits + "e", value);
#else
return StringFormat("%.*e", digits, value);
#endif
}
//+------------------------------------------------------------------+
//| Converting numeric value into the shortest string representation |
//| that round-trips into the same numeric value. The result will |
//| contain at most 17 significant digits, discarding trailing zeros.|
//| The round-trip ("%.17g") format specifier ensures that a numeric |
//| value converted to a string is always parsed back into the same |
//| numeric value, StringToDouble(Repr(f)) == f. |
//| Note: results are consistent with David M. Gay's dtoa.c library. |
//+------------------------------------------------------------------+
// toString()
string Repr(const double value)
{
//--- https://stackoverflow.com/a/35708911/4208440
//--- https://www.exploringbinary.com/number-of-digits-required-for-round-trip-conversions/
//--- Try format with 15, 16, 17 significant digits to return the shortest
//--- decimal numeric string which round-trips to the specified value.
string str = NULL;
for(int sig = 15; sig <= 17; sig++)
{
#ifdef __MQL4__
if(value == StringToDouble(str = StringFormat("%." + (string)sig + "g", value)))
break;
#else
if(value == StringToDouble(str = StringFormat("%.*g", sig, value)))
break;
#endif
}
return str;
}
//+------------------------------------------------------------------+
//| Converting float value into the shortest string representation |
//| that round-trips into the same numeric value. This ensures that |
//| a float value converted to a string is always parsed back into |
//| the same numeric value. i.e., (float)(Repr(f)) == f. |
//+------------------------------------------------------------------+
// toString()
string Repr(const float value)
{
//--- Try format with 6, 7, 8, 9 significant digits to return the shortest
//--- decimal numeric string which round-trips to the specified value.
string str = NULL;
for(int sig = 6; sig <= 9; sig++)
{
#ifdef __MQL4__
if(value == float(str = StringFormat("%." + (string)sig + "g", value)))
break;
#else
if(value == float(str = StringFormat("%.*g", sig, value)))
break;
#endif
}
return str;
}
//+------------------------------------------------------------------+
2026-07-26 11:42:21 +03:00
//| Make a diagnostic dump of a double for use in Print()/debugging. |
//| Combines Repr, hex bit pattern, exponent, and significand into |
//| a single readable string, e.g.: |
//| DoubleDebugInfo(0.1) |
//| => "0.1 [hex=0x3FB999999999999A, exp=-4, significand=1.6]" |
//+------------------------------------------------------------------+
string DoubleDebugInfo(const double value)
{
if(!MathIsValidNumber(value))
return Repr(value) + " [non-finite]";
if(value == 0.0)
return Repr(value) + " [hex=" + DoubleToHexadecimal(value) + "]";
return StringFormat("%s [hex=%s, exp=%d, significand=%s]",
Repr(value),
DoubleToHexadecimal(value),
GetExponent(value),
Repr(GetSignificand(value)));
}
//+------------------------------------------------------------------+
2026-07-25 16:49:20 +03:00
//| Formats double with thousands separator and specified decimals. |
2026-08-02 14:42:54 +03:00
//| Note: digits can be only 0-8 decimal places. |
2026-07-25 16:49:20 +03:00
//+------------------------------------------------------------------+
/**
* Alert( FormatDouble(balance, 2) );
*/
string FormatDouble(const double value, const int digits, const string separator=",")
{
2026-07-26 11:42:21 +03:00
const double numb = MathAbs(value);
const string sign = (value < 0.0) ? "-" : "";
2026-07-25 16:49:20 +03:00
string num_str = DoubleToString(NormalizeDouble(numb, digits), digits); // FormatDouble(1.005, 2) => "1.01"
//--- Find the length of the integer part
int pos = StringFind(num_str, ".");
int length = (pos > -1) ? pos : StringLen(num_str);
//--- Format the integer part with thousand separators
for(int i = length - 3; i > 0; i -= 3)
{
// insert a separator at position i
string tmp = StringSubstr(num_str, 0, i);
tmp += separator;
num_str = tmp + StringSubstr(num_str, i);
}
return sign + num_str;
}
#endif // #ifndef MATH_UTILS_UNIQUE_HEADER_ID_H