Is this a bug in MiscUtils or am I missing something?
decimal a = (1M/30);
int b = 59;
Assert.AreEqual(a*b, Operator.MultiplyAlternative(a, b));
Assert.AreEqual(b*a, Operator.MultiplyAlternative(b, a));
Fails on the last line:
expected: <1.9666666666666666666666666647>
but was: <0>
Update
As Petr pointed out there is some coercion going on in the CreateExpression method which is causing the problem. When using the compiler we don't see such issues because the parameters are lifted to the type with the highest precision, which also becomes the return type.
The second 'Assert' should have failed anyway, because if consistent with normal C# behaviour, I would expect it to lift the first parameter (b) to a decimal
and perform the operation. Normally, if we wanted to store the result to a variable of a type with lower precision we would need to do an explicit cast. However, since the method we are calling has a return type that may be of lower precision and the caller has explicitly invoked the method that returns a lower precision result - it seems justifiable to automatically perform a potentially truncating cast as part of the operation. In other words the expected result from the second expression would be 1
.
So, we can change CreateExpression to reflect that behaviour as follows:
if (castArgsToResultOnFailure && !( // if we show retry
typeof(TArg1) == typeof(TResult) && // and the args aren't
typeof(TArg2) == typeof(TResult)))
{ // already "TValue, TValue, TValue"...
var ltc = Type.GetTypeCode(lhs.Type);
var rtc = Type.GetTypeCode(rhs.Type);
// Use the higher precision element
if (ltc > rtc)
{
// TArg1/TResult is higher precision than TArg2. Simply lift rhs
var castRhs = Expression.Convert(rhs, lhs.Type);
return
Expression.Lambda<Func<TArg1, TArg2, TResult>>(body(lhs, castRhs), lhs, rhs).Compile();
}
// TArg2 is higher precision than TArg1/TResult. Lift lhs and Cast result
var castLhs = Expression.Convert(lhs, rhs.Type);
var castResult = Expression.Convert(body(castLhs, rhs), lhs.Type);
return Expression.Lambda<Func<TArg1, TArg2, TResult>>(castResult, lhs, rhs).Compile();
}
The second assertion therefore needs to be rewritten:
Assert.AreEqual((int)(b*a), Operator.MultiplyAlternative(b, a));
Now both assertions succeed. As before, depending on the order of the parameters, different results will be returned, but now the second invocation produces a result that is logically correct.