Siemens S7 LAD: Convert Integer to Real with Two Decimal Places

David Krause17 min read
S7-1200SiemensTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

1. Problem Definition and Signal Path

A pressure transmitter scaled to engineering units delivers a 16-bit signed integer (INT) to the PLC: raw value 10353, representing 10353 mbar or 10.353 bar. The user wants the CPU's webserver to render the value as 10.35 bar with exactly two decimal places. The raw integer carries no display metadata, so the formatting work must be performed explicitly by either the PLC program or the webserver's rendering layer.

The end-to-end chain on a Siemens S7-1500 (or S7-1200) platform is:

  1. Acquire the raw INT from the analog input module's process image (for example, an ET 200SP AI 2xU/I 2-/4-wire HS).
  2. Convert INT to REAL with the standard IEC conversion INT_TO_REAL (SCL) or its LAD equivalent DI_R / I_DI cascade.
  3. Scale to bar: divide by 1000.0 using DIV_R in LAD or the / operator in SCL.
  4. Round to two decimal places with ROUND in SCL or its LAD equivalent RND (round to DINT), followed by a division by 100.0 to restore the engineering scale.
  5. Format the result with VAL_STRG and parameter PREC = 2, or send the REAL to a custom webserver page and let JavaScript format it via Number.prototype.toFixed(2).

Steps 1 to 4 produce a numerically correct floating-point result. Step 5 is the cosmetic formatting step that produces the trailing zero the user wants. Skipping step 5 and displaying the REAL as-is in the webserver yields 10.35 (the underlying value), but the user-perceived "missing zero" comes from the page rendering, not the value itself.

1.1 Target Platform and Firmware

All code below is valid for:

  • S7-1500 CPU 1511-1 PN / 1515-2 PN / 1518-4 PN/DP with firmware V2.9 or later (TIA Portal V17 or V18).
  • S7-1200 CPU 1214C DC/DC/DC, 1215C DC/DC/DC, 1217C DC/DC/DC with firmware V4.4 or later.
  • ET 200SP analog input modules with standard scaling (0 to 27648 for unipolar signals).

For full parameter definitions of every instruction used (DI_R, DIV_R, RND, VAL_STRG), consult the S7-1500 programming and operating manual on Siemens Industry Online Support or the TIA Portal help (F1 on the selected instruction). VAL_STRG is documented in the "String + Char" instruction set available in TIA Portal V14 SP1 and later.

2. Numerical Foundation: Scaling, Truncation, and Rounding

Three distinct numeric operations appear in the conversion chain and must not be confused.

2.1 Scaling

Scaling is the linear rescaling of an integer to engineering units. For a 4 mA to 20 mA pressure transmitter with 0 to 25 bar range, the Siemens standard scaling formula is:

Pressure_bar = (Raw_INT - 0) * (25.0 - 0.0) / (27648 - 0) = Raw_INT * 25.0 / 27648

For the user's case, scaling is simpler: 1 bar equals 1000 mbar by definition, so:

Pressure_bar = Pressure_mbar / 1000.0

For raw value 10353: 10353 / 1000.0 = 10.353 bar.

2.2 Truncation

Truncation drops the fractional part without rounding. 10.353 truncated to two decimals is 10.35 (the third decimal, 3, is below 5, so the second decimal is preserved). Truncation is implemented in S7 as TRUNC in SCL or the LAD instruction TRUNC. Truncation is acceptable for display only when the underlying process never exhibits the round-half-to-even edge case (see Section 8).

2.3 Rounding

