//+------------------------------------------------------------------+ //| math_utils.mqh | //| Copyright © 2018, Amr Ali | //| https://www.mql5.com/en/users/amrali | //+------------------------------------------------------------------+ #property copyright "Copyright © 2018, Amr Ali" #property link "https://www.mql5.com/en/users/amrali" #property version "4.35" #property description "Handy functions for comparison, rounding, formatting and debugging of doubles (prices, lots and money)." #ifdef __MQL4__ #property strict #endif #ifndef MATH_UTILS_UNIQUE_HEADER_ID_H #define MATH_UTILS_UNIQUE_HEADER_ID_H //+------------------------------------------------------------------+ //| 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); 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); double RoundPrice(double pPrice, string pSymbol = NULL); double RoundVolume(double pVolume, string pSymbol = NULL); 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); 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 ClampTop(const T value, const T max); T ClampBot(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); int FloorLog10(const double value); double GetPower10(const int power); double MathSign(const double value); IV. Handy functions for low-level binary operations on doubles -------------------------------------------------------------- long DoubleToLongBits(const double value); double LongBitsToDouble(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 NextAfter(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); 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 //+------------------------------------------------------------------+ //| Compares two doubles and returns a value indicating whether one | //| is nearly equal to, less than, or greater than the other. | //+------------------------------------------------------------------+ int Compare(const double a, const double b, const int digits) { // bool res = MathAbs(a - b) < 0.5 * MathPow(10, -digits); bool res = NormalizeDouble(a - b, digits) == 0; //--- if(res) return 0; else return (a > b) ? 1 : -1; } //+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+ //| Check almost equality (ignore tiny rounoff error) | //| 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; } //+------------------------------------------------------------------+ //| Check whether two numbers are equal up to "n" significant | //| digits of precision (at least "n" significant digits agree). | //| 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)); } //+------------------------------------------------------------------+ //| Get number of fractional digits that agree after the decimal | //| point of two numbers. GetEqualDigits(3.124, 3.122) => 2 | //+------------------------------------------------------------------+ int GetEqualDigits(const double a, const double b) { //--- https://stackoverflow.com/a/49242211 return (a == b) ? 30 : MathMax(-FloorLog10(MathAbs(a - b)) - 1, 0); } //+------------------------------------------------------------------+ //| Get number of significant digits that agree of two numbers. | //| For example, GetEqualSigDigits(3.124, 3.122) => 3 | //+------------------------------------------------------------------+ int GetEqualSigDigits(const double a, const double b) { return (a == b) ? 17 : FloorLog10(MathMax(MathAbs(a), MathAbs(b))) - FloorLog10(MathAbs(a - b)); } //+------------------------------------------------------------------+ //| II. Handy functions for rounding of doubles. | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Fast ceil, floor, and round using arithmetic operators. | //+------------------------------------------------------------------+ double Ceil (const double v) { double k=(double)(long)v; return k + (v > k); } double Floor(const double v) { double k=(double)(long)v; return k - (v < k); } double Round(const double v) { return (double)(long)((v < 0) ? v - 0.5 : v + 0.5); } double Trunc(const double v) { return (double)(long)v; } //+------------------------------------------------------------------+ //| 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. */ double Ceil (const double value, const int digits) { double p = GetPower10(digits); return Ceil ((value * p) * (1 - DBL_EPSILON * MathSign(value))) / p; } double Floor(const double value, const int digits) { double p = GetPower10(digits); return Floor((value * p) * (1 + DBL_EPSILON * MathSign(value))) / p; } double Round(const double value, const int digits) { double p = GetPower10(digits); return Round((value * p) * (1 + DBL_EPSILON)) / p; } double Trunc(const double value, const int digits) { double p = GetPower10(digits); return Trunc((value * p) * (1 + DBL_EPSILON)) / p; } //+------------------------------------------------------------------+ //| Round to a whole multiple of some increment. | //+------------------------------------------------------------------+ double Ceil (const double value, const double step) { return StripError(Ceil (StripError(value / step)) * step); } double Floor(const double value, const double step) { return StripError(Floor(StripError(value / step)) * step); } double Round(const double value, const double step) { return StripError(Round(StripError(value / step)) * step); } double Trunc(const double value, const double step) { return StripError(Trunc(StripError(value / step)) * step); } //+------------------------------------------------------------------+ //| 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; double p = GetPower10(MathAbs(digits)); return digits > 0 ? Round(value * p) / p : Round(value / p) * p; } //+------------------------------------------------------------------+ //| Eliminate floating-point inaccuracies. Round to 15 significant | //| figures to strip the floating-point round-off error (at the 16th | //| significant digit) in the intermediate calculations. | //+------------------------------------------------------------------+ 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. | //+------------------------------------------------------------------+ double RoundPrice(double pPrice, string pSymbol = NULL) { pSymbol = pSymbol == NULL ? _Symbol : pSymbol; double ticksize = SymbolInfoDouble(pSymbol, SYMBOL_TRADE_TICK_SIZE); if(ticksize == 0) { Print(__FUNCTION__, ": error, cannot retrieve ticksize for " + pSymbol); return (0); } return Round(pPrice, ticksize); } //+------------------------------------------------------------------+ //| Round a calculated volume to the nearest minlot + multiple*step. | //+------------------------------------------------------------------+ double RoundVolume(double pVolume, string pSymbol = NULL) { pSymbol = pSymbol == NULL ? _Symbol : pSymbol; double minlot = SymbolInfoDouble(pSymbol, SYMBOL_VOLUME_MIN); double maxlot = SymbolInfoDouble(pSymbol, SYMBOL_VOLUME_MAX); double lotstep = SymbolInfoDouble(pSymbol, SYMBOL_VOLUME_STEP); if(minlot == 0 || maxlot == 0 || lotstep == 0) { Print(__FUNCTION__, ": error, cannot retrieve volume info for " + pSymbol); return (0); } const double Epsilon = 0.000001; pVolume -= minlot; if(pVolume < -Epsilon) { return (0); } if(pVolume < Epsilon) { return (minlot); } pVolume = minlot + Round(pVolume, lotstep); if(pVolume > maxlot) { pVolume = maxlot; } return (pVolume); } //+------------------------------------------------------------------+ //| 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)); double p = GetPower10(digits); 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; } //+------------------------------------------------------------------+ //| Get the decimal part of x (always has the same sign as x) | //+------------------------------------------------------------------+ 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. | //+------------------------------------------------------------------+ int GetIntegerDigits(const double value) { if(!value || MathAbs(value) < 1.0) 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 while(MathMod(value, 10) == 0) { value /= 10; 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); } //+------------------------------------------------------------------+ //| Remap value on a scale [min, max] to normalized scale [0, 1]. | //+------------------------------------------------------------------+ double Normalize(const double value, const double min, const double max) { return Remap(value, min, max, 0, 1); } //+------------------------------------------------------------------+ //| 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); } //+------------------------------------------------------------------+ //| Clamping (limiting) a number to boundaries (range). | //+------------------------------------------------------------------+ template T Clamp(const T value, const T min, const T max) { //if(value < min) return min; //if(value > max) return max; //return value; return MathMin(MathMax(value, min), max); } //+------------------------------------------------------------------+ //| Clamping (limiting) a number to a maximum value. | //+------------------------------------------------------------------+ template T ClampTop(const T value, const T max) { return MathMin(value, max); } //+------------------------------------------------------------------+ //| Clamping (limiting) a number to a minimum value. | //+------------------------------------------------------------------+ template T ClampBot(const T value, const T min) { return MathMax(value, min); } //+------------------------------------------------------------------+ //| Wrap a value into range between [min, max]. | //+------------------------------------------------------------------+ // https://stackoverflow.com/a/14416133 template T Wrap(const T value, const T min, const T max) { 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; } //+------------------------------------------------------------------+ //| Avoids zero divide error that forces the mql program to stop. | //+------------------------------------------------------------------+ double safeDiv(const double a, const double b) { //--- force double division. return (b != 0) ? a / b : 0; } //+------------------------------------------------------------------+ //| 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 }; //+------------------------------------------------------------------+ //| Returns the exponent of the scientific notation of a number. | //| In scientific notation a number is converted to a decimal number | //| between 1.0 and 10, multiplied by 10 raised to some power. | //| It computes shift the decimal point to keep only one non-zero | //| digit before the decimal point. | //| | //| Faster than floor(log10(n)): 3x | //| Uses the relationship log10(x) = log2(x) * log10(2) | //| http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 | //+------------------------------------------------------------------+ int FloorLog10(double value) { int result = 0; value = fabs(value); /* Not in lookup table */ if (value < 1e-22 || value > 1e22) { result = (int) Floor(log10(value)); } else { int e = GetExponent(value); /* floor(log2(x)) */ int t = ((e + 1) * 20201781) >> 26; /* log10(2) ~= 20201781/2^26 */ if(t >= 0) result = t - (value < PowersOf10[t]); /* t may be off by one */ else result = t - (value < 1 / PowersOf10[-t]); } return result; } //+------------------------------------------------------------------+ //| Returns pow(10, (int)power), uses fast lookup table for powers. | //| https://github.com/php/php-src/blob/master/ext/standard/math | //| Faster than pow(10, n): 20-30x | //+------------------------------------------------------------------+ double GetPower10(const int power) { /* Not in lookup table */ if (power < -22 || power > 22) { return pow(10.0, (double)power); } if(power >= 0) return PowersOf10[power]; return 1 / PowersOf10[-power]; } //+------------------------------------------------------------------+ //| Computes the sign of a value as 1, 0, -1 | //+------------------------------------------------------------------+ /** * It is recommended that a function return 1, 0, and -1, respectively. */ double MathSign(const double value) { return (value > 0) - (value < 0); } //+------------------------------------------------------------------+ //| 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; } //+------------------------------------------------------------------+ //| Returns the raw encoding of exponent in the bit representation. | //+------------------------------------------------------------------+ int RawExponent(const double value) { return (int)((DoubleToLongBits(value) >> 52) & 0x7FF); } //+------------------------------------------------------------------+ //| Returns raw encoding of significand in the bit representation. | //+------------------------------------------------------------------+ long RawSignificand(const double value) { return (long)(DoubleToLongBits(value) & 0xFFFFFFFFFFFFF); } //+------------------------------------------------------------------+ //| Returns the unbiased (adjusted) exponent of a double value. | //+------------------------------------------------------------------+ int GetExponent(const double value) { // return (int) MathFloor(MathLog(MathAbs(value)) / M_LN2); return RawExponent(value) - 1023; } //+------------------------------------------------------------------+ //| 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. | //+------------------------------------------------------------------+ double GetSignificand(const double value) { // return 1.0 + RawSignificand(value) / MathPow(2, 52); return LongBitsToDouble(RawSignificand(value) | 0x3FF0000000000000); } //+------------------------------------------------------------------+ //| 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]. | //+------------------------------------------------------------------+ //https://stackoverflow.com/a/67952907/4208440 bool IsExactDouble(const double value) { if(value == 0) return true; int exp2 = GetExponent(value); int exp10 = FloorLog10(value); //--- check for any mismatch between the exact decimal and //--- the round-trip representation. int rightmost_bits = (52 - exp2) - (16 - exp10); //--- create bitmask for rightmost bits ulong mask = ((ulong)1 << rightmost_bits) - 1; //--- 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. | //+------------------------------------------------------------------+ double NextAfter(const double value) { long bits = DoubleToLongBits(value); //--- Increment the integer representation to move to //--- the next representable value away from zero. return LongBitsToDouble(bits + 1); } //+------------------------------------------------------------------+ //| Advances a floating-point number by a specified number of ULPs. | //+------------------------------------------------------------------+ double DoubleAdvance(const double value, const long distance) { long bits = DoubleToLongBits(value); return LongBitsToDouble(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) { return MathAbs(NextAfter(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) { long bits1 = DoubleToLongBits(a); long bits2 = DoubleToLongBits(b); return bits1 - bits2; } //+------------------------------------------------------------------+ //| 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) { long bits = DoubleToLongBits(value); return StringFormat("0x%.16I64X", bits); } //+------------------------------------------------------------------+ //| 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 int exponent = (int) Floor(MathLog(MathAbs(value)) / M_LN2); int digits = 52 - exponent; //--- 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; } //+------------------------------------------------------------------+ //| Formats double with thousands separator and specified decimals. | //+------------------------------------------------------------------+ /** * Alert( FormatDouble(balance, 2) ); */ string FormatDouble(const double value, const int digits, const string separator=",") { double numb = MathAbs(value); string sign = (value < 0) ? "-" : ""; 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