From 19359d63fbd0bfde956ec95bf5db1aca02627bd5 Mon Sep 17 00:00:00 2001 From: Brad Glass <37139318+G18SSY@users.noreply.github.com> Date: Fri, 16 Aug 2019 12:05:05 +0100 Subject: [PATCH] Fix a bug when mixing floating point with integer (#32) * Add a check that ensures that the Min/Max functions run in the highest precision of the 2 parameters. Originally only the type of the first parameter was checked. Fixes a bug that would cause Min(2,1.97) to return 2 because 1.97 was converted to an int during expression evaluation. --- src/NCalc/Numbers.cs | 165 +++++++++++++++++++++++++++++------ test/NCalc.Tests/Fixtures.cs | 12 +++ 2 files changed, 151 insertions(+), 26 deletions(-) diff --git a/src/NCalc/Numbers.cs b/src/NCalc/Numbers.cs index 50535cb..5e3e36c 100644 --- a/src/NCalc/Numbers.cs +++ b/src/NCalc/Numbers.cs @@ -1,4 +1,6 @@ using System; +using System.Collections; +using System.Security.Cryptography; namespace NCalc { @@ -1775,32 +1777,32 @@ public static object Max(object a, object b) return a; } - TypeCode typeCodeA = a.GetTypeCode(); + TypeCode typeCode = ConvertToHighestPrecision(ref a, ref b); - switch (typeCodeA) + switch (typeCode) { case TypeCode.Byte: - return Math.Max((Byte)a, Convert.ToByte(b)); + return Math.Max((Byte)a, (Byte)b); case TypeCode.SByte: - return Math.Max((SByte)a, Convert.ToSByte(b)); + return Math.Max((SByte)a, (SByte)b); case TypeCode.Int16: - return Math.Max((Int16)a, Convert.ToInt16(b)); + return Math.Max((Int16)a, (Int16)b); case TypeCode.UInt16: - return Math.Max((UInt16)a, Convert.ToUInt16(b)); + return Math.Max((UInt16)a, (UInt16)b); case TypeCode.Int32: - return Math.Max((Int32)a, Convert.ToInt32(b)); + return Math.Max((Int32)a, (Int32)b); case TypeCode.UInt32: - return Math.Max((UInt32)a, Convert.ToUInt32(b)); + return Math.Max((UInt32)a, (UInt32)b); case TypeCode.Int64: - return Math.Max((Int64)a, Convert.ToInt64(b)); + return Math.Max((Int64)a, (Int64)b); case TypeCode.UInt64: - return Math.Max((UInt64)a, Convert.ToUInt64(b)); + return Math.Max((UInt64)a, (UInt64)b); case TypeCode.Single: - return Math.Max((Single)a, Convert.ToSingle(b)); + return Math.Max((Single)a, (Single)b); case TypeCode.Double: - return Math.Max((Double)a, Convert.ToDouble(b)); + return Math.Max((Double)a, (Double)b); case TypeCode.Decimal: - return Math.Max((Decimal)a, Convert.ToDecimal(b)); + return Math.Max((Decimal)a, (Decimal)b); } return null; @@ -1825,36 +1827,147 @@ public static object Min(object a, object b) return a; } - TypeCode typeCodeA = a.GetTypeCode(); + TypeCode typeCode = ConvertToHighestPrecision(ref a,ref b); - switch (typeCodeA) + switch (typeCode) { case TypeCode.Byte: - return Math.Min((Byte)a, Convert.ToByte(b)); + return Math.Min((Byte)a, (Byte)b); case TypeCode.SByte: - return Math.Min((SByte)a, Convert.ToSByte(b)); + return Math.Min((SByte)a, (SByte)b); case TypeCode.Int16: - return Math.Min((Int16)a, Convert.ToInt16(b)); + return Math.Min((Int16)a, (Int16)b); case TypeCode.UInt16: - return Math.Min((UInt16)a, Convert.ToUInt16(b)); + return Math.Min((UInt16)a, (UInt16)b); case TypeCode.Int32: - return Math.Min((Int32)a, Convert.ToInt32(b)); + return Math.Min((Int32)a, (Int32)b); case TypeCode.UInt32: - return Math.Min((UInt32)a, Convert.ToUInt32(b)); + return Math.Min((UInt32)a, (UInt32)b); case TypeCode.Int64: - return Math.Min((Int64)a, Convert.ToInt64(b)); + return Math.Min((Int64)a, (Int64)b); case TypeCode.UInt64: - return Math.Min((UInt64)a, Convert.ToUInt64(b)); + return Math.Min((UInt64)a, (UInt64)b); case TypeCode.Single: - return Math.Min((Single)a, Convert.ToSingle(b)); + return Math.Min((Single)a, (Single)b); case TypeCode.Double: - return Math.Min((Double)a, Convert.ToDouble(b)); + return Math.Min((Double)a, (Double)b); case TypeCode.Decimal: - return Math.Min((Decimal)a, Convert.ToDecimal(b)); + return Math.Min((Decimal)a, (Decimal)b); } return null; } + + private static TypeCode ConvertToHighestPrecision(ref object a, ref object b) + { + TypeCode typeCodeA = a.GetTypeCode(); + TypeCode typeCodeB = b.GetTypeCode(); + + if (typeCodeA==typeCodeB) + return typeCodeA; + + if (!(TypeCodeBitSize(typeCodeA, out bool floatingPointA) is int bitSizeA)) + return TypeCode.Empty; + if (!(TypeCodeBitSize(typeCodeB, out bool floatingPointB) is int bitSizeB)) + return TypeCode.Empty; + + if (floatingPointA != floatingPointB) + { + if (floatingPointA) + { + b = ConvertTo(b, typeCodeA); + + return typeCodeA; + } + else + { + a = ConvertTo(a, typeCodeB); + + return typeCodeB; + } + } + + if (bitSizeA > bitSizeB) + { + b = ConvertTo(b, typeCodeA); + + return typeCodeA; + } + else + { + a = ConvertTo(a, typeCodeB); + + return typeCodeB; + } + + } + + private static int? TypeCodeBitSize(TypeCode typeCode,out bool floatingPoint) + { + floatingPoint = false; + switch (typeCode) + { + case TypeCode.SByte: return 8; + case TypeCode.Byte: return 8; + case TypeCode.Int16: return 16; + case TypeCode.UInt16: return 16; + case TypeCode.Int32: return 32; + case TypeCode.UInt32: return 32; + case TypeCode.Int64: return 64; + case TypeCode.UInt64: return 64; + case TypeCode.Single: + floatingPoint = true; + return 32; + case TypeCode.Double: + floatingPoint = true; + return 64; + case TypeCode.Decimal: + floatingPoint = true; + return 128; + default: return null; + } + } + + private static object ConvertTo(object value, TypeCode toType) + { + switch (toType) + { + case TypeCode.Byte: + return Convert.ToByte(value); + + case TypeCode.SByte: + return Convert.ToSByte(value); + + case TypeCode.Int16: + return Convert.ToInt16(value); + + case TypeCode.UInt16: + return Convert.ToUInt16(value); + + case TypeCode.Int32: + return Convert.ToInt32(value); + + case TypeCode.UInt32: + return Convert.ToUInt32(value); + + case TypeCode.Int64: + return Convert.ToInt64(value); + + case TypeCode.UInt64: + return Convert.ToUInt64(value); + + case TypeCode.Single: + return Convert.ToSingle(value); + + case TypeCode.Double: + return Convert.ToDouble(value); + + case TypeCode.Decimal: + return Convert.ToDecimal(value); + } + + return null; + } } } diff --git a/test/NCalc.Tests/Fixtures.cs b/test/NCalc.Tests/Fixtures.cs index 64bf144..a73dc6f 100644 --- a/test/NCalc.Tests/Fixtures.cs +++ b/test/NCalc.Tests/Fixtures.cs @@ -655,6 +655,18 @@ public void ShouldAddDoubleAndDecimal() Assert.Equal(11M, e.Evaluate()); } + + [InlineData("Min(2,1.97)",1.97)] + [InlineData("Max(2,2.33)",2.33)] + [Theory] + public void ShouldCheckPrecisionOfBothParametersForMaxAndMin(string expression, double expected) + { + var e=new Expression(expression); + + var result = e.Evaluate(); + + Assert.Equal(expected,result); + } } }