iLogic says equal number are not equal??

iLogic says equal number are not equal??

DRoam
Mentor Mentor
2,898 Views
24 Replies
Message 1 of 25

iLogic says equal number are not equal??

DRoam
Mentor
Mentor

I often have iLogic rules check if a parameter equals a value, in order to take some appropriate action. This is the kind of if/then test that needs to be very reliable, and you would think it would be -- just checking if one number equals another. However, sometimes I'll run an equality test and the iLogic will say that the two numbers are not equal, when in fact they are. I've run into this strange glitch multiple times before, and thought I'd post to try and find out what the heck is going on.

 

I put together a quick script to demonstrate the issue. To reproduce, first create a new Part and create a parameter in the Part called "d0". Then paste the following rule into the Part and run it:

 

 

oSetVal = 1.23456789

Parameter("d0") = oSetVal

IsEqual = If(Parameter("d0") = oSetVal,True,False)
		
MessageBox.Show( _
	"Parameter: " & Parameter("d0") & vbCrLf & _
	"Set value: " & oSetVal & vbCrLf & _
	"Equal: " & IsEqual )

 

The rule literally sets the parameter equal to the value, and a message box confirms that they are indeed equal, and yet an equality test says the values are not equal.

 

You can experiment with different numbers and see that most of the time, the code returns the correct "True" result. However, for some oddball values, it says they're not equal, even though they are. (Most numbers following the pattern 3.456789, 4.56789, etc. on up to 7.89 also exhibit the glitch).

 

What's the reason for this? Is there some way to fix this so I can be confident my parameter equality tests in iLogic rules will actually return the correct result?

 

 

 

Running Inventor 2017.4.6 on a Lenovo Windows 10 64-bit desktop

0 Likes
2,899 Views
24 Replies
Replies (24)
Message 2 of 25

DRoam
Mentor
Mentor

Here's another version of the rule that makes it easier to test different values and gives better visual feedback of the true/false result with a message box icon:

 

Do
	oSetVal = InputBox("Enter a Set Value:","Set Value",Parameter("d0"))
	
	If oSetVal = "" Then Exit Do
	
	Parameter("d0") = oSetVal
	
	IsEqual = If(Parameter("d0") = oSetVal,True,False)
	
	oIcon = If(IsEqual,MessageBoxIcon.Information,MessageBoxIcon.Exclamation)
		
	oAnswer = MessageBox.Show( _
		"Parameter: " & Parameter("d0") & vbCrLf & _
		"Set value: " & oSetVal & vbCrLf & _
		"Equal: " & IsEqual _
		,"Result",MessageBoxButtons.OKCancel,oIcon)
Loop Until oAnswer = vbCancel
0 Likes
Message 3 of 25

LukeDavenport
Collaborator
Collaborator

Search ‘iLogic Lies Excitech’ in Google and my blog on this topic may help?

Luke

0 Likes
Message 4 of 25

DRoam
Mentor
Mentor

Hi Luke, thanks for directing me to that article and related thread. Glad I'm not alone in this.

 

Unfortunately, though, the (best) solution in your video was to use the API to get and compare the parameter's value, and unfortunately that's the method I was actually using when I ran into this issue. I just used the normal iLogic Parameter("x") method in my example to simplify things, after double-checking that the issue occurs when doing that, too.

 

Please take a look at the revised code below, which uses both methods -- the API access method, and the normal Parameter("x") method. If you try the values I mentioned in my first post (like 1.23456789, or 7.89), both the API method and normal method say the values are not equal.

 

Dim oPartDoc As PartDocument = ThisDoc.Document
Dim oCompDef As PartComponentDefinition = oPartDoc.ComponentDefinition
Dim oParam As Parameter = oCompDef.Parameters("d0")

