Search code examples
stringcodesysiec61131-3

Unexpected Functionality in Codesys REAL_TO_STRING


On an IFM CR1203:

For most 3-decimal REAL values, REAL_TO_STRING outputs the expected value. enter image description here

For others, it outputs the expected value with '0001' at the end. enter image description here

Why is this happening, and what is a good workaround?


Solution

  • Floating point numbers, as per the IEEE 754 standard, were designed to be fast and space efficient, but NOT PRECISE. When you store a floating point number (REAL), it's stored with some tailing error. To convert from a flawed (by design) data type into a precise string representation is not trivial, and different implementations do different assumtions.

    Here's an example code from C++ to ilustrate this:

    // the 'f' at the end of the literal signifies that the number is a 32 bit float
    cout << "REAL" << endl;
    cout << setprecision(10) << 0.1f << endl << 0.2f << endl << 0.3f << endl << 0.1f + 0.2f << endl;
    cout << 0.525f << endl << 0.535f << endl;
    /*
    Output:
    REAL
    0.1000000015
    0.200000003
    0.3000000119
    0.3000000119
    0.5249999762
    0.5350000262
    */
    

    One solution to your problem would be to use a more precise data type from the start (LREAL). A C++ example again:

    cout << "LREAL" << endl;
    cout << setprecision(10) << 0.1 << endl << 0.2 << endl << 0.3 << endl << 0.1 + 0.2 << endl;
    cout << 0.525 << endl << 0.535 << endl;
    /*
    Output:
    LREAL
    0.1
    0.2
    0.3
    0.3
    0.525
    0.535
    */
    

    Though, keep in mind that converting from a 32 bit floating point number to a 64 bit will preserve the error:

    cout << "REAL to LREAL" << endl;
    cout << setprecision(10) << (double)0.1f << endl << (double)0.2f << endl << (double)0.3f << endl << (double)(0.1f + 0.2f) << endl;
    cout << (double)0.525f << endl << (double)0.535f << endl;
    /*
    Output:
    REAL to LREAL
    0.1000000015
    0.200000003
    0.3000000119
    0.3000000119
    0.5249999762
    0.5350000262
    */
    

    So if you are going to use a different data type, be sure to use it everywhere where you need the precision.

    Alternative to the use of an LREAL, you could create your own decimal number format, like for example storing two integer numbers, one for the value before the floating point, and one for the value after the floating point, and create a custom to string function. But 99% of the time this is overkill.

    The simplest solution might be to just limit the number of digits after the floating point, if you know that your numbers only require a certain precision. In your case, you could just chop off anything after the third decimal digit and be done with it.