Expression fails in Calculated Property that works in Dynamic Property

I converted some dynamic properties to calculated properties, and to my big surprise they failed. A dynamic property could have an expression like:

{InterceptRange*100 / Distance}

where InterceptRange is 4, and Distance is 6. The expected answer is 66. In the calculated property the same expressions results in an expression evaluation error.

If I change the expression to

{InterceptRange / Distance}

it still fails, but

{(float)(InterceptRange / Distance)}

works (result 0.0).

{(float)(InterceptRange*100 / Distance)}

also fails.

If there is a simple module I could try to edit it to reproduce the problem there.

Try 100.0. And till out the number of characters , somehow, deemed necessary by this forum.

Can you show the exact error message from the errorLog?

In the cases you give, you seem to indicate that the calculation doesn’t give the expected result, but otherwise works. However, the above statement seem to indicate that Vassal (or rather the BeanShell interpreter) fails to even parse the expression, or similar.

Also, remember that a Calculated Property trait may be evaluated at any time, which means that InterceptRange and Distance must have values at all times to which the multiplication * and division / operators make sense. That is, if either of these properties are not a number, or Distance=0, then you will get an error.

The calculation

{ a / b }

where both a and b are integer valued, will be an integer division. That is, all fractions are dropped. For example

{10 / 3} -> 3

The calculation

{(float)(a / b)}

doesn’t fundamentally change this, because the division it self is still integer division. However, the calculation

{ x / y }

where either x or y is real-valued (floating point number) will be a real division (fractions are kept. For example

{ 10. / 3 } -> 3.3333333

which is why @palad0n’s suggestion is right.
Note, that the above also means that

{ (float)a / b }

where a and b are integers, is also a real division. For example

{ (float)10 / 3 } -> 3.3333333

In your specific case, I assume you want the result to be integer valued, so you should do

{(int)(InterceptRange * 100. / Distance)}

which with InterceptRange=4 and Distance=6 becomes

{(int)(4 * 100. / 6)} = {(int)( 4 * 16.666666666666668)} = ((int)(66.66666666666667)} = {66}

because the (int) cast floors (removes fractions) the real number. If you want to round the number, do

{Math.round(InterceptRange * 100. / Distance)}

In the example above that will give you 67.

Yours,
Christian

Thanks for pointing me to the errorlog.This has given me valuable insight - and changed my idea of the problem completely.

I retract my description of the problem I found. There is still a problem, but it is of another nature than I thought.

Vassal has the habit of only reporting an error the first time it occurs, and because of this, I didn’t notice a stream of errors that occur the first time I execute a certain function, because they didn’t occur ever after, as long as I don’t reload the module.

I have now discovered that Vassal gives calculated properties a value when calling a command in a counter not just when the calculated property is used, but also at start, even if that calculated property is not used. This happens before any passed values in the call are given a value, so that I get error messages from every calculated properties that use a passed value. Subsequent use of the calculated properties do not give error messages because now the dynamic properties with passed values have their values.

The error messages that I saw came from that first call, and not from my own use of the calculated properties. When I changed the program to use dynamic properties, there is no error, because they are not called before the program starts executing.

Because Vassal only gives of an error messages the first time, I only saw error messages for the calculated property I was changing all the time, and not for the five other calculated properties that are dependent on passed values.

I hope this explanation helps understanding what I mean.

Intermixed with the problem of calculated properties was some problems with floating point calculations, but I have worked around them by multiplying all values with 100, and only dividing by 100 at the end of the calculations.

Yes, that is a little of a “funny” feature of Calculated Property. Vassal will “initialise” all properties. For a calculated property, that means that it will try to calculate its value given the current state of the module. If that’s not possible - then you will get an error.

It may not be possible for a Calculated Property to calculate its value because some other properties (piece, zone, map, or module level) that it depends on, are not set yet, or have an incompatible initial value.

Suppose you have the calculated property

ReducedDiceRoll = Dice_result / 2

but the global property Dice_result has not been set yet or is initialised to the empty string "", Then your calculation will fail, because java.lang.String and java.lang.Integer are not valid operators to division /.

So what is the solution. Well, one can attack this in two ways:

  1. Make sure all properties (piece, zone, map, or module levels) have sensible initial values. This can be hard to achieve.
  2. In the calculated trait, protect against badly defined values. For example
    ReducedDiceRoll = (Dice_result == "" ? 0 : Dice_result / 2)
    
    This could fail if one does not catch all bad values.

Weeeellll, one could argue that with a Dynamic Property you set an explicit initial value (possibly empty), and as such it is evaluted.

It sounds like you want to do floating point arithmetic, but end up doing integer arithmetic. The most common thing is, that you do a division somewhere, where both numerator and denominator are integers.

a / b

In that case, you can cast either to be floating point with f.ex.

(float)a / b

(remember, you need to cast the whole numerator). If you want an integer result, you can either cast back to integer, or use Math.round, Math.floor, or Math.ceil, to round, round-down, or round-up, respectively.

(int)((float)a / b)
Math.round((float)a / b)
Math.floor((float)a / b)
Math.ceil((float)a / b)

Another common idiom for rounding, is to add 0.5 and then cast to int

int((float)a / b + 0.5)

A problem with multiplying by 10^n and then dividing by 10^n, is that you effectively make your precision n. That may cause some rounding errors along the way, so it is better to go to floating point operations, and then round at the end (unless you can make n large).

Note that the various Sum functions assumes (undocumented) that the summed property is integer! So if you want to calculate the average property value, you would need

(float)Sum("Property", "Filter") / Count("Property")

and possibly round to integer in some way.

Yours,
Christian

I do take precautions for negative numbers, and the precision does not need to be large here.

Rather, you should take precautions against uninitialised values - e.g., property A is initialised to "" - the empty string, and then compared against a numeric value

{A == 0}

which will fail.

Yours,
Christian

I don’t understand the concern here: lots will fail in the module if there is a use of uninitialised values.

Anyway, my module works fine now. I have turned some dynamic properties into calculated properties, but those that are dependent on initialisation have been kept as dynamic.I could give some properties an initial value so that they can be used in calculated properties, but I generally prefer not to do so, because I would rather have the module come up with an error message, than plod on with a wrong value if I forget to properly initialise a property.