Do
	oSetVal = InputBox("Enter a Set Value:","Set Value",Parameter("d0"))
	
	If oSetVal = "" Then Exit Do
	
	Parameter("d0") = oSetVal
	
	oParamValAPI = oParam.Value / 2.54
	oDiffAPI = oParamValAPI-oSetVal
	
	IsEqualAPI = If(oParamValAPI = oSetVal,True,False)
	
	oIcon = If (IsEqualAPI, MessageBoxIcon.Information, MessageBoxIcon.Exclamation)
	
	oParamValNormal = Parameter("d0")
	oDiffNormal = oParamValNormal-oSetVal
	
	IsEqualNormal = If(oParamValNormal = oSetVal,True,False)
	
	oAnswer = MessageBox.Show( _
		"Set value: " & oSetVal & vbCrLf & _
		"API Param Equal: " & IsEqualAPI & vbCrLf & _
		"API Param Difference: " & oDiffAPI & vbCrLf & _
		vbCrLf & _
		"Normal Param Equal: " & IsEqualNormal & vbCrLf & _
		"Normal Param Difference: " & oDiffNormal & vbCrLf _
		,"Result",MessageBoxButtons.RetryCancel,oIcon)
Loop Until oAnswer = vbCancel

Do you still think this could be the same root-cause issue as in your article and the other thread? Or is this something different?

0 Likes
Message 5 of 25

DRoam
Mentor
Mentor

Another thing I don't understand, is @MjDeck says that iLogic does all of the math operations in double precision (15 decimal places) and does comparisons in decimal precision (7 decimal places). Ok, that sounds like it could definitely cause a glitch like this.

 

However, here's the math for a number like 440.25 (one of the lowest-precision numbers I've seen that produce the glitch):

 

Input value: exactly 440.25

To database units: 440.25 * 2.54 = exactly 1118.235

Output - back to working units: 1118.235 / 2.54 = exactly 440.25

Output - Input = 440.25 - 440.25 = 0

 

Nowhere in that series of calculations is there a decimal number with digits past 7 decimal places. The highest number of decimal places necessary to perform those calculations is only 3. So even if you only did the math operations AND comparison to 3-decimal-place precision, the input value should still equal the output value. There shouldn't be a difference between the input and output of 5.6843418860808E-14, as the iLogic code indicates.

 

Somehow, the calculation the iLogic is doing goes something like this:

 

Input value: exactly 440.25

To database units: 440.25 * 2.54 = approx. 1118.235

Output - back to working units: approx. 1118.235 / 2.54 = 440.2500000000000568434188608080

Output - Input = 440.2500000000000568434188608080 - 440.25 = 5.6843418860808E-14

 

Another thing I don't understand, is 440.2500000000000568434188608080 is WAY more than 15 decimal places of precision, so how on earth is the iLogic calculating/storing that for the value of the output anyway?

 

In my understanding, if a calculation requires less decimal places than the working precision of the iLogic calculations (apparently 15 decimal places), then the calculations/conversion should be exact. But apparently they're not.

 

And also in my understanding, if the difference between two numbers being compared is less than the working precision of the comparison (apparently 7 decimal places), then the comparison should say they are equal. But apparently that's wrong, too.

 

So why are these two understandings incorrect? Why is this 5.674...E-14 value showing up in the first place, how is it even being stored in the output value, and why is such a small difference resulting in a False equality test?

 

0 Likes
Message 6 of 25

MjDeck
Autodesk
Autodesk

@DRoam, it will work if you use the Parameter.ValueForEquals function for comparison. That gives you a DoubleForEquals object. The regular Parameter function can't do that. Here's a modified version of your original rule.

oSetVal = 1.23456789
Parameter("d0") = oSetVal
IsEqual = If(Parameter.ValueForEquals("d0") = oSetVal,True,False)		
MessageBox.Show( _
	"Parameter: " & Parameter("d0") & vbCrLf & _
	"Set value: " & oSetVal & vbCrLf & _
	"Equal: " & IsEqual )

Mike Deck
Software Developer
Autodesk, Inc.

0 Likes
Message 7 of 25

MjDeck
Autodesk
Autodesk

About your second question: this is an example of round-off error in double precision math. This is a limitation of double precision (15 or 16 decimal places) math in all programming languages. You can demonstrate it with JavaScript in a web browser. For instance, in Chrome hit F12 to bring up the Developer Tools. Select the Console tab in the lower pane. Then type in:

let i=440.25
let idb=i*2.54
let idbi=idb/2.54
idbi

