diff --git a/src/unity.c b/src/unity.c index 6313cd5..aceedb5 100644 --- a/src/unity.c +++ b/src/unity.c @@ -263,20 +263,27 @@ static void UnityPrintDecimalAndNumberWithLeadingZeros(_US32 fraction_part, _US3 } } #ifndef UNITY_ROUND_TIES_AWAY_FROM_ZERO - #define ROUND_TIES_TO_EVEN(num_int, num) \ - if ((num_int & 1) == 1 && num_int > (num)) /* Odd and was rounded up */ \ - if ((num) - (_US32)(num) <= 0.5) num_int -= 1 /* and remainder was 0.5, a tie */ +/* If rounds up && remainder 0.5 && result odd && below cutoff for double precision issues */ + #define ROUND_TIES_TO_EVEN(orig, num_int, num) \ + if (num_int > (num) && (num) - (num_int-1) <= 0.5 && (num_int & 1) == 1 && orig < 1e22) \ + num_int -= 1 /* => a tie to round down to even */ #else - #define ROUND_TIES_TO_EVEN(num_int, num) + #define ROUND_TIES_TO_EVEN(orig, num_int, num) /* Remove macro */ #endif -/* - * char buffer[19]; - * if (number > 4294967296.0 || -number > 4294967296.0) - * snprintf(buffer, sizeof buffer, "%.8e", number); - * else - * snprintf(buffer, sizeof buffer, "%.6f", number); - * UnityPrint(buffer); +/* Printing floating point numbers is hard. Some goals of this implementation: works for embedded + * systems, floats or doubles, and has a reasonable format. The key paper in this area, + * 'How to Print Floating-Point Numbers Accurately' by Steele & White, shows an approximation by + * scaling called Dragon 2. This code uses a similar idea. The other core algorithm uses casts and + * floating subtraction to give exact remainders after the decimal, to be scaled into an integer. + * Extra trailing 0's are excluded. The output defaults to rounding to nearest, ties to even. You + * can enable rounding ties away from zero. Note: the _UD parameter can typedef to float or double. + + * The old version required compiling in snprintf. For reference, with a similar format as now: + * char buf[19]; + * if (number > 4294967296.0 || -number > 4294967296.0) snprintf(buf, sizeof buf, "%.8e", number); + * else snprintf(buf, sizeof buf, "%.6f", number); + * UnityPrint(buf); */ void UnityPrintFloat(_UD number) { @@ -293,9 +300,9 @@ void UnityPrintFloat(_UD number) { const _US32 divisor = (1000000/10); _UU32 integer_part = (_UU32)number; - _US32 fraction_part = (_US32)((number - integer_part)*1000000.0 + 0.5); + _US32 fraction_part = (_US32)((number - (_UD)integer_part)*1000000.0 + 0.5); /* Double precision calculation gives best performance for six rounded decimal places */ - ROUND_TIES_TO_EVEN(fraction_part, (number - integer_part)*1000000.0); + ROUND_TIES_TO_EVEN(number, fraction_part, (number - (_UD)integer_part)*1000000.0); if (fraction_part == 1000000) /* Carry across the decimal point */ { @@ -320,7 +327,7 @@ void UnityPrintFloat(_UD number) } integer_part = (_US32)(number / divide + 0.5); /* Double precision calculation required for float, to produce 9 rounded digits */ - ROUND_TIES_TO_EVEN(integer_part, number / divide); + ROUND_TIES_TO_EVEN(number, integer_part, number / divide); UNITY_OUTPUT_CHAR('0' + integer_part / divisor); UnityPrintDecimalAndNumberWithLeadingZeros(integer_part % divisor, divisor / 10); diff --git a/test/tests/testunity.c b/test/tests/testunity.c index b9f6fdc..c52af67 100644 --- a/test/tests/testunity.c +++ b/test/tests/testunity.c @@ -5,7 +5,6 @@ ========================================== */ #include "unity.h" -#include #include #include @@ -61,19 +60,20 @@ static int SetToOneMeanWeAlreadyCheckedThisGuy; void setUp(void) { - SetToOneToFailInTearDown = 0; - SetToOneMeanWeAlreadyCheckedThisGuy = 0; + SetToOneToFailInTearDown = 0; + SetToOneMeanWeAlreadyCheckedThisGuy = 0; } void tearDown(void) { - if (SetToOneToFailInTearDown == 1) - TEST_FAIL_MESSAGE("<= Failed in tearDown"); - if ((SetToOneMeanWeAlreadyCheckedThisGuy == 0) && (Unity.CurrentTestFailed > 0)) - { - UnityPrint(": [[[[ Test Should Have Passed But Did Not ]]]]"); - UNITY_OUTPUT_CHAR('\n'); - } + endPutcharSpy(); /* Stop suppressing test output */ + if (SetToOneToFailInTearDown == 1) + TEST_FAIL_MESSAGE("<= Failed in tearDown"); + if ((SetToOneMeanWeAlreadyCheckedThisGuy == 0) && (Unity.CurrentTestFailed > 0)) + { + UnityPrint(": [[[[ Test Should Have Passed But Did Not ]]]]"); + UNITY_OUTPUT_CHAR('\n'); + } } void testUnitySizeInitializationReminder(void) @@ -2238,7 +2238,7 @@ void testIgnoredAndThenFailInTearDown(void) #endif #ifdef USING_OUTPUT_SPY -int putchar(int); +#include #define SPY_BUFFER_MAX 40 static char putcharSpyBuffer[SPY_BUFFER_MAX]; #endif @@ -2267,7 +2267,7 @@ void putcharSpy(int c) if (indexSpyBuffer < SPY_BUFFER_MAX - 1) putcharSpyBuffer[indexSpyBuffer++] = (char)c; } else - c = putchar(c); + putchar((char)c); #endif } @@ -3305,11 +3305,105 @@ void testFloatPrintingInfinityAndNaN(void) #if defined(UNITY_EXCLUDE_FLOAT) || !defined(USING_OUTPUT_SPY) TEST_IGNORE(); #else - TEST_ASSERT_EQUAL_PRINT_FLOATING("Inf", 3.40282346638e38f*2.0f); TEST_ASSERT_EQUAL_PRINT_FLOATING("Inf", 1.0f / f_zero); - TEST_ASSERT_EQUAL_PRINT_FLOATING("-Inf", -3.40282346638e38f*2.0f); + TEST_ASSERT_EQUAL_PRINT_FLOATING("-Inf", -1.0f / f_zero); - TEST_ASSERT_EQUAL_PRINT_FLOATING("NaN", -3.40282346638e38f*2.0f * f_zero); + TEST_ASSERT_EQUAL_PRINT_FLOATING("NaN", 0.0f / f_zero); +#endif +} + +#if defined(UNITY_TEST_ALL_FLOATS_PRINT_OK) && defined(USING_OUTPUT_SPY) +static void AllFloatPrinting_LessThan32Bits(void) +{ + char expected[18]; + union { float f_value; int32_t int_value; } u; + /* Float representations are laid out in integer order, walk up the list */ + for (u.f_value = 0.00000050000005f; u.f_value <= 4294967040.0f; u.int_value += 1) + { + startPutcharSpy(); + + UnityPrintFloat(u.f_value); /*1.5x as fast as sprintf 5e-7f - 0.01f, 20s vs 30s*/ + int len = sprintf(expected, "%.6f", u.f_value); + + while (expected[len - 1] == '0' && expected[len - 2] != '.') { len--; } + expected[len] = '\0'; /* delete trailing 0's */ + + if (strcmp(expected, getBufferPutcharSpy()) != 0) + { + double six_digits = ((double)u.f_value - (uint32_t)u.f_value)*1000000.0; + /* Not a tie (remainder != 0.5) => Can't explain the different strings */ + if (six_digits - (uint32_t)six_digits != 0.5) + { + /* Fail with diagnostic printing */ + TEST_ASSERT_EQUAL_PRINT_FLOATING(expected, u.f_value); + } + } + } +} + +/* Compared to perfect, floats are occasionally rounded wrong. It doesn't affect + * correctness, though. Two examples (of 13 total found during testing): + * Printed: 6.19256349e+20, Exact: 619256348499999981568.0f <= Eliminated by ROUND_TIES_TO_EVEN + * Printed: 2.19012272e+35, Exact: 219012271499999993621766990196637696.0f */ +static void AllFloatPrinting_Larger(const float start, const float end) +{ + unsigned int wrong = 0; + char expected[18]; + union { float f_value; int32_t int_value; } u; + for (u.f_value = start; u.f_value <= end; u.int_value += 1) + { + startPutcharSpy(); + + UnityPrintFloat(u.f_value); /*Twice as fast as sprintf 2**32-1e12, 10s vs 21s*/ + sprintf(expected, "%.8e", u.f_value); + + int len = 11 - 1; /* 11th char is 'e' in exponential format */ + while (expected[len - 1] == '0' && expected[len - 2] != '.') { len --; } + if (expected[14] != '\0') memmove(&expected[12], &expected[13], 3); /* Two char exponent */ + memmove(&expected[len], &expected[11 - 1], sizeof "e+09"); /* 5 char length */ + + if (strcmp(expected, getBufferPutcharSpy()) != 0) + { + wrong++; + /* endPutcharSpy(); UnityPrint("Expected "); UnityPrint(expected); + UnityPrint(" Was "); UnityPrint(getBufferPutcharSpy()); UNITY_OUTPUT_CHAR('\n'); */ + + if (wrong > 10 || (wrong > 3 && end <= 1e25f)) + TEST_ASSERT_EQUAL_PRINT_FLOATING(expected, u.f_value); + /* Empirical values from the current routine, don't be worse when making changes */ + } + } +} +#endif + +/* Exhaustive testing of all float values we differentiate when printing. Doubles + * are not explored here -- too many. These tests confirm that the routine works + * for all floats > 5e-7, positives only. Off by default due to test time. + * Compares Unity's routine to your sprintf() C lib, tested to pass on 3 platforms. + * Part1 takes a long time, around 3 minutes compiled with -O2 + * Runs through all floats from 0.000001 - 2**32, ~300 million values */ +void testAllFloatPrintingPart1_LessThan32Bits(void) +{ +#if defined(UNITY_TEST_ALL_FLOATS_PRINT_OK) && defined(USING_OUTPUT_SPY) + AllFloatPrinting_LessThan32Bits(); +#else + TEST_IGNORE(); /* Ignore one of three */ +#endif +} + +/* Test takes a long time, around 3.5 minutes compiled with -O2, try ~500 million values */ +void testAllFloatPrintingPart2_Larger(void) +{ +#if defined(UNITY_TEST_ALL_FLOATS_PRINT_OK) && defined(USING_OUTPUT_SPY) + AllFloatPrinting_Larger(4294967296.0f, 1e25f); +#endif +} + +/* Test takes a long time, around 3.5 minutes compiled with -O2, try ~500 million values */ +void testAllFloatPrintingPart3_LargerStill(void) +{ +#if defined(UNITY_TEST_ALL_FLOATS_PRINT_OK) && defined(USING_OUTPUT_SPY) + AllFloatPrinting_Larger(1e25f, 3.40282347e+38f); #endif } @@ -3878,11 +3972,10 @@ void testDoublePrintingInfinityAndNaN(void) #if defined(UNITY_EXCLUDE_DOUBLE) || !defined(USING_OUTPUT_SPY) TEST_IGNORE(); #else - TEST_ASSERT_EQUAL_PRINT_FLOATING("Inf", 1.7976931348623157e308*10.0); TEST_ASSERT_EQUAL_PRINT_FLOATING("Inf", 1.0 / d_zero); - TEST_ASSERT_EQUAL_PRINT_FLOATING("-Inf", -1.7976931348623157e308*10.0); + TEST_ASSERT_EQUAL_PRINT_FLOATING("-Inf", -1.0 / d_zero); - TEST_ASSERT_EQUAL_PRINT_FLOATING("NaN", -1.7976931348623157e308*10.0 * d_zero); + TEST_ASSERT_EQUAL_PRINT_FLOATING("NaN", 0.0 / d_zero); #endif }