diff --git a/Tests/NFUnitTestConversions/UnitTestConvertTests.cs b/Tests/NFUnitTestConversions/UnitTestConvertTests.cs index f679069d..b9573542 100644 --- a/Tests/NFUnitTestConversions/UnitTestConvertTests.cs +++ b/Tests/NFUnitTestConversions/UnitTestConvertTests.cs @@ -53,7 +53,6 @@ public void Cast_FloatingPoint() public void Convert_Positive() { string number = "12"; - int actualNumber = 12; SByte value_sb = Convert.ToSByte(number); Assert.Equal(value_sb, (byte)12); Byte value_b = Convert.ToByte(number); @@ -76,7 +75,6 @@ public void Convert_Positive() public void Convert_PositivePlus() { string number = "+12"; - int actualNumber = 12; SByte value_sb = Convert.ToSByte(number); Assert.Equal(value_sb, (byte)12); Byte value_b = Convert.ToByte(number); @@ -101,19 +99,18 @@ public void Convert_Negative() { string number = "-12"; int actualNumber = -12; - SByte value_sb = Convert.ToSByte(number); - Assert.Equal(value_sb, (sbyte)actualNumber); - Assert.Throws(typeof(ArgumentOutOfRangeException), () => { Byte value_b = Convert.ToByte(number); }); + Assert.Equal(value_sb, (sbyte)actualNumber, "Test1"); + Assert.Throws(typeof(ArgumentOutOfRangeException), () => { Byte value_b = Convert.ToByte(number); }, "Test2"); Int16 value_s16 = Convert.ToInt16(number); - Assert.Equal(value_s16, (short)actualNumber); - Assert.Throws(typeof(ArgumentOutOfRangeException), () => { UInt16 value_u16 = Convert.ToUInt16(number); }); + Assert.Equal(value_s16, (short)actualNumber, "Test3"); + Assert.Throws(typeof(ArgumentOutOfRangeException), () => { UInt16 value_u16 = Convert.ToUInt16(number); }, "Test4"); Int32 value_s32 = Convert.ToInt32(number); - Assert.Equal(value_s32, (int)actualNumber); - Assert.Throws(typeof(ArgumentOutOfRangeException), () => { UInt32 value_u32 = Convert.ToUInt32(number); }); + Assert.Equal(value_s32, (int)actualNumber, "Test5"); + Assert.Throws(typeof(ArgumentOutOfRangeException), () => { UInt32 value_u32 = Convert.ToUInt32(number); }, "Test6"); Int64 value_s64 = Convert.ToInt32(number); - Assert.Equal(value_s64, (long)actualNumber); - Assert.Throws(typeof(ArgumentOutOfRangeException), () => { UInt64 value_u64 = Convert.ToUInt64(number); }); + Assert.Equal(value_s64, (long)actualNumber, "Test7"); + Assert.Throws(typeof(ArgumentOutOfRangeException), () => { UInt64 value_u64 = Convert.ToUInt64(number); }, "Test8"); } [TestMethod] @@ -166,14 +163,32 @@ public void Convert_Whitespace() public void Convert_DoubleNormalizeNeg() { string number = "-3600030383448481.123456"; - double actualNumber = -3600030383448481.123456; + double actualNumber = -3600030383448481.123456; // note: this is the double as calculated by the Roslyn compiler - not the same as the native code routine double value_dd = Convert.ToDouble(number); - Assert.Equal(value_dd, actualNumber); - number = "+0.00000000484874758559e-3"; - actualNumber = 4.84874758559e-12; - Assert.Equal(actualNumber, Convert.ToDouble(number)); + Assert.Equal(value_dd, actualNumber, $"Convert.ToDouble should be {number}"); // this works if the numbers are fairly small - i.e. not e^x sized + + // Examples of how we can get differences due to differences between the compiler parser, and the native parser + // And differences caused by the native parsers "fast speed" causing rounding errors + string num1 = "+0.000000004848747585e-3"; + string num2 = "4.848747585e-12"; // same number as above, but we've moved the digits over a bit and adjusted the exponent + double dnum1Roslyn = +0.000000004848747585e-3; // Roslyn compiler will parse the value and put it into the double at compile time + double dnum1Native = Convert.ToDouble(num1); // Native code will parse the value and put it into the double at run time + double dnum2Roslyn = 4.848747585e-12; // Compiler + double dnum2Native = Convert.ToDouble(num2); // Native + + + // Now we will compare some of the ToString values + // compare native to native - but parsing e-3 versus e-12 means 9 more loops thru the double multiplication where rounding can occur so this won't work for all numbers + Assert.Equal(dnum1Native.ToString(), dnum2Native.ToString(), $"Comparing native parse tostring"); + // compare roslyn to roslyn - the roslyn parser is more accurate and that means the double is much more likely to be the same + Assert.Equal(dnum1Roslyn.ToString(), dnum2Roslyn.ToString(), $"Comparing Roslyn parse tostring"); + // Now mix things up + Assert.Equal(dnum1Roslyn.ToString(), dnum2Native.ToString(), $"Comparing Roslyn parse and native parse tostring"); + Assert.Equal(dnum1Native.ToString(), dnum2Roslyn.ToString(), $"Comparing Roslyn parse and native parse tostring"); + Assert.Equal(dnum1Roslyn.ToString(), dnum1Native.ToString(), $"Comparing Roslyn to native using {num1}"); + Assert.Equal(dnum2Roslyn.ToString(), dnum2Native.ToString(), $"Comparing Roslyn to natvie using {num2}"); } [TestMethod] @@ -198,48 +213,128 @@ public void Convert_HexInt() } [TestMethod] + public void Convert_BoundaryValues() { - double valMax = double.MaxValue; - string numMax = valMax.ToString(); - double valMin = double.MinValue; - string numMin = valMin.ToString(); - - Assert.Equal(valMax, Convert.ToDouble(numMax)); - Assert.Equal(valMin, Convert.ToDouble(numMin)); - - valMax = float.MaxValue; - numMax = valMax.ToString(); - valMin = float.MinValue; - numMin = valMin.ToString(); - - Assert.Equal(valMax, Convert.ToDouble(numMax)); - Assert.Equal(valMin, Convert.ToDouble(numMin)); + //*** + //* Boundary tests - tests of the min and max values for double, float and int's. + //* Note for double/float - the ToString() function is limited to a range around 2^64 and 2^-64 - otherwise you get a string of 'oor' or '-oor' (oor = out-of-range) + // Boundary tests for double/float include the numbers that are around the edge of where out-of-range is produced. + //*** + + const string OUT_OF_RANGE = "oor"; // nanoPrintf can only print up to 2^64-2 as a max value for double/floating + const string OUT_OF_RANGE_NEG = "-oor"; // nanoPrintf can only print down to -2^64+2 as a min value for double/floating + + const string DOUBLE_MAX_VAL = "1.7976931348623157E+308"; // will get 'oor' when printed + const string DOUBLE_MAX_HEX = "0x7FEFFFFFFFFFFFFF"; // value from IEEE 574 + const string DOUBLE_MIN_VAL = "-1.7976931348623157E+308"; // will get '-oor' when printed + const string DOUBLE_MIN_HEX = "0xFFEFFFFFFFFFFFFF"; // value from IEEE 574 + const string DOUBLE_ZERO_HEX = "0x0000000000000000"; + const string DOUBLE_LARGEST_PRINT = "1.84467440737095E+19"; // this is the largest number you can ask for ToString() and get a response + const string DOUBLE_LARGESTINVALID_PRINT = "1.8446744073709552E+19"; // first positive value that will get the response 'oor' when printed + const string DOUBLE_SMALLEST_PRINT = "-1.32585973029787E+19"; // this is the smallest number you can ask for ToString() and get a response + const string DOUBLE_SMALLESTINVALID_PRINT = "-1.8446744073709552E+19"; // first negative value that will get the response '-oor' when printed + + const string FLOAT_MAX_VAL = "3.40282347E+38"; + const string FLOAT_MAX_HEX = "0x7F7FFFFF"; // will get 'oor' when printed + const string FLOAT_MIN_VAL = "-3.40282347E+38"; + const string FLOAT_MIN_HEX = "0xFF7FFFFF"; // will get '-oor' when printed + const string FLOAT_ZERO_HEX = "0x00000000"; + const string FLOAT_LARGEST_PRINT = "1.844674E+19"; // this is the largest number you can ask for ToString() and get a response + const string FLOAT_LARGESTINVALID_PRINT = "1.8446744E+19"; // first positive value that will get the response 'oor' when printed + const string FLOAT_SMALLEST_PRINT = "-1.844674E+19"; // this is the smallest number you can ask for ToString() and get a response + const string FLOAT_SMALLESTINVALID_PRINT = "-1.8446744E+19"; // first negative value that will get the response '-oor' when printed + + // boundary: double max + string time = DateTime.UtcNow.ToString("hh:mm:ss"); + double doubleMax = double.MaxValue; + Assert.Equal(doubleMax.ToString(), OUT_OF_RANGE, "nanoPrintf returns oor for double > 2^64-2"); + Assert.Equal(DoubleToHex(doubleMax), DOUBLE_MAX_HEX, "Hex value to double max value does not match"); + Assert.Equal(DoubleToHex(Convert.ToDouble(DOUBLE_MAX_VAL)), DOUBLE_MAX_HEX, "Parsing double max value does not return correct hex value"); + + // boundary: double min + double doubleMin = double.MinValue; + Assert.Equal(doubleMin.ToString(), OUT_OF_RANGE_NEG, "nanoPrintf returns oor for double < -2^64+2"); + Assert.Equal(DoubleToHex(doubleMin), DOUBLE_MIN_HEX,"Hex value to double min value does not match"); + Assert.Equal(DoubleToHex(Convert.ToDouble(DOUBLE_MIN_VAL)), DOUBLE_MIN_HEX, "Parsing double min value does not return correct hex value"); + + // boundary: double zero + double doubleZero = 0; // test that zero gets a zero exponent and a value like 1023 the exponent bias used in floating point math + Assert.Equal(doubleZero.ToString(), "0", "ToString of a double with zero value formats incorrectly"); + Assert.Equal(DoubleToHex(doubleZero), DOUBLE_ZERO_HEX, "Double with zero value returns the wrong hex value"); + + // boundary: double largest-in-range + double doubleInRange = Convert.ToDouble(DOUBLE_LARGEST_PRINT); + Assert.Equal(doubleInRange.ToString(), DOUBLE_LARGEST_PRINT, "Double.ToString did not return the correct value for largest in range value"); + + // boundary: double largest-out-of-range + double doubleOutRange = Convert.ToDouble(DOUBLE_LARGESTINVALID_PRINT); + Assert.Equal(doubleOutRange.ToString(), OUT_OF_RANGE, "Double.ToString did not return 'oor' for first out-of-range value"); + + // boundary: double smallest-in-range + double doubleInRangeNeg = Convert.ToDouble(DOUBLE_SMALLEST_PRINT); + Assert.Equal(doubleInRangeNeg.ToString(), DOUBLE_SMALLEST_PRINT, "Double.ToString did not return the correct value for smallest in range value"); + + // boundary: double smallest-out-of-range + double doubleOutRangeNeg = Convert.ToDouble(DOUBLE_SMALLESTINVALID_PRINT); + Assert.Equal(doubleOutRangeNeg.ToString(), OUT_OF_RANGE_NEG, "Double.ToString did not return 'oor' for smallest out-of-range value"); + + // boundary: float max + float floatMax = float.MaxValue; + Assert.Equal(floatMax.ToString(), OUT_OF_RANGE, "nanoPrintf return oor for float > 2^64-2"); + Assert.Equal(FloatToHex(floatMax), FLOAT_MAX_HEX, "Hex value to float max values does not match"); + Assert.Equal(FloatToHex((float)Convert.ToDouble(FLOAT_MAX_VAL)), FLOAT_MAX_HEX, "Parsing float max value does not return correct hex value"); + + // boundary: float min + float floatMin = float.MinValue; + Assert.Equal(floatMin.ToString(), OUT_OF_RANGE_NEG, "nanoPrintf returns oor for float < -2^64+2"); + Assert.Equal(FloatToHex(floatMin), FLOAT_MIN_HEX, "Hex value to double min value does not match"); + Assert.Equal(FloatToHex((float)Convert.ToDouble(FLOAT_MIN_VAL)), FLOAT_MIN_HEX, "Parsing float min value does not return correct hex value"); + + //boundary: float zero + float floatZero = 0; // test that zero gets a zero exponent and not a value like 1023 the exponent bias used in floating point math + Assert.Equal(floatZero.ToString(), "0", "ToString of a string with zero value formats incorrectly"); + Assert.Equal(FloatToHex(floatZero), FLOAT_ZERO_HEX, "Float with zero value returns the wrong hex value"); + + // boundary: float largest-in-range + float floatInRange = (float)Convert.ToDouble(FLOAT_LARGEST_PRINT); + Assert.Equal(floatInRange.ToString(), FLOAT_LARGEST_PRINT, "Float.ToString did not return the correct value for largest in range value"); + + // boundary: float largest-out-of-range + float floatOutRange = (float)Convert.ToDouble(FLOAT_LARGESTINVALID_PRINT); + Assert.Equal(floatOutRange.ToString(), OUT_OF_RANGE, "Float.ToString did not return 'oor' for first out-of-range value"); + + // boundary: float smallest-in-range + float floatInRangeNeg = (float)Convert.ToDouble(FLOAT_SMALLEST_PRINT); + Assert.Equal(floatInRangeNeg.ToString(), FLOAT_SMALLEST_PRINT, "Float.ToString did not return the correct value for smallest in range value"); + + // boundary: float smallest-out-of-range + float floatOutRangeNeg = (float)Convert.ToDouble(FLOAT_SMALLESTINVALID_PRINT); + Assert.Equal(floatOutRangeNeg.ToString(), OUT_OF_RANGE_NEG, "Float.ToString did not return 'oor' for smallest out-of-range value"); long lMax = long.MaxValue; - numMax = lMax.ToString(); + string numMax = lMax.ToString(); long lMin = long.MinValue; - numMin = lMin.ToString(); + string numMin = lMin.ToString(); - Assert.Equal(lMax, Convert.ToInt64(numMax)); - Assert.Equal(lMin, Convert.ToInt64(numMin)); + Assert.Equal(lMax, Convert.ToInt64(numMax), "Int64 Max"); + Assert.Equal(lMin, Convert.ToInt64(numMin), "Int64 Min"); ulong ulMax = ulong.MaxValue; numMax = ulMax.ToString(); ulong ulMin = ulong.MinValue; numMin = ulMin.ToString(); - Assert.Equal(ulMax, Convert.ToUInt64(numMax)); - Assert.Equal(ulMin, Convert.ToUInt64(numMin)); - + Assert.Equal(ulMax, Convert.ToUInt64(numMax), "UInt64 Max"); + Assert.Equal(ulMin, Convert.ToUInt64(numMin), "UInt64 Min"); long iMax = int.MaxValue; numMax = iMax.ToString(); long iMin = int.MinValue; numMin = iMin.ToString(); - Assert.Equal(iMax, Convert.ToInt32(numMax)); - Assert.Equal(iMin, Convert.ToInt32(numMin)); + Assert.Equal(iMax, Convert.ToInt32(numMax), "Int32 Max"); + Assert.Equal(iMin, Convert.ToInt32(numMin), "Int32 Min"); uint uiMax = uint.MaxValue; numMax = uiMax.ToString(); @@ -270,6 +365,10 @@ public void Convert_BoundaryValues() sbyte sbMin = sbyte.MinValue; numMin = sbMin.ToString(); + sbMax = Convert.ToSByte(numMax); + + sbMin = Convert.ToSByte(numMin); + Assert.Equal(sbMax, Convert.ToSByte(numMax)); Assert.Equal(sbMin, Convert.ToSByte(numMin)); @@ -280,6 +379,7 @@ public void Convert_BoundaryValues() Assert.Equal(bMax, Convert.ToByte(numMax)); Assert.Equal(bMin, Convert.ToByte(numMin)); + } @@ -369,5 +469,46 @@ public void Convert_FromBoolean() Assert.Equals(convTrueIsOne, 1); Assert.Equals(convFalseIsZero, 0); } + + #region Double/Floating point number helpers + + /// + /// Converts the given double to a hexidecimal display - to be used to test boundary cases. + /// + /// The double to convert. + /// "+Infinity", "-Infinity", "NaN" or "0x[16 hex bytes]" + public static string DoubleToHex(double d) + { + if (double.IsPositiveInfinity(d)) + return "+Infinity"; + if (double.IsNegativeInfinity(d)) + return "-Infinity"; + if (double.IsNaN(d)) + return "NaN"; + + string returnValue = string.Format("0x{0:X16}", BitConverter.DoubleToInt64Bits(d)); + return returnValue; + } + + /// + /// Converts the given double to a hexidecimal display - to be used to test boundary cases. + /// + /// The single (float) to convert. + /// "+Infinity", "-Infinity", "NaN" or "0x[8 hex bytes]" + public static string FloatToHex(float f) + { + if (float.IsPositiveInfinity(f)) + return "+Infinity"; + if (float.IsNegativeInfinity(f)) + return "-Infinity"; + if (float.IsNaN(f)) + return "NaN"; + string returnValue = string.Format("0x{0:X8}", BitConverter.ToInt32(BitConverter.GetBytes(f),0)); // CoreLibrary/mscorlib does not implement SingleToInt32Bits + return returnValue; + } + + + #endregion + } } diff --git a/nanoFramework.CoreLibrary/System/Convert.cs b/nanoFramework.CoreLibrary/System/Convert.cs index 3944aade..ff3aa539 100644 --- a/nanoFramework.CoreLibrary/System/Convert.cs +++ b/nanoFramework.CoreLibrary/System/Convert.cs @@ -205,7 +205,7 @@ public static long ToInt64(string value, int fromBase = 10) [CLSCompliant(false)] public static ulong ToUInt64(string value, int fromBase = 10) { - return (ulong)NativeToInt64(value.Trim(), true, 0, 0, fromBase); + return (ulong)NativeToInt64(value.Trim(), false, 0, 0, fromBase); // the interface use long for min/max, and uint64 is bigger. Setting min/max to 0/0 will cause the native code to calculate the largest value and return it as Int64 which when cast to UInt64 returns the larger numbers that a UInt64 can reach } ///