RSLogix 5000 - AOI - Rounding

drawson

Member
Join Date
Aug 2005
Location
Calgary
Posts
92
I am trying to create an add-on instruction to round a value.

I have defined the following parameters for the instruction
INPUT: ValueToRound --> REAL
INPUT: Places --> DINT
OUTPUT: RoundedValue --> REAL

I've written the code as follows
IntermediateValue1 = ValueToRound*(10^Places)
DecimalPortion = IntermediateValue1 - TRUNC(IntermediateValue1)
If DecimalPortion >= 0.5 Then
RoundedValue = TRUNC(IntermediateValue1+1)/(10^Places)
Else
RoundedValue = TRUNC(IntermediateValue1)/(10^Places)
End_If

My problem is as follows: If I enter
ValueToRound = 3676.4444
Places = -2

The function calculates the values as 3699.9998 not 3700 as I expected. I assume it has something to do with the floating point math but can't figure out why. Dividing a truncated value by a power of 10 should work.

Any Suggestions?
 
First off, I have to ask, why are you rounding? Except for display purposes, there is seldom an good reason to round IEEE-754 floats, and the operations to do it are rather complex.

I apologize if this seems a bit pedantic, I'm just trying to explain what the challenges are in what you are trying to do.

There are many numbers that cannot be exactly represented in the IEEE-754 floating point format. 36.764444 happens to be one of those. So right out of the chute, you encounter a problem. This one however doesn't have a say in the outcome, as all you are doing is looking to see is if .764444 is greater than or less than .5.

It is greater than so it seems intuitive that if you add one to 36, giving you 37, you are good do go. But in order to get there, you want to multiply and divide by 10^-2, and here is where you hit a brick wall. o_O 0.01 is another number that cannot be represented in an IEEE float. Instead, .01 becomes .0099999998. Look at the result you actually got and its pretty obvious where this is going. I have even more bad news. Not only is .01 not exactly representable, but .1, .001, .0001, .00001, .... .000000000000000000001, etc are all numbers that cannot be exactly expressed in IEEE-754 float format. And those happen to be the very numbers expressed as 10^-n.

Now modern programs have complex algorithms that round values for display as a string of characters on your computer screen, so you see .01 on your screen, but that is a string, not a number. Inside, in memory it is .0099999998, and when you perform a computation that is what is used.

So, what to do about it?

What is the range of numbers that you will be using? Is the conceivable range of numbers, after shifting the decimal place, within the range of a DINT?
I suspect that they are - besides, long before you exceed the range of a DINT your float will start to suffer from resolution problems that render rounding to two or three decimal places moot, but that is another topic altogether. So if the answer is yes, then I suggest you multiply or divide depending on the sign of n by 10^|n| where 10^|n| is a positive integer, not a float, and make the destination address a DINT. This will automatically round. Then divide or multiply by 10^|n| again depending on the sign of n to an integer destination, then move that to your float.

In your example, start with
CPT XTAG (10**ABS(Places))
XTAG is type DINT
Use XIC Places.31 and XIO Places.32 to see if Places is negative or positive.
because bit Places.31 is set, Places is negative, so divide.
DIV 3676.4444 XTAG DINT_TAG will put 37 in DINT_TAG rounding automatically (well not actually automatically, the processor will jump through all kinds of complex gymnastics to make the conversion, but it will do it for you instead of you coding it.)
Now MUL DINT_TAG XTAG DINT_TAG will put 3700 in DINT_TAG.
Finally, MOV DINT_TAG REAL_TAG

For cases where N is positive, do the opposite, multiply first, then divide.


Over at MRPlc.com in the downloads section http://forums.mrplc.com/index.php?autocom=downloads&showfile=761 I have posted a program written for the PLC/5 and SLC500 platforms that does tuncation and rounding by direct manipulation of the IEEE-754 float's mantissa. You can take a look at it and see what's involved behind the scenes, but even it won't work for all possible numbers.


Is places always going to be negative? If so then there are some other easy solutions.

Last of all, I have to ask again, why are you rounding? I'm just asking because we have seen other posts here where someone was attempting to round a number and it really wasn't necessary and boiled down to a misunderstanding of what the number meant and how it was really represented in the processor.
 
Last edited:
Alaric thanks for the info.

I am rounding because I am required to do so by specificiation. We produce API well casing and are bound by the specification created by API. They define the hydrostatic test pressure using a calculation based on pipe diameter, wall thickness, and specified minimum yield strength. The result of the calculation is then to be rounded to the nearest 100 psi and used as the test pressure for that particular product.

I decided that while I was creating a rounding routine I had might as well create and AOI that I could reuse rather than just one-time code for this project. I took your advice and used DINT data types instead. Thanks for the help.

I have never fully understood floating point data before. I always thought of it simply as a structure to hold a real number.

Thanks Again.
 
I'm glad I was able to help.

Here is more info on how floats are stored:http://en.wikipedia.org/wiki/IEEE_754-1985

If you want to see what a particular float looks like in Binary, here is a useful tool:
http://babbage.cs.qc.edu/IEEE-754/Decimal.html


I need to make a correction to my post above. I wrote
Use XIC Places.31 and XIO Places.32 to see if Places is negative or positive.

Apparently I fat-fingered the keyboard. It should read:

Use XIC Places.31 and XIO Places.31 to see if Places is negative or positive.

For those who may read this in the future but who don't understand what its doing, bit 31 of any dint is the sign bit. If it is set the number is negative, if it is clear the number is positive.
 
Round using MOV

Using MOV to copt the value from a real to a dint is a simple way to round that accomplishes roughly what you want. .5 cases are a little odd in that they round to the nearest even number due to the nature of floating point numbers not being able to represent .5 exactly.

See http://www.plctalk.net/qanda/showthread.php?t=95690 for a good writeup.
 
Here is the simple rounding algorithm:

RoundedResult = truncate(RawData + 0.5, 1) for 1 digit precision
RoundedResult = truncate(RawData + 0.05, 2) for 2 digit precision
...
 
Using MOV to copt the value from a real to a dint is a simple way to round that accomplishes roughly what you want. .5 cases are a little odd in that they round to the nearest even number due to the nature of floating point numbers not being able to represent .5 exactly.

See http://www.plctalk.net/qanda/showthread.php?t=95690 for a good writeup.
Sorry, missed the requirement for varying precision. Likewise, for large numbers (ABS > 2 billion) this wouldn't be useful.
 

Similar Topics

Hi All, I'm working on an AOI that needs a ONS incorporated into the logic. Every time I run the instruction the ONS gets stuck on even when the...
Replies
5
Views
3,065
Hello!! I am trying to create a AOI to perform lead lag pumping operations based on run time. I do have 8 pumps out of which 6 are duty and 2...
Replies
6
Views
2,616
Hello, Have aoi written in Ladder to pass in and out string variables. String VAriables that interact with PLC program are declared as InOut...
Replies
5
Views
4,195
I am currently working on integrating Vin Verfication into a PLC via calculating and verifying the check digit. Is there an AOI for this or is the...
Replies
5
Views
1,580
I just installed RSLogix 5000 V18.00.00 (CPR 9 SR 2. When I try to add a AOI the only type I can select is Ladder Diagram. In other words the...
Replies
3
Views
4,323
Back
Top Bottom