ExpressionEvaluator icon indicating copy to clipboard operation
ExpressionEvaluator copied to clipboard

`OptionForceIntegerNumbersEvaluationsAsDoubleByDefault` leads to not recognising three parameter version of Math.Round

Open midgleyc opened this issue 4 years ago • 2 comments

The code

var evaluator = new ExpressionEvaluator()
{
    OptionForceIntegerNumbersEvaluationsAsDoubleByDefault = true
};

string expression = "Math.Round(1,0,MidpointRounding.AwayFromZero)";

Console.WriteLine(expression);
Console.WriteLine(evaluator.Evaluate(expression));

gives the error

[System.Math] object has no Method named "Round".

instead of the expected result "1".

This came about in 1.4.31.0 and worked in 1.4.30.0 (but 1.4.31.0 is a good fix for #110, so thanks for that!)

midgleyc avatar Aug 03 '21 15:08 midgleyc

Yes of course as in C# You can't put a double as second argument in Math.Round(double,int,MidpointRounding). And when you set OptionForceIntegerNumbersEvaluationsAsDoubleByDefault = true it's what you are doing so it's like Math.Round(1d,0d,MidpointRounding.AwayFromZero). This is not possible in C#. The bug in version 1.4.30.0 is that it forced a conversion of numbers where C# do not. And so it selected sometime wrong methods because of conversions that shouldn't append automatically. So I think current version is more accurate as it is more C# like. I am sorry but there is no way to manage all these cases directly in the code of EE without breaking something or creating very specific conditions that should not be in the code of EE but customized by the user.

So for your case there are 5 solutions to manage it.

  1. You can use the build in and shorter version of Round (without Math, description here) it still make the conversion for you.
Round(1,0,MidpointRounding.AwayFromZero)
  1. You can cast back the second parameter to int
Math.Round(1,(int)0,MidpointRounding.AwayFromZero)
  1. You can manage the call of Math.Round in the event:
evaluator.PreEvaluateFunction+= PreEvaluateFunction;
private void PreEvaluateFunction(object sender, FunctionEvaluationEventArg e)
{
     if(e.Name.Equals("Round") && e.This is ClassOrEnumType classOrTypeName && classOrTypeName.Type == typeof(Math))
     // use e.EvaluateArgs() that you can cast and use how you want.
     // and return the result in e.Value
}
  1. You can specify yourself how you want to cast parameters in specific case with the event : (There is no documentation for this one yet, just a talk in an other issue that I didn't found again. but everything is manageable with the e argument)
evaluator.EvaluateParameterCast += EvaluateParameterCast;
  1. You keep : OptionForceIntegerNumbersEvaluationsAsDoubleByDefault = false and use the C# writing:
Math.Round(1d,0,MidpointRounding.AwayFromZero)

codingseb avatar Aug 04 '21 20:08 codingseb

Thanks, it works well with:

evaluator.EvaluateParameterCast += Evaluator_EvaluateParameterCast;

[...]

private static void Evaluator_EvaluateParameterCast(object? sender, ParameterCastEvaluationEventArg e)
{
    if (e.MethodInfo.DeclaringType == typeof(Math) && e.MethodInfo.Name == "Round" && e.ParameterType == typeof(int) && e.OriginalArg is double original)
    {
        e.Argument = (int) original;
    }
}

midgleyc avatar Aug 05 '21 08:08 midgleyc