That will show the value of idbi as:
440.25000000000006


Mike Deck
Software Developer
Autodesk, Inc.

0 Likes
Message 8 of 25

DRoam
Mentor
Mentor

@MjDeck, thanks for the reply. That works for a parameter in the current document, but most of my rules where this is important are iterating through Parts in an assembly, so I have to access the parameters using the API. And the DoubleForEquals functionality seems to be a strictly iLogic thing.

 

What exactly does the DoubleForEquals method do? Is is it something I could emulate after getting a parameter's value via the API, such as by rounding?

0 Likes
Message 9 of 25

philip1009
Advisor
Advisor

Rounding could be a viable solution, I don't know enough about double for equals to provide input on that.  Another iLogic snippet that could work is EqualWithinTolerance(a, b, 0.001), if that works you can make the comparison without taking the extra rounding steps.

0 Likes
Message 10 of 25

LukeDavenport
Collaborator
Collaborator

Or if you need an API method as you're not working in iLogic then you might have to do a precision check like the below:

 

Dim Precision As Double = 0.000001
If Math.Abs(Parameters("TEST1").Value - Parameters("TEST2").Value ) < Precision Then

        MsgBox("Numbers match")

Else

        MsgBox("No match")

End If

0 Likes
Message 11 of 25

DRoam
Mentor
Mentor

I'd really like to find a way to get the actual model value of the parameter itself (in the parameter's units, not in database units), so that I can then use that value in any type of calculation (including a comparison) without having to worry about this "different by an amount less than 1E14" nonsense.

 

Since the rounding error always seems to be on the order of 1E14 or higher, I think rounding to 10 decimal places would be a reasonable happy medium to allow for high-precision numbers, but strip out any rounding error.

 

 

I tried simply doing this to strip the rounding error from the retrieved parameter value:

 

 

oParamValAPI = Round(oParam.Value/2.54,10)

 

I used this to get oParamValAPI in my code in post #4, and it seems to work well for just about any value. The only values I could get it to throw an equality false-negative on were very large numbers (as in over a million) with lots of decimal places (more than 6).

 

Does anyone see any issue with doing this? Or know of a better way to reliably get the actual model value for use in both calculations and comparisons?

 

I still don't quite understand why the rounding error is happening in the first place, for such a simple low-decimal calculation like 440.25/2.54, that shouldn't require any rounding in the first place. But I'm happy to let that go if we can find a practical solution for it.

0 Likes
Message 12 of 25

DRoam
Mentor
Mentor

Or would I be better off working with parameters as Decimal values, like below?

 

Dim oParamValAPI As Decimal = oParam.Value/2.54

This seems to be resilient against equality false-negatives as well.

0 Likes
Message 13 of 25

MjDeck
Autodesk
Autodesk

The Decimal data type is good, but I can't guarantee that it will provide the equality test results that you expect in all cases. The question is: how accurate are the numbers that you're converting into decimals?
You can use the DoubleForEquals type instead of Decimal. Here's a sample:

Dim oParamValAPI As DoubleForEquals = CDbl(oParam.Value) / 2.54

DoubleForEquals stores the value in double precision. But it makes comparisons in single precision.

 

I wouldn't recommend rounding, unless you're only using the rounded values for testing equality. You could easily lose more accuracy by rounding, in addition to the built-in round-off error.

Instead of rounding, as @philip1009 said you can use EqualWithinTolerance. 

 

To get the actual model value of the parameter in its own units, you can use the API method UnitsOfMeasure.ConvertUnits.


Mike Deck
Software Developer
Autodesk, Inc.

0 Likes
Message 14 of 25

clutsa
Collaborator
Collaborator

I still don't quite understand why the rounding error is happening in the first place, for such a simple low-decimal calculation like 440.25/2.54, that shouldn't require any rounding in the first place. But I'm happy to let that go if we can find a practical solution for it.


This may go way more in depth then you care to think about, but I'll put it here if you want to make your ears bleed later.

https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html

The long and short to your answer is that while 1118.235 is a very simple number in a base 10 (or decimal number) everything the computer does is really done in base 2 (binary)  and in base 2 that number is much more complex.