Rounding adjusts the least-significant retained digit based on the first dropped digit. The S7-1500 / S7-1200 instruction RND rounds to the nearest integer using round-half-to-even (banker's rounding), the IEEE 754 default rounding mode. This means:

  • 0.5 rounds to 0 (even).
  • 1.5 rounds to 2 (even).
  • 2.5 rounds to 2 (even).
  • 3.5 rounds to 4 (even).
  • 10.355 rounds to 10.36 because 1035.5 / 100 maps to the nearest even integer 1036.

The IEC 61131-3 third edition specifies that ROUND shall produce the nearest integer, with ties resolved in a way compatible with the platform. Siemens implements banker's rounding. If your customer specification demands round-half-away-from-zero (the "schoolbook" rule where 2.5 always rounds up to 3), you must implement a manual correction (see Section 6.3).

3. Method 1: Integer Scaling with DIV in Ladder Logic

The simplest approach is to perform all arithmetic in INT (or DINT) to avoid floating-point execution time. The trade-off is loss of precision when scaling by a non-power-of-two factor.

3.1 LAD Network Example

Network 1: Scale 10353 mbar (INT) to 10.35 bar (INT, hundredths).

  MW100  (mbar, INT = 10353)
   |---[ MOVE  ]---> MW110                // duplicate for clarity
   |---[ I_DI  ]---> MD120                // sign-extend INT to DINT
   |---[ DI_R  ]---> MD130                // DINT to REAL (10353.0)
   |---[ /R 1000.0 ]---> MD140            // 10.353 bar (REAL)
   |---[ *R 100.0  ]---> MD150            // 1035.3 (REAL)
   |---[ RND       ]---> MD160            // 1035 (DINT, banker's round)
   |---[ DI_R      ]---> MD170            // 1035.0 (REAL)
   |---[ /R 100.0  ]---> MD180            // 10.35 (REAL)
   |---[ ROUND     ]---> MW190            // 10 (DINT, integer part)
   |---[ MOD  100  ]---> MW200            // 35 (DINT, fractional part as 0.35 × 100)

For raw value 10353 the integer-result sequence is 1035 (hundredths) and 35 (two-decimal integer remainder). The webserver page can format MW190 + "." + MW200 as "10.35". This avoids VAL_STRG but requires string concatenation logic on the SCL side (Section 6.4).

3.2 Limitations

The INT-scaling path fails when the result exceeds the INT range (-32768 to +32767). For a 25 bar transmitter at 27648 raw, the integer-scaled value 25 * 100 = 2500 hundredths fits in INT, but a 350 bar transmitter at the same scaling would overflow. Always check the worst-case value against DINT#32767 before choosing this method.

4. Method 2: Real Division with INT_TO_REAL

The standard IEC conversion path uses REAL arithmetic throughout. It is slower than the INT path on the S7-1200 (firmware V4.4) but negligible on the S7-1500.

4.1 SCL Implementation

FUNCTION_BLOCK FB_PressureScale
VAR
  raw_mbar   : INT;        // input, raw integer from AI module
  bar_value  : REAL;       // output, scaled value in bar
  bar_rounded: REAL;       // output, value rounded to 0.01 bar
END_VAR

BEGIN
  // Step 1: INT to REAL conversion
  bar_value := INT_TO_REAL(raw_mbar) / 1000.0;

  // Step 2: round to two decimal places
  bar_rounded := REAL_TO_DINT(bar_value * 100.0 + 0.5 * SGN(bar_value));
  // Note: SGN returns -1, 0, or +1; the +0.5 / -0.5 correction
  // implements round-half-away-from-zero, not banker's rounding.
  bar_rounded := DINT_TO_REAL(bar_rounded) / 100.0;
END_FUNCTION_BLOCK

The + 0.5 * SGN() correction is the explicit round-half-away-from-zero formula. If the S7-1500 RND behavior (round-half-to-even) is acceptable, replace the body of Step 2 with bar_rounded := ROUND(bar_value * 100.0) / 100.0;.

4.2 LAD Equivalent

  MW100  (raw_mbar INT)
   |---[ I_DI   ]---> MD200              // sign-extend
   |---[ DI_R   ]---> MD204              // 10353.0
   |---[ /R 1000.0 ]---> MD208            // 10.353 bar
   |---[ *R 100.0  ]---> MD212            // 1035.3
   |---[ RND       ]---> MD216            // 1035 (DINT)
   |---[ DI_R      ]---> MD220            // 1035.0
   |---[ /R 100.0  ]---> MD224            // 10.35 bar

Round-trip for raw value 10353: MD224 = 10.35. For raw value 10359, MD224 = 10.36 (because 1035.9 rounds to 1036).

4.3 Performance Notes

On the S7-1500, a single REAL divide takes approximately 18 ns (CPU 1515-2 PN), and a RND takes 12 ns. The total method-2 cost is well under 200 ns per call, and the FB can be invoked from the OB1 (or OB35 cyclic interrupt) without measurable CPU load. On the S7-1200 CPU 1214C, the same sequence takes approximately 18 microseconds, still negligible for cyclic 100 ms scan rates.

5. Method 3: VAL_STRG with PREC = 2 (Siemens-Recommended)

For webserver display, the most compact solution is to use Siemens' VAL_STRG instruction. It converts a numeric value to a STRING with controlled precision, sign, and exponent formatting in a single call.

5.1 VAL_STRG Interface

Parameter Direction Type Description
IN IN REAL, LREAL, DINT, INT Value to format.
FORMAT IN WORD Bit 8: exponent notation; Bits 0-3: number of digits left of decimal.
PREC IN SINT Number of decimal places (0 to 7 for REAL; 0 to 15 for LREAL).
OUT OUT STRING, WSTRING Result string.

For two decimal places, set PREC = 2 and FORMAT = 0 (no exponent, default left-of-decimal count).

5.2 SCL Call Example

// Variant 1: 2 decimals, no exponent, fixed-point notation
VAL_STRG(
    IN     := bar_rounded,           // REAL input 10.35
    FORMAT := 0,                     // fixed-point, default width
    PREC   := 2,                     // 2 decimals
    OUT    := "DB_Web".PressureText // STRING(20) output
);

The output string for bar_rounded = 10.35 is exactly '10.35' (six characters). For 10.3 the output is '10.30', with the trailing zero preserved. This is the missing behavior the user wants from the webserver.

5.3 LAD Call Example

  MD224  (bar_rounded REAL = 10.35)
   |---[ VAL_STRG ]---> DB100.PressureText (STRING[20])
       FORMAT = W#16#0000
       PREC   = 2

In LAD, drop the VAL_STRG block from the "String + Char" instruction palette onto the network, then wire the REAL input, set FORMAT to 0 (or W#16#0 in hexadecimal), and set PREC to 2. The OUT pin writes directly to the STRING tag in the data block.

5.4 Webserver Binding

To expose DB_Web.PressureText on the S7 webserver, the variable must be in a data block with the "Webserver" attribute enabled (in TIA Portal: DB properties → Attributes → "Accessible from HMI/OPC UA/Webserver"). The webserver page then references the variable as := "DB_Web".PressureText in the HTML AWP syntax. Refer to the S7-1500 Webserver programming manual on Siemens Industry Online Support for AWP syntax details.

5.5 Why PREC = 2 is Safe for Banker's Rounding

VAL_STRG with PREC = 2 performs IEEE 754 round-half-to-even to two decimal places, then prints the result as fixed-point ASCII. The intermediate rounding prevents visual artifacts such as '10.3499999...' from leaking through (a known issue when displaying raw REALs in JavaScript). The behavior is consistent with Section 2.3: 10.355 → 10.36, 10.345 → 10.34.

6. Method 4: Reusable SCL Function Block

For projects with dozens of scaled values (pressure, flow, temperature, level), a generic SCL function block avoids code duplication and centralizes the rounding policy.

6.1 FB Definition

FUNCTION_BLOCK FB_RealFormatter
VAR_INPUT
  rawValue    : REAL;     // scaled REAL input
  decimals    : SINT;     // 0..7
  truncate    : BOOL;     // FALSE = ROUND, TRUE = TRUNC
END_VAR
VAR_OUTPUT
  text        : STRING[32]; // formatted output
  rawResult   : REAL;     // rounded/truncated REAL
END_VAR
VAR
  scaledDint  : DINT;
  scaleFactor : REAL;
END_VAR

BEGIN
  // Compute scale factor (10^decimals)
  IF decimals = 0 THEN
    scaleFactor := 1.0;
  ELSIF decimals = 1 THEN scaleFactor := 10.0;
  ELSIF decimals = 2 THEN scaleFactor := 100.0;
  ELSIF decimals = 3 THEN scaleFactor := 1000.0;
  ELSIF decimals = 4 THEN scaleFactor := 10000.0;
  ELSIF decimals = 5 THEN scaleFactor := 100000.0;
  ELSIF decimals = 6 THEN scaleFactor := 1000000.0;
  ELSE scaleFactor := 10000000.0;
  END_IF;

  // Apply round or truncate
  IF truncate THEN
    scaledDint := REAL_TO_DINT(rawValue * scaleFactor);
  ELSE
    scaledDint := ROUND(rawValue * scaleFactor);
  END_IF;

  rawResult := DINT_TO_REAL(scaledDint) / scaleFactor;

  // Format to string
  VAL_STRG(
    IN     := rawResult,
    FORMAT := 0,
    PREC   := decimals,
    OUT    := text
  );
END_FUNCTION_BLOCK

6.2 Call Example

// In OB35 every 100 ms
FB_RealFormatter_1(
  rawValue := bar_value,        // 10.353
  decimals := 2,                // two decimals
  truncate := FALSE             // use round-half-to-even
);
// FB_RealFormatter_1.text = '10.35'
// FB_RealFormatter_1.rawResult = 10.35

6.3 Manual Round-Half-Away-from-Zero Patch

Replace the line:

  scaledDint := ROUND(rawValue * scaleFactor);

with the explicit correction:

  // Add a small bias toward zero then round.
  // For positive values: +0.5 * (1/scaleFactor).
  // For negative values: -0.5 * (1/scaleFactor).
  // This breaks the round-half-to-even tie by always pushing
  // ties away from zero (schoolbook rounding).
  IF rawValue >= 0.0 THEN
    scaledDint := REAL_TO_DINT(rawValue * scaleFactor + 0.5);
  ELSE
    scaledDint := REAL_TO_DINT(rawValue * scaleFactor - 0.5);
  END_IF;

For rawValue = 10.355 and scaleFactor = 100.0, the positive branch yields REAL_TO_DINT(1035.5 + 0.5) = REAL_TO_DINT(1036.0) = 1036, then 10.36. For rawValue = -10.355 the negative branch yields REAL_TO_DINT(-1035.5 - 0.5) = -1036, then -10.36. This is round-half-away-from-zero as specified by ASTM E29 for industrial measurement reporting.

6.4 Manual String Concatenation (No VAL_STRG)

On the S7-1200 firmware V4.2 and earlier, VAL_STRG may not be available. In that case, build the string manually:

// Integer part and fractional part separated by '.'
integerPart := REAL_TO_DINT(rawResult);
fractionalPart := REAL_TO_DINT((rawResult - DINT_TO_REAL(integerPart)) * 100.0);

// Build string: "10" + "." + "35"
INT_TO_STRING(integerPart, 0, text);        // produces "10"
CONCAT(IN1 := text, IN2 := '.', OUT := text); // "10."
INT_TO_STRING(fractionalPart, 0, tempStr);   // produces "35"
CONCAT(IN1 := text, IN2 := tempStr, OUT := text); // "10.35"

This works but loses the leading zero for single-digit fractions (0.5 would render as "0.5" correctly because REAL_TO_DINT(0.5 * 100) = 50, but 0.05 would render as "0.5" due to REAL_TO_DINT(5.0) = 5). To force two-digit fractions, pad with a leading zero check:

IF fractionalPart < 10 THEN
  CONCAT(IN1 := text, IN2 := '0', OUT := text);
END_IF;

7. Webserver JavaScript Alternative (toFixed)

If the S7 webserver serves the raw REAL via a custom HTML page, the formatting can move to the browser. The PLC stays integer- or REAL-only; the page renders two decimals with JavaScript.

7.1 Why This is Often Cleaner

  • The PLC uses less memory (no STRING tags, no VAL_STRG calls).
  • Multiple consumers (HMI, SCADA, third-party dashboards) can each format the value differently without burdening the PLC.
  • JavaScript toFixed is well-supported and renders trailing zeros natively.

7.2 HTML AWP Example

<!-- AWP variable declaration -->
<!-- AWP_Out_Variable Name='PressureBar' Use='"DB_Web".PressureBar' -->

<p>Pressure: <span id="pressure">--</span> bar</p>

<script>
  // Poll the PLC every 2 seconds
  setInterval(function() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', '/awp/DB_Web/PressureBar', true);
    xhr.onload = function() {
      if (xhr.status === 200) {
        // xhr.responseText is a string representation of the REAL
        var v = parseFloat(xhr.responseText);
        if (!isNaN(v)) {
          document.getElementById('pressure').innerText = v.toFixed(2);
        }
      }
    };
    xhr.send();
  }, 2000);
</script>

The v.toFixed(2) call returns a STRING with exactly two decimal places, with trailing zeros preserved. For v = 10.35 the result is "10.35". For v = 9.9 the result is "9.90", which is the cosmetic behavior the user is asking for.

7.3 Caveats of toFixed

JavaScript Number.prototype.toFixed is defined by ECMA-262 to return a string with the requested number of digits, but the rounding rule is implementation-defined. V8 (Chrome, Node.js) uses round-half-to-even. SpiderMonkey (Firefox) uses round-half-away-from-zero on some versions. If the rounding policy matters, format the value on the PLC with VAL_STRG (Section 5) before sending it to the page as a STRING, so the rule is consistent across browsers.

7.4 Direct Webserver JSON Access

The S7-1500 webserver also supports a JSON-style interface through the AWP command :='DB_Web'.PressureBar: embedded in the page. The browser then parses the value as a number and calls toFixed(2) on it. Reference the "S7-1500 Webserver" manual section on user-defined pages for the full AWP syntax.

8. Edge Cases: Negative Numbers, Round-Half, and Overflow

Industrial measurement is rarely all positive. Differential pressure, vacuum, and bidirectional flow can produce negative raw values. Each edge case must be handled.

8.1 Edge Case Matrix

Raw INT REAL after /1000 ROUND(*100)/100 (banker's) TRUNC(*100)/100 VAL_STRG PREC=2
10353 10.353 10.35 10.35 '10.35'
10359 10.359 10.36 10.35 '10.36'
10355 10.355 10.36 (tie → 1036 even) 10.35 '10.36'
10345 10.345 10.34 (tie → 1034 even) 10.34 '10.34'
-10353 -10.353 -10.35 -10.35 '-10.35'
-10355 -10.355 -10.36 (tie → -1036 even) -10.35 '-10.36'
0 0.0 0.0 0.0 '0.00'
5 0.005 0.00 (banker's) or 0.01 (away) 0.00 '0.00' or '0.01'

8.2 Negative Numbers and DI_R / I_DI

Siemens LAD instruction I_DI sign-extends a 16-bit INT to a 32-bit DINT. Without this step, DI_R would interpret the high word as zero, turning -10353 (INT) into +55183 (DINT after zero-extension) and producing a positive scaled value. Always include the I_DI pre-step for negative-capable signals.

8.3 Overflow on the INT Path

For a -1 bar to 25 bar transmitter with 4 to 20 mA scaling, the raw INT range is approximately 0 to 27648. The integer-scaled value (hundredths) is 0 to 2500, which fits in INT. For a 0 to 100 bar transmitter at the same scaling, the integer-scaled value is 0 to 10000, still fits. For a 0 to 400 bar transmitter, the integer-scaled value is 0 to 40000, which overflows INT (max 32767) and must be held in DINT. Use DINT_TO_REAL and the DINT variants of DIV and RND in that case.

8.4 NaN and Infinity from a Disconnected Sensor

An open-circuit 4 to 20 mA loop typically drives the analog input to 0 (or the overrange value 32767, depending on module configuration). The PLC sees a valid INT, not NaN. The conversion chain produces a valid REAL. The webserver displays the resulting bar value. If the customer wants the webserver to show "--.--" on sensor fault, wrap the conversion with a quality-code check from the AI module's diagnostic interrupt (OB82) and replace the STRING with a placeholder before VAL_STRG runs.

8.5 Floating-Point Precision

REAL (32-bit IEEE 754) has approximately 7 significant decimal digits. The conversion 10353.0 / 1000.0 cannot be represented exactly; the actual stored value is 10.3530006408691406... When the user reads 10.353 in the watch table, TIA Portal truncates the display to a few digits. The round-to-2-decimals step then snaps it to 10.35. This is why the round-then-format approach is essential; formatting the raw REAL directly yields '10.35300064' in some implementations.

9. Verification and Commissioning Checklist

Before the webserver goes live, run the following verification steps in the TIA Portal PLCSIM or on the real CPU.

  1. Watch table test: Create a watch table with the raw INT, the bar_value REAL, the bar_rounded REAL, and the formatted STRING. Force the raw INT to 10353. Verify bar_rounded = 10.35 and the STRING = '10.35'.
  2. Edge case sweep: Force the raw INT to 0, 1, 5, 10353, 10355, 10359, 20000, 27648, -100, and -27648 (the last only for bidirectional scaling). Verify each output against the table in Section 8.1.
  3. Browser test: Open the S7 webserver URL (default http://<cpu-ip>) from Chrome, Firefox, and Edge. Verify the rendered text shows two decimals with trailing zeros.
  4. Round-trip test: Click the "Update" button on the custom web page (if implemented) and confirm the displayed value matches the watch table value to two decimal places.
  5. CPU load: In TIA Portal online & diagnostics, monitor OB1 cycle time. The conversion FB should add less than 0.5 ms to the cycle on a CPU 1515-2 PN, well below the 1% warning threshold.
  6. String length: Confirm the output STRING is dimensioned to hold the worst-case value, including sign, integer digits, decimal point, and two decimals. For 5-digit integer values with sign: STRING[9] is sufficient ('-12345.67' = 9 chars).
  7. Webserver access rights: Configure the user authentication in the CPU's webserver properties so the page is not anonymously writable. Refer to the S7-1500 Webserver manual for the access level definitions.
  8. Documentation: Annotate the FB with the rounding policy (banker's vs. away-from-zero) in the block comment. This avoids the next maintainer guessing why 0.5 sometimes rounds down.
Safety note: Two-decimal display is a presentation detail, not a measurement standard. Process-critical control loops must use the full-resolution REAL value, not the rounded two-decimal string. The rounded STRING is for human display only.

10. Frequently Asked Questions

How do I round a REAL to 2 decimal places in S7 LAD?

Multiply the REAL by 100.0, apply the LAD RND block to convert to DINT, then convert back to REAL and divide by 100.0. The sequence REAL → *R 100.0 → RND → DI_R → /R 100.0 produces the rounded value on the S7-1500. The RND instruction uses round-half-to-even (banker's rounding) per IEEE 754.

Why does 9.90 display as 9.9 on the S7 webserver?

REAL values in the S7 webserver are stored as 32-bit floats. The number 9.90 and 9.9 are mathematically identical at that precision. The webserver page renders the value as-is, dropping the trailing zero. To force the trailing zero, format the value as a STRING on the PLC using VAL_STRG with PREC = 2, or use JavaScript toFixed(2) on the browser side.

Should I use VAL_STRG with PREC = 2 or manual rounding in SCL?

Use VAL_STRG with PREC = 2 for webserver display, because it does the rounding and the string formatting in a single instruction and preserves trailing zeros. Use manual rounding (multiply, RND, divide) when the rounded REAL itself is consumed by further math, because the STRING output of VAL_STRG cannot be used in arithmetic operations.

What is the difference between RND, TRUNC, CEIL, and FLOOR in S7?

RND rounds to the nearest integer using round-half-to-even. TRUNC drops the fractional part (toward zero). CEIL rounds toward positive infinity (10.1 → 11, -10.1 → -10). FLOOR rounds toward negative infinity (10.9 → 10, -10.1 → -11). For two-decimal display formatting, RND is the correct choice; TRUNC causes systematic downward bias.

How do I show trailing zeros in a Siemens webserver variable?

Bind the webserver to a STRING tag, not a REAL tag. Set the STRING from VAL_STRG with PREC = 2 on every PLC cycle. The webserver renders the STRING verbatim, including the trailing zero. If the page uses JavaScript toFixed(2), the browser renders the trailing zero regardless of the underlying REAL value.

Can I use DINT instead of REAL to avoid floating-point errors?

Yes. Hold the value in hundredths (or tenths) of bar as a DINT, for example pressure_cbar : DINT; pressure_cbar := raw_mbar / 10; gives the value in centibar. The webserver page then formats it as (pressure_cbar / 100) + '.' + (pressure_cbar MOD 100) in JavaScript, or via INT_TO_STRING concatenation in SCL. This avoids IEEE 754 round-off but requires manual decimal-point placement in the display layer.

Back to blog