Truncate Doubles Using NormalizeDouble in MQL4
Learn how to use NormalizeDouble() to truncate and round floating-point values correctly in MQL4, avoiding precision errors that can break your Expert Advisor.
Precision Control
Round doubles to the exact decimal places you need
Avoid Float Errors
Prevent floating-point comparison bugs in your EA
Lot Size Safety
Correctly format lot sizes before order submission
Core MQL4 Function
Rounds a double to a specified number of decimal places — the standard MQL4 method for truncating floating-point precision
What Is NormalizeDouble() in MQL4?
In MQL4, all price values, lot sizes, and calculated figures are stored as double (floating-point) numbers. Due to how computers represent decimals in binary, these numbers often carry tiny invisible errors — for example, 1.30000000000001 instead of 1.30000. The NormalizeDouble() function solves this by rounding a double to a specified number of decimal places, effectively truncating unwanted precision.
Function Syntax:
- value — The double-precision number you want to normalize.
- digits — The number of decimal places to round to (0–8).
The function returns the rounded value as a double.
Why Truncating Doubles Matters in Forex EAs
Floating-point precision errors are a silent killer in MQL4 development. They cause comparisons to fail unexpectedly, lot sizes to be rejected by the broker, and price levels to be off by a fraction of a pip. Using NormalizeDouble() at the right moments is one of the most important defensive coding habits for any EA developer.
5 Key Use Cases for NormalizeDouble()
1. Normalizing Lot Sizes
Brokers require lot sizes rounded to a specific number of decimal places (usually 2). Passing a raw calculated lot size like 0.10000000000003 will cause OrderSend() to fail with an invalid volume error.
double rawLot = AccountBalance() * 0.01 / 1000.0; double safeLot = NormalizeDouble(rawLot, 2); // safeLot = 0.10 instead of 0.10000000000003
Best Practice: Always normalize lot sizes to 2 decimal places before passing them to OrderSend().
2. Normalizing Price Levels
Stop Loss, Take Profit, and order entry prices must match the broker's required decimal precision. Use Digits (a built-in MQL4 variable) to automatically match the symbol's pip precision.
double entryPrice = Ask + 0.0010; double normalizedEntry = NormalizeDouble(entryPrice, Digits); double stopLoss = Bid - 0.0050; double normalizedSL = NormalizeDouble(stopLoss, Digits);
3. Safe Double Comparisons
Comparing raw doubles directly can produce unexpected results due to hidden precision. Normalizing both sides of a comparison before evaluating them makes your logic predictable and reliable.
// Unreliable — may fail due to floating-point imprecision
if(calculatedValue == 1.5) { ... }
// Reliable — normalize before comparing
if(NormalizeDouble(calculatedValue, 5) == NormalizeDouble(1.5, 5)) { ... }
Example: An EA checking whether the current price equals a target level may never trigger if raw doubles are compared directly — even when the values look identical on screen.
4. Displaying Values in Logs and Alerts
When printing doubles to the Expert journal or sending alerts, normalizing first prevents cluttered output like 1.234560000000001 and ensures readable, professional log entries.
double atr = iATR(NULL, 0, 14, 1);
Print("ATR value: ", NormalizeDouble(atr, Digits));
5. Truncating Indicator Values
Custom indicator outputs often contain excess precision. Normalizing them before using in EA logic reduces noise and ensures consistent behaviour across different brokers and platforms.
Full MQL4 Example: TruncateDouble Function
Here is a practical, reusable helper function that wraps NormalizeDouble() for the most common EA scenarios:
// ─── Helper: Truncate a double to N decimal places ───────────────────────────
double TruncateDouble(double value, int decimals)
{
return NormalizeDouble(value, decimals);
}
// ─── Helper: Normalize a lot size to broker requirements ─────────────────────
double NormalizeLot(double rawLot, int decimals = 2)
{
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
// Round to nearest lot step
double stepped = MathFloor(rawLot / lotStep) * lotStep;
// Clamp within broker limits
stepped = MathMax(minLot, MathMin(maxLot, stepped));
return NormalizeDouble(stepped, decimals);
}
// ─── Helper: Normalize a price to symbol digits ───────────────────────────────
double NormalizePrice(double price)
{
return NormalizeDouble(price, Digits);
}
// ─── Demo ─────────────────────────────────────────────────────────────────────
void OnStart()
{
// 1. Basic double truncation
double rawValue = 1.23456789;
Print("Raw value: ", rawValue);
Print("Truncated to 2dp: ", TruncateDouble(rawValue, 2)); // 1.23
Print("Truncated to 4dp: ", TruncateDouble(rawValue, 4)); // 1.2346
// 2. Lot size normalization
double calculatedLot = AccountBalance() * 0.02 / 10000.0;
double safeLot = NormalizeLot(calculatedLot);
Print("Raw lot: ", calculatedLot);
Print("Normalized lot: ", safeLot);
// 3. Price normalization
double rawEntry = Ask + 0.00123456;
double safeEntry = NormalizePrice(rawEntry);
Print("Raw entry price: ", rawEntry);
Print("Normalized entry: ", safeEntry);
// 4. Safe comparison using NormalizeDouble
double a = 1.10000000000001;
double b = 1.10000;
bool rawMatch = (a == b); // Often FALSE
bool safeMatch = (NormalizeDouble(a, 5) == NormalizeDouble(b, 5)); // TRUE
Print("Raw comparison: ", rawMatch ? "MATCH" : "NO MATCH");
Print("Safe comparison: ", safeMatch ? "MATCH" : "NO MATCH");
}
NormalizeDouble() Digits Reference
| digits Value | Decimal Places | Typical Use Case |
|---|---|---|
| 0 | Integer rounding | Rounding to whole numbers |
| 2 | Hundredths (0.01) | Lot sizes (most brokers) |
| 4 | Pips (0.0001) | 4-digit broker prices |
| 5 | Pipettes (0.00001) | 5-digit broker prices (most common) |
| Digits | Symbol-specific | Always use for price levels (recommended) |
Using the built-in Digits variable as the second argument is the safest approach for price normalization — it automatically adapts to whether your broker uses 4-digit or 5-digit pricing.
Common Mistakes to Avoid
❌ Mistake 1: Submitting a raw calculated lot size to OrderSend() without normalization — will result in ERR_INVALID_TRADE_VOLUME (131).
❌ Mistake 2: Comparing doubles with == directly — floating-point representation makes this unreliable even for values that appear equal.
❌ Mistake 3: Hardcoding digits = 5 for all price values — some symbols (like JPY pairs or XAU/USD) use different precision. Always use Digits.
✅ Best Practice: Normalize every double before using it in order functions, comparisons, or logging. It costs nothing and prevents hard-to-trace bugs.
Practical Checklist for EA Developers
- Lot sizes — Always normalize to 2 decimal places and respect
MODE_LOTSTEP. - Price levels — Always normalize using
Digitsbefore passing toOrderSend(). - Double comparisons — Normalize both operands before using
==,<, or>. - Indicator values — Normalize before storing or comparing to avoid platform-specific precision differences.
- Logging — Normalize before printing to keep the Experts journal clean and readable.
Key Takeaways
-
NormalizeDouble(value, digits) is MQL4's built-in function for truncating and rounding doubles
-
Always normalize lot sizes to 2 decimal places before submitting orders
-
Use the built-in Digits variable for price normalization to support all broker types
-
Never compare raw doubles with == — floating-point imprecision makes this unreliable
-
Make NormalizeDouble() a reflex — use it wherever a double feeds into an order, comparison, or log
Understanding Floating-Point Representation in MQL4
MQL4 uses the IEEE 754 double-precision standard for all double values — the same standard used in C, C++, and most programming languages. This means every decimal number is stored in binary, and most fractions cannot be represented exactly. The result is hidden rounding errors that accumulate silently through your calculations.
What Computers Actually Store vs. What You Expect
| What You Write | What Is Actually Stored | Error |
|---|---|---|
| 0.1 | 0.10000000000000000555... | ~5.5e-18 |
| 1.3 | 1.29999999999999982236... | ~1.8e-16 |
| 0.1 + 0.2 | 0.30000000000000004441... | ~4.4e-17 |
| NormalizeDouble(0.1+0.2, 5) | 0.30000 | 0 (corrected) |
These errors are invisible when printing to the screen but cause real failures in comparisons and broker order submissions.
NormalizeDouble() vs MathRound() vs MathFloor()
MQL4 provides several rounding functions. Understanding which to use — and when — prevents subtle logic errors in your EA.
| Function | Behaviour | Best Used For |
|---|---|---|
| NormalizeDouble(v, d) | Rounds to d decimal places (banker's rounding) | Prices, lots, all EA doubles |
| MathRound(v) | Rounds to nearest integer | Counting bars, integer conversions |
| MathFloor(v) | Rounds down to nearest integer | Lot step calculations (always round down) |
| MathCeil(v) | Rounds up to nearest integer | Buffer sizing, array allocation |
| (int) cast | Truncates toward zero (no rounding) | Integer math only — avoid for lots/prices |
double value = 1.23567; Print(NormalizeDouble(value, 2)); // 1.24 (rounds to 2dp) Print(MathRound(value)); // 1.0 (nearest integer) Print(MathFloor(value)); // 1.0 (floor) Print(MathCeil(value)); // 2.0 (ceiling) Print((int)value); // 1 (truncates, no round) // For lot sizing — always use MathFloor with lot step, then NormalizeDouble: double rawLot = 0.137; double lotStep = 0.01; double safeLot = NormalizeDouble(MathFloor(rawLot / lotStep) * lotStep, 2); Print(safeLot); // 0.13 (floor to step, then normalize)
Using NormalizeDouble() Inside a Full EA
Below is a minimal but complete EA skeleton demonstrating exactly where and how NormalizeDouble() must be applied throughout the order lifecycle — from lot calculation through to Stop Loss and Take Profit assignment.
extern double RiskPercent = 1.0; // Risk % of account balance per trade
extern int StopLossPips = 50; // Stop loss in pips
extern int TakeProfitPips = 100; // Take profit in pips
//+------------------------------------------------------------------+
//| Calculate safe lot size |
//+------------------------------------------------------------------+
double GetLotSize(double stopLossPips)
{
double pipValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
double minLot = MarketInfo(Symbol(), MODE_MINLOT);
double maxLot = MarketInfo(Symbol(), MODE_MAXLOT);
double riskAmount = AccountBalance() * RiskPercent / 100.0;
double rawLot = riskAmount / (stopLossPips * pipValue * 10);
// Step 1: floor to nearest lot step
double steppedLot = MathFloor(rawLot / lotStep) * lotStep;
// Step 2: clamp within broker limits
steppedLot = MathMax(minLot, MathMin(maxLot, steppedLot));
// Step 3: NormalizeDouble — ALWAYS the final step
return NormalizeDouble(steppedLot, 2);
}
//+------------------------------------------------------------------+
//| Main EA tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if(OrdersTotal() > 0) return; // Already in a trade
double pointSize = MarketInfo(Symbol(), MODE_POINT);
double pip = (Digits == 5 || Digits == 3) ? pointSize * 10 : pointSize;
// Normalize all price levels before use
double entryPrice = NormalizeDouble(Ask, Digits);
double stopLoss = NormalizeDouble(Ask - StopLossPips * pip, Digits);
double takeProfit = NormalizeDouble(Ask + TakeProfitPips * pip, Digits);
// Get normalized lot size
double lotSize = GetLotSize(StopLossPips);
// Submit order — all values are already normalized
int ticket = OrderSend(
Symbol(),
OP_BUY,
lotSize,
entryPrice,
3, // slippage in points
stopLoss,
takeProfit,
"NormalizeDouble EA",
0,
0,
clrGreen
);
if(ticket < 0)
Print("OrderSend failed. Error: ", GetLastError());
else
Print("Order opened. Ticket: ", ticket,
" | Lots: ", lotSize,
" | SL: ", stopLoss,
" | TP: ", takeProfit);
}
Digits, Points, and Pips — Why They Affect Normalization
One of the most common sources of normalization confusion is the relationship between Digits, Point, and pips. These differ between brokers and currency pairs, so getting them right is essential before calling NormalizeDouble().
4-Digit Brokers
- •
Digits = 4(e.g. EURUSD = 1.3050) - •
Point = 0.0001 - • 1 pip = 1 Point = 0.0001
- • Normalize prices to 4 decimal places
5-Digit Brokers (Most Common)
- •
Digits = 5(e.g. EURUSD = 1.30504) - •
Point = 0.00001 - • 1 pip = 10 Points = 0.0001
- • Normalize prices to 5 decimal places
Pro Tip: Always use NormalizeDouble(price, Digits) rather than hardcoding 4 or 5. The Digits variable automatically reflects your broker and symbol — including JPY pairs (Digits = 2 or 3) and gold (XAU/USD, Digits = 2).
Frequently Asked Questions
Q: Does NormalizeDouble() truncate or round?
It rounds, not truncates. It uses standard half-up rounding: NormalizeDouble(1.2350, 3) returns 1.235, and NormalizeDouble(1.2355, 3) returns 1.236. If you need to always round down (e.g. lot sizing), combine MathFloor() first, then NormalizeDouble().
Q: Do I need NormalizeDouble() if I'm just reading prices from the chart?
Prices returned directly by Ask, Bid, or iClose() are generally already at broker precision. However, the moment you perform any arithmetic on them (adding pip offsets, averaging prices, calculating spread), normalization becomes necessary before passing values to order functions.
Q: Can NormalizeDouble() cause issues if I use too few digits?
Yes. Normalizing a price to fewer digits than the broker expects can cause ERR_INVALID_STOPS (130) on some platforms. For example, normalizing a 5-digit broker price to only 4 digits may shift your stop loss by a full pip. Always match the digits argument to what the broker requires — use the Digits variable for prices.
Q: Is NormalizeDouble() available in MQL5 too?
Yes — NormalizeDouble() exists in MQL5 with identical syntax and behaviour. In MQL5 you would typically use _Digits (with underscore) instead of Digits, and use SymbolInfoInteger(Symbol(), SYMBOL_DIGITS) for dynamic symbol queries.
Q: What happens if I pass a negative digits value?
Passing a negative digits argument causes NormalizeDouble() to round to the left of the decimal point. For example, NormalizeDouble(1234.56, -2) returns 1200.0. This is rarely useful in Forex EA work and should be avoided to prevent unintended results.
NormalizeDouble() Quick Reference Cheat Sheet
Lot Size
NormalizeDouble(MathFloor(lot/step)*step, 2)
Any Price Level
NormalizeDouble(price, Digits)
Safe Comparison
NormalizeDouble(a, 5) == NormalizeDouble(b, 5)
Stop / Take Profit
NormalizeDouble(Bid - sl*pip, Digits)
Indicator Value
NormalizeDouble(iMA(...), Digits)
Clean Log Output
Print(NormalizeDouble(value, 5))