If I've helped you, please help me by supporting this idea.
Mass Override for Each Model State

Custom Glyph Icon for iMates

0 Likes
Message 15 of 25

clutsa
Collaborator
Collaborator

Just for fun, I found this site... try your numbers in this and see why they aren't perfect. (make sure to pick some of the binary options for the output) https://www.exploringbinary.com/floating-point-converter/

If I've helped you, please help me by supporting this idea.
Mass Override for Each Model State

Custom Glyph Icon for iMates

0 Likes
Message 16 of 25

BrianEkins
Mentor
Mentor

This isn't an iLogic problem but as has been said, is just a fact of life when working with computers and floating point numbers.  I wrote a post several years ago about this that might help.

 

https://modthemachine.typepad.com/my_weblog/2013/08/comparing-floating-point-numbers.html

 

As far as getting the "actual" value of a parameter, the true value of the parameter is stored internally in Inventor in database units.  The core of Inventor is all database units.  The only time Inventor takes units into account is when the user enters a string that can be a value with a unit or when Inventor needs to display a value.

---------------------------------------------------------------
Brian Ekins
Inventor and Fusion 360 API Expert
Website/Blog: https://EkinsSolutions.com
0 Likes
Message 17 of 25

DRoam
Mentor
Mentor

I've been having good luck so far using DoubleForEquals. However, I'm having trouble with a function I'm trying to build that can return the value of a Parameter, whether it's a numeric, text, or boolean parameter. Everything is working perfectly, except returning a DoubleForEquals value if it's numeric.

 

Below is some sample code that demonstrates the behavior I'm seeing:

 

'Before running, create a Numeric parameter called "d0"

Sub Main()
	SetValue = 440
	Parameter.Param("d0").Units = "in"
	Parameter.Param("d0").Expression = SetValue & " in"
	
	MessageBox.Show( _
		NumericFun & vbCrLf & TypeName(NumericFun) & vbCrLf & (NumericFun = SetValue) & vbCrLf & vbCrLf & _
		GenericFun & vbCrLf & TypeName(GenericFun) & vbCrLf & (GenericFun = SetValue) )
End Sub

Function NumericFun As DoubleForEquals
	Return CDbl(Parameter.Param("d0").Value / 2.54)
End Function

Function GenericFun As Object
	'If is text or Boolean param, return text or Boolean. Otherwise...
	Dim oReturnVal As DoubleForEquals = CDbl(Parameter.Param("d0").Value / 2.54)
	Return oReturnVal
End Function

If you run the code, you'll see that even though GenericFun  is returning a DoubleForEquals value (just like NumericFun), it's not being treated like one during the equality test.

 

For some reason, the function has to be explicitly defined to return a DoubleForEquals value, as NumericFun does. GenericFun clearly returns a DoubleForEquals value, as shown by the "TypeName(GenericFun)" result, but the equality test returns False anyway.

 

Why is this? Is there anything I can do to get GenericFun to return a DoubleForEquals value that can be directly tested for equality?

0 Likes
Message 18 of 25

clutsa
Collaborator
Collaborator

the way you set "SetValue" it was an integer. I would have thought setting it to double would have worked but didn't. This seems to work though...

Sub Main()
	Dim SetValue As DoubleForEquals = 440 '<-----------
	Parameter.Param("d0").Units = "in"
	Parameter.Param("d0").Expression = SetValue & " in"
	
	MessageBox.Show( _
		NumericFun & vbCrLf & TypeName(NumericFun) & vbCrLf & (NumericFun = SetValue) & vbCrLf & vbCrLf & _
		GenericFun & vbCrLf & TypeName(GenericFun) & vbCrLf & (GenericFun = SetValue) )
End Sub

Function NumericFun As DoubleForEquals
	Return CDbl(Parameter.Param("d0").Value / 2.54)
End Function

Function GenericFun As Object
	'If is text or Boolean param, return text or Boolean. Otherwise...
	Dim oReturnVal As DoubleForEquals = CDbl(Parameter.Param("d0").Value / 2.54)
	Return oReturnVal
End Function
If I've helped you, please help me by supporting this idea.
Mass Override for Each Model State

Custom Glyph Icon for iMates

0 Likes
Message 19 of 25

DRoam
Mentor
Mentor

In my real-life use of the function, I won't be initially setting the value via code at all. This is a more true-to-life way to test the function how I'll actually be using it:

 

'Before running, create a Numeric parameter called "d0" and set to 440 inches.

Sub Main()
	MessageBox.Show( _
		NumericFun & vbCrLf & TypeName(NumericFun) & vbCrLf & (NumericFun = 440) & vbCrLf & vbCrLf & _
		GenericFun & vbCrLf & TypeName(GenericFun) & vbCrLf & (GenericFun = 440) )
End Sub

Function NumericFun As DoubleForEquals
	Return CDbl(Parameter.Param("d0").Value / 2.54)
End Function

Function GenericFun As Object
	'If is text or Boolean param, return text or Boolean. Otherwise...
	Dim oReturnVal As DoubleForEquals = CDbl(Parameter.Param("d0").Value / 2.54)
	Return oReturnVal
End Function

So the DoubleForEquals "magic" needs to take place on the value returned by GenericFun itself.

 

To give a bit more explanation of what I'm after, I basically have two use cases for the GenericFun function:

Use Case 1: To get the value of a numeric Parameter (the real-life version of the function can take a parameter name as an argument, but this sample GenericFun function just gets the "d0" parameter to keep it simple) and assign it to a variable for use in both calculations and equality testing. I can use DoubleForEquals for this, e.g.:

Dim oValue As DoubleForEquals = GenericFun

If oValue = 440 Then
	Parameter("x") = oValue * 3.25
Else
	Parameter("x") = oValue * 4
End If

Use Case 2: For immediate equality testing, without first being assigned to a variable. Needs to return a value that can be equality-tested directly, e.g.:

If GenericFun = 440 Then
	Parameter("x") = 1430
End If

My goal is to be able to use the same function for both use cases, without having to do anything special. GenericFun accomplishes Use Case #1 just fine. However, I can only get it to accomplish #2 if it's explicitly defined to return a DoubleForEquals value, like NumericFun. But then I would have to create and use a separate function for getting text parameter values.

 

Aside from being used directly in equality tests, my real-life GenericFun does everything perfectly: getting text, boolean, and numeric parameter values, converting numeric parameters to desired output units, and equality tests after assigning the returned value to a DoubleForEquals variable.

 

If there's some way to get GenericFun to spit out a DoubleForEquals value that's actually treated as such by the equality test, then I'll have a function that's the equivalent of the Parameter.Value method, except:

  • It actually returns the value in the parameter's units, rather than database units
  • It can return Nothing, 0, empty string, or False (depending on expected type) if the parameter doesn't exist, rather than throwing an exception
  • The result can be used directly in equality comparisons

 

0 Likes
Message 20 of 25

clutsa
Collaborator
Collaborator

It also appears that only the number you're checking against has to be set as the DoubleForEquals...

Sub Main()
	
	Dim SetValue As DoubleForEquals = 440 '<-----------
	Parameter.Param("d0").Units = "in"
	Parameter("d0") = SetValue
	'Parameter.Param("d0").Expression = SetValue & " in"
	
	MessageBox.Show( _
		SetValue & vbCrLf & TypeName(SetValue) & vbCrLf & vbCrLf & _
		NumericFun & vbCrLf & TypeName(NumericFun) & vbCrLf & (NumericFun = SetValue) & vbCrLf & vbCrLf & _
		GenericFun & vbCrLf & TypeName(GenericFun) & vbCrLf & (GenericFun = SetValue))
End Sub

Function NumericFun As Double
	Return CDbl(Parameter.ValueForEquals("d0"))
End Function

Function GenericFun As Object
	'If is text or Boolean param, return text or Boolean. Otherwise...
	Dim oReturnVal = CDbl(Parameter.ValueForEquals("d0"))
	Return oReturnVal
End Function

 Edit: I hadn't seen your last post when I posted this. I was just posting something I had found.

If I've helped you, please help me by supporting this idea.
Mass Override for Each Model State

Custom Glyph Icon for iMates

0 Likes