SQRT(4294967294) with a DWORD operand returns -1.#QNAN, but SQRT(4294967294.0) with a REAL literal returns 65536. The square root function works. The problem is the operand: by the time it reaches SQRT it is already negative or already Not-a-Number, because the implicit DWORD-to-REAL conversion does not read 16#FFFFFFFE as the unsigned value 4,294,967,294.
Why don't a REAL literal, ABS, or a wider result variable fix it?
Typing the constant as 4294967294.0 shows that the math is correct, but it only fixes literals. In a live program the value comes from a counter, a totalizer, or a communication buffer stored in a DWORD tag. The same implicit conversion runs on every scan.
Wrapping the argument in ABS hides the failure instead of fixing it. If the runtime converts through a signed 32-bit integer, 16#FFFFFFFE becomes -2, ABS returns 2, and SQRT returns about 1.414. No NaN appears to warn you. A NaN at least stands out in the watch window, while a plausible wrong number goes straight to the HMI or into the next calculation.
Declaring the result as LREAL, or assigning it to a wider variable, changes nothing. The result type has no effect on how the argument was converted, and that conversion happens before SQRT executes.
Treating the bit pattern as an IEEE 754 infinity is also the wrong model. 16#FF800000 is negative infinity, but 16#FFFFFFFE is not. Its exponent field is all ones and its mantissa is non-zero, which makes it a NaN. Both explanations point to the same place: the conversion step, and that is where the fix goes.
Where does the value go bad between the DWORD and SQRT?
When you pass it a DWORD, the compiler inserts a conversion. Many runtimes implement that conversion with the processor's signed integer-to-float instruction, because common FPUs have no native unsigned version.
- Values up to 2147483647 (16#7FFFFFFF) convert correctly.
- From 16#80000000 upward, the top bit is read as a sign bit, and the value lands between -2147483648 and -1.
- 16#FFFFFFFE maps to -2.
The square root of a negative number is an IEEE 754 invalid operation. The FPU returns its default quiet NaN, which has the sign bit set. The Windows C runtime prints that value as -1.#QNAN, so this text is the typical signature of a Windows-hosted runtime or simulation. A target panel may display it differently, but the stored bits are the same.
A second path produces the identical symptom. Some compilers copy the raw 32 bits into a REAL with no numeric conversion. Read as REAL bits, 16#FFFFFFFE is already a negative NaN, and SQRT of a NaN returns NaN.
| Signal | Source | Wrong-value symptom |
|---|---|---|
| DWORD operand | Counter, totalizer, comm buffer | None: 16#FFFFFFFE is valid unsigned data |
| Implicitly converted REAL (signed path) | Compiler-inserted integer-to-float conversion | -2.0 for 16#FFFFFFFE; every input at or above 16#80000000 turns negative |
| Implicitly converted REAL (bit-copy path) | Raw 32-bit move into a REAL | NaN for 4294967294; tiny or huge nonsense values for ordinary integers |
| SQRT output | FPU square root | -1.#QNAN |
| Result converted back to DWORD | 65536 instead of 65535 near the top of the range (precision, not sign) | |
| Downstream logic | Comparisons, scaling, PID input | NaN propagates; comparisons return FALSE and alarms never trip |
How do I tell signed conversion from a raw bit copy?
Measure before you change any code. Run three test values through the exact expression that fails. Put both the converted REAL and the SQRT output in the watch list.
- 16#7FFFFFFF (2147483647): the signed path gives about 46340.95. The bit-copy path gives a positive NaN, because the exponent field is all ones.
- 16#80000000 (2147483648): the signed path converts to -2147483648.0, and SQRT returns NaN. The bit-copy path gives -0.0, and SQRT returns -0.0 or 0.
- 16#40800000 (1082130432): the signed path gives about 32895.8. The bit-copy path converts to 4.0, and SQRT returns 2.0.
Next, test the explicit conversion on its own. Assign the DWORD to a REAL using and watch the value.
- If the explicit conversion shows 4.29497e9 for 16#FFFFFFFE, only the implicit path is wrong.
How do I feed SQRT a correct value across the full DWORD range?
- Replace the implicit conversion with an explicit . If the runtime supports LREAL, use instead: its 53-bit significand holds every DWORD value exactly.
- Re-run the three test values. The fix is correct when 16#FFFFFFFE converts to 4294967294 (LREAL) or 4294967296 (REAL, rounded).
- If the explicit conversion still goes negative, build the float from two 16-bit halves. Each half is at most 65535, so even a signed conversion reads it correctly.
- Check that the converted value is at least 0.0 before calling SQRT, so a corrupted input cannot pass a NaN downstream.
Why does REAL give 65536 when the integer root is 65535?
REAL has a 24-bit significand, so it cannot store 4294967294 exactly. The value rounds to 4294967296.0, which is exactly 2^32, and SQRT returns exactly 65536. The true root is 65535.99998, so the correct integer root is 65535.
LREAL alone does not fix this either. and round to nearest, so 65535.99998 still becomes 65536. You have two options:
- Use LREAL and
TRUNCthe result. - Compute the root entirely in integer arithmetic, which is exact and needs no FPU.
The loop runs at most 16 iterations, so the scan-time cost is negligible. The OSCAT library's mathematics section ships its math functions as readable source. Open the one you need to see how it handles type conversion before you adopt it.
How do I confirm the fix holds across the whole range?
Check the converted input first and the output second. Then run a test vector that covers both sides of the sign-bit boundary and the edges of the rounding error:
- 0 returns 0, and 1 returns 1.
- 16#7FFFFFFF and 16#80000000 both return 46340. If the second one returns NaN, the signed conversion path is still active.
- 4294836225 (65535 squared) returns 65535, and 4294836224 returns 65534.
- 16#FFFFFFFE and 16#FFFFFFFF both return 65535. If either returns 65536, the result is being rounded instead of truncated.
- Force the input source to its rollover value in the running program and confirm that downstream comparisons and scaling behave correctly.
FAQ
How do I take the square root of a DWORD in Structured Text?
Convert explicitly with (or ), then call SQRT on the converted value. Don't pass the DWORD straight to SQRT. If 16#80000000 or higher still converts to a negative number, combine the high and low 16-bit words as hi * 65536.0 + lo before calling SQRT.
How do I get an exact integer square root of a 32-bit unsigned value?
Use a bitwise integer square root, which takes at most 16 iterations. Alternatively, use LREAL followed by TRUNC.
If an explicit returns a negative value for 16#80000000 or 16#FFFFFFFE, the runtime is converting through a signed integer. Use the split-word workaround and report the behavior to the PLC manufacturer's official support channel. Include your programming software and firmware versions, the three test values, and the converted values you observed.