TestMathHelp.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. ///////////////////////////////////////////////////////////////////////////////
  2. // Copyright (c) Electronic Arts Inc. All rights reserved.
  3. ///////////////////////////////////////////////////////////////////////////////
  4. #include <EABase/eabase.h>
  5. #include <EAStdC/internal/Config.h>
  6. #include <EAStdC/EAMathHelp.h>
  7. #include <EAStdC/EARandom.h>
  8. #include <EAStdC/EABitTricks.h>
  9. #include <EAStdCTest/EAStdCTest.h>
  10. #include <EATest/EATest.h>
  11. #include <EASTL/string.h>
  12. #include <EAAssert/eaassert.h>
  13. EA_DISABLE_ALL_VC_WARNINGS()
  14. #include <float.h>
  15. #include <stdio.h>
  16. #include <math.h>
  17. EA_RESTORE_ALL_VC_WARNINGS()
  18. using namespace EA::StdC;
  19. template<class T, class U, size_t count>
  20. bool TestArray(const char *testname, T (&values)[count], U (*testFunc)(T), U (*referenceFunc)(T), bool round_adjust = false, bool unit_only = false)
  21. {
  22. typedef U tResult;
  23. //const bool isUnsigned = ((T)-1 >= 0);
  24. for(size_t i = 0; i < count; ++i)
  25. {
  26. const T& v = values[i];
  27. //if (isUnsigned && (v < 0)) // This can never be true.
  28. // continue;
  29. if (unit_only && (v < 0 || v > 1))
  30. continue;
  31. const tResult testResult = testFunc(v);
  32. const tResult refResult = referenceFunc(v);
  33. if (testResult != refResult)
  34. {
  35. // If we are testing a rounding function, account for differences between
  36. // always-up and round-to-nearest-even
  37. if (round_adjust)
  38. {
  39. if (v == ((int32_t)v + ((v < 0) ? -0.5f : +0.5f)))
  40. {
  41. if (refResult == testFunc(v - 0.01f) ||
  42. refResult == testFunc(v + 0.01f))
  43. {
  44. continue;
  45. }
  46. }
  47. }
  48. EA::UnitTest::Report(" Function \"%s\" FAILED at index %d\n", testname, (int)i);
  49. EA::UnitTest::Report(" input[i]: %s\n", eastl::to_string(v).c_str());
  50. EA::UnitTest::Report(" test function: %s\n", eastl::to_string(testResult).c_str());
  51. EA::UnitTest::Report(" reference: %s\n", eastl::to_string(refResult).c_str());
  52. return false;
  53. }
  54. }
  55. // EA::UnitTest::Report(" Function \"%s\" passed.\n", testname);
  56. return true;
  57. }
  58. ///////////////////////////////////////////////////////////////////
  59. // reference implementations
  60. //
  61. EA_NO_UBSAN static uint32_t ref_RoundToUint32(float32_t v)
  62. {
  63. return (uint32_t)floorf(v + 0.5f);
  64. }
  65. static int32_t ref_FloorToInt32(float32_t v)
  66. {
  67. return (int32_t)floorf(v);
  68. }
  69. static int32_t ref_CeilToInt32(float32_t v)
  70. {
  71. return (int32_t)ceilf(v);
  72. }
  73. static int32_t ref_RoundToInt32(float32_t v)
  74. {
  75. return (int32_t)floorf(v + 0.5f);
  76. }
  77. static int32_t ref_TruncateToInt32(float32_t v)
  78. {
  79. return (int32_t)v;
  80. }
  81. EA_NO_UBSAN static uint8_t ref_UnitFloatToUint8(float fValue)
  82. {
  83. return (uint8_t)floorf((fValue * 255.0f) + 0.5f);
  84. }
  85. static uint8_t ref_ClampUnitFloatToUint8(float fValue)
  86. {
  87. if (fValue < 0.0f)
  88. fValue = 0.0f;
  89. if (fValue > 1.0f)
  90. fValue = 1.0f;
  91. return (uint8_t)floorf((fValue * 255.0f) + 0.5f);
  92. }
  93. ///////////////////////////////////////////////////////////////////
  94. // tests
  95. //
  96. #if defined(EA_COMPILER_MSVC) && defined(EA_PLATFORM_WINDOWS)
  97. static bool IsFPUModePP()
  98. {
  99. return (_controlfp(0, 0) & _MCW_PC) == _PC_24;
  100. }
  101. #else
  102. static bool IsFPUModePP()
  103. {
  104. return false;
  105. }
  106. #endif
  107. static int32_t BitReprOfFloat(float f)
  108. {
  109. union { float f; int32_t i; } typepun;
  110. typepun.f = f;
  111. return typepun.i;
  112. }
  113. static int TestMathHelpConversions(const char* /*testmode*/)
  114. {
  115. int nErrorCount(0);
  116. /////////////////////////////////////////////////////////////////////
  117. // Zero tests
  118. //
  119. // Zero must convert to zero, exactly.
  120. //
  121. static float zerosource[2] = { 0, -sqrtf(0) };
  122. // EA::UnitTest::Report("\nZero tests (%s):\n", testmode);
  123. nErrorCount += !TestArray("RoundToUint32", zerosource, RoundToUint32, ref_RoundToUint32);
  124. nErrorCount += !TestArray("RoundToInt32", zerosource, RoundToInt32, ref_RoundToInt32);
  125. nErrorCount += !TestArray("FloorToInt32", zerosource, FloorToInt32, ref_FloorToInt32);
  126. nErrorCount += !TestArray("CeilToInt32", zerosource, CeilToInt32, ref_CeilToInt32);
  127. nErrorCount += !TestArray("TruncateToInt32", zerosource, TruncateToInt32, ref_TruncateToInt32);
  128. nErrorCount += !TestArray("FastRoundToInt23", zerosource, FastRoundToInt23, ref_RoundToInt32);
  129. nErrorCount += !TestArray("UnitFloatToUint8", zerosource, UnitFloatToUint8, ref_UnitFloatToUint8);
  130. nErrorCount += !TestArray("ClampUnitFloatToUint8", zerosource, ClampUnitFloatToUint8, ref_ClampUnitFloatToUint8);
  131. /////////////////////////////////////////////////////////////////////
  132. // Square root ramp tests
  133. //
  134. // These are designed to catch basic errors. One nasty test case is
  135. // negative zero -- this is known to trip EAMath's IntRound().
  136. //
  137. static float source[3072];
  138. for(int i = 0; i < 256; ++i)
  139. {
  140. source[i*12+ 8] = source[i*12+4] = source[i*12+0] = sqrtf((float)i);
  141. source[i*12+ 9] = source[i*12+5] = source[i*12+1] = sqrtf((float)i + 0.5f);
  142. source[i*12+10] = source[i*12+6] = source[i*12+2] = -sqrtf((float)i);
  143. source[i*12+11] = source[i*12+7] = source[i*12+3] = -sqrtf((float)i + 0.5f);
  144. for(int j=0; j<4; ++j)
  145. {
  146. int32_t v1 = BitReprOfFloat(source[(i * 12) + 4 + j]);
  147. int32_t v2 = BitReprOfFloat(source[(i * 12) + 8 + j]);
  148. if (v1 & 0x7fffffff)
  149. --v1;
  150. if (v2 & 0x7fffffff)
  151. ++v2;
  152. }
  153. }
  154. // EA::UnitTest::Report("\nSquare root tests (%s):\n", testmode);
  155. nErrorCount += !TestArray("RoundToUint32", source, RoundToUint32, ref_RoundToUint32, true);
  156. nErrorCount += !TestArray("RoundToInt32", source, RoundToInt32, ref_RoundToInt32, true);
  157. nErrorCount += !TestArray("FloorToInt32", source, FloorToInt32, ref_FloorToInt32);
  158. nErrorCount += !TestArray("CeilToInt32", source, CeilToInt32, ref_CeilToInt32);
  159. nErrorCount += !TestArray("TruncateToInt32", source, TruncateToInt32, ref_TruncateToInt32);
  160. nErrorCount += !TestArray("FastRoundToInt23", source, FastRoundToInt23, ref_RoundToInt32, true);
  161. nErrorCount += !TestArray("UnitFloatToUint8", source, UnitFloatToUint8, ref_UnitFloatToUint8, true, true);
  162. nErrorCount += !TestArray("ClampUnitFloatToUint8", source, ClampUnitFloatToUint8, ref_ClampUnitFloatToUint8, true);
  163. /////////////////////////////////////////////////////////////////////
  164. // Epsilon tests
  165. //
  166. // These are designed to catch errors caused by accidentally rounding
  167. // off significant bits through adjustment arithmetic. RZMathHelp's
  168. // FastRoundToSint32() fails this test.
  169. //
  170. static float epsource[261*6];
  171. // EA::UnitTest::Report("\nEpsilon tests (%s):\n", testmode);
  172. for(int i=0; i< 261; ++i)
  173. {
  174. int j = i - 256;
  175. epsource[i*6+0] = ldexpf(1.0f, j);
  176. epsource[i*6+1] = -ldexpf(1.0f, j);
  177. epsource[i*6+2] = ldexpf(1.0f, j) + 1.0f;
  178. epsource[i*6+3] = -ldexpf(1.0f, j) + 1.0f;
  179. epsource[i*6+4] = ldexpf(1.0f, j) - 1.0f;
  180. epsource[i*6+5] = -ldexpf(1.0f, j) - 1.0f;
  181. }
  182. nErrorCount += !TestArray("RoundToUint32", epsource, RoundToUint32, ref_RoundToUint32, true);
  183. nErrorCount += !TestArray("RoundToInt32", epsource, RoundToInt32, ref_RoundToInt32, true);
  184. nErrorCount += !TestArray("FloorToInt32", epsource, FloorToInt32, ref_FloorToInt32);
  185. nErrorCount += !TestArray("CeilToInt32", epsource, CeilToInt32, ref_CeilToInt32);
  186. nErrorCount += !TestArray("TruncateToInt32", epsource, TruncateToInt32, ref_TruncateToInt32);
  187. nErrorCount += !TestArray("FastRoundToInt23", epsource, FastRoundToInt23, ref_RoundToInt32, true);
  188. nErrorCount += !TestArray("UnitFloatToUint8", epsource, UnitFloatToUint8, ref_UnitFloatToUint8, true, true);
  189. nErrorCount += !TestArray("ClampUnitFloatToUint8", epsource, ClampUnitFloatToUint8, ref_ClampUnitFloatToUint8, true);
  190. static float epsource2[261*4];
  191. // EA::UnitTest::Report("\nUnsigned epsilon tests (%s):\n", testmode);
  192. for(int i=0; i<261; ++i)
  193. {
  194. int j = i-256;
  195. epsource[i*4+0] = ldexpf(1.0f, j) + 2147483647.0f;
  196. epsource[i*4+1] = -ldexpf(1.0f, j) + 2147483647.0f;
  197. epsource[i*4+2] = ldexpf(1.0f, j) + 2147483648.0f;
  198. epsource[i*4+3] = -ldexpf(1.0f, j) + 2147483648.0f;
  199. }
  200. nErrorCount += !TestArray("RoundToUint32", epsource2, RoundToUint32, ref_RoundToUint32, true);
  201. /////////////////////////////////////////////////////////////////////
  202. // Range tests
  203. //
  204. // These check for internal overflows.
  205. //
  206. static float uint32rangesource[2] = { 0x80000000, 0xfffffe00 }; // We cannot use the correct bounds because single precision (24-bit) significands will round them out of range.
  207. // EA::UnitTest::Report("\nUint32 range tests (%s):\n", testmode);
  208. nErrorCount += !TestArray("RoundToUint32", uint32rangesource, RoundToUint32, ref_RoundToUint32, true);
  209. static float int32rangesource[2] = { -0x7fffff00, 0x7fffff00 }; // We cannot use the correct bounds because single precision (24-bit) significands will round them out of range.
  210. // EA::UnitTest::Report("\nInt32 range tests (%s):\n", testmode);
  211. nErrorCount += !TestArray("RoundToInt32", int32rangesource, RoundToInt32, ref_RoundToInt32, true);
  212. nErrorCount += !TestArray("FloorToInt32", int32rangesource, FloorToInt32, ref_FloorToInt32);
  213. nErrorCount += !TestArray("CeilToInt32", int32rangesource, CeilToInt32, ref_CeilToInt32);
  214. nErrorCount += !TestArray("TruncateToInt32", int32rangesource, TruncateToInt32, ref_TruncateToInt32);
  215. static float Int23rangesource[2] = { -0x003fffff, 0x003fffff };
  216. // EA::UnitTest::Report("\nInt23 range tests (%s):\n", testmode);
  217. nErrorCount += !TestArray("FastRoundToInt23", Int23rangesource, FastRoundToInt23, ref_RoundToInt32, true);
  218. static float Uint8rangesource[3] = { 0, 128, 255 };
  219. nErrorCount += !TestArray("UnitFloatToUint8", Uint8rangesource, UnitFloatToUint8, ref_UnitFloatToUint8, true);
  220. nErrorCount += !TestArray("ClampUnitFloatToUint8", Uint8rangesource, ClampUnitFloatToUint8, ref_ClampUnitFloatToUint8, true);
  221. return nErrorCount;
  222. }
  223. static int TestMathHelpDiagnosisFunctions(const char* /*testmode*/)
  224. {
  225. static const union
  226. {
  227. uint32_t i;
  228. float32_t f;
  229. } kFloat32Tests[]={
  230. { 0x00000000 } , // zero
  231. { 0x80000000 } , // negative zero
  232. { 0x3F800000 } , // +1
  233. { 0xBF800000 } , // -1
  234. { 0x00000001 } , // denormal
  235. { 0x80000001 } , // negative denormal
  236. { 0x7F800000 } , // +Inf
  237. { 0xFF800000 } , // -Inf
  238. { 0x7F800001 } , // -SNaN
  239. { 0xFF800001 } , // +SNaN
  240. { 0x7FFFFFFF } , // -QNaN
  241. { 0xFFFFFFFF } , // +QNaN
  242. { 0xFFC00000 } // QNaN indefinite
  243. };
  244. static const union
  245. {
  246. uint64_t i;
  247. float64_t f;
  248. } kFloat64Tests[]={
  249. { UINT64_C(0x0000000000000000) } , // zero
  250. { UINT64_C(0x8000000000000000) } , // negative zero
  251. { UINT64_C(0x3FF0000000000000) } , // +1
  252. { UINT64_C(0xBFF0000000000000) } , // -1
  253. { UINT64_C(0x0000000000000001) } , // denormal
  254. { UINT64_C(0x8000000000000001) } , // negative denormal
  255. { UINT64_C(0x7FF0000000000000) } , // +Inf
  256. { UINT64_C(0xFFF0000000000000) } , // -Inf
  257. { UINT64_C(0x7FF0000000000001) } , // -SNaN
  258. { UINT64_C(0xFFF0000000000001) } , // +SNaN
  259. { UINT64_C(0x7FFFFFFFFFFFFFFF) } , // -QNaN
  260. { UINT64_C(0xFFFFFFFFFFFFFFFF) } , // +QNaN
  261. { UINT64_C(0xFFF8000000000000) } // QNaN indefinite
  262. };
  263. static const struct {
  264. bool mDenormal:1;
  265. bool mInfinite:1;
  266. bool mNAN:1;
  267. bool mIndefinite:1;
  268. } kTestReference[]={
  269. { false, false, false, false }, // zero
  270. { false, false, false, false }, // negative zero
  271. { false, false, false, false }, // +1
  272. { false, false, false, false }, // -1
  273. { true, false, false, false }, // denormal
  274. { true, false, false, false }, // negative denormal
  275. { false, true, false, false }, // +Inf
  276. { false, true, false, false }, // -Inf
  277. { false, false, true, false }, // -QNaN
  278. { false, false, true, false }, // +QNaN
  279. { false, false, true, false }, // -SNaN
  280. { false, false, true, false }, // +SNaN
  281. { false, false, true, true }, // -QNaN (indefinite)
  282. };
  283. int nTestsFailed = 0;
  284. // EA::UnitTest::Report("\nClassification tests:\n");
  285. for(size_t i=0; i<sizeof kTestReference / sizeof kTestReference[0]; ++i)
  286. {
  287. const float32_t f32 = kFloat32Tests[i].f;
  288. const float64_t f64 = kFloat64Tests[i].f;
  289. const uint32_t i32 = kFloat32Tests[i].i;
  290. const uint64_t i64 = kFloat64Tests[i].i;
  291. const bool isDenormal = kTestReference[i].mDenormal;
  292. const bool isInfinite = kTestReference[i].mInfinite;
  293. const bool isNAN = kTestReference[i].mNAN;
  294. const bool isIndefinite = kTestReference[i].mIndefinite;
  295. const bool isNormal = !isDenormal && !isInfinite && !isNAN;
  296. if (IsNormal(f32) != isNormal) {
  297. ++nTestsFailed;
  298. EA::UnitTest::Report(" IsNormal(float32_t) FAIL: %g (%" PRIx32 ")\n", f32, i32);
  299. }
  300. if (IsNormal(f64) != isNormal) {
  301. ++nTestsFailed;
  302. EA::UnitTest::Report(" IsNormal(float64_t) FAIL: %g (%" PRIx64 ")\n", f64, i64);
  303. }
  304. if (IsDenormalized(f32) != isDenormal) {
  305. ++nTestsFailed;
  306. EA::UnitTest::Report(" IsDenormalized(float32_t) FAIL: %g (%" PRIx32 ")\n", f32, i32);
  307. }
  308. if (IsDenormalized(f64) != isDenormal) {
  309. ++nTestsFailed;
  310. EA::UnitTest::Report(" IsDenormalized(float64_t) FAIL: %g (%" PRIx64 ")\n", f64, i64);
  311. }
  312. if (IsIndefinite(f32) != isIndefinite) {
  313. ++nTestsFailed;
  314. EA::UnitTest::Report(" IsIndefinite(float32_t) FAIL: %g (%" PRIx32 ")\n", f32, i32);
  315. }
  316. if (IsIndefinite(f64) != isIndefinite) {
  317. ++nTestsFailed;
  318. EA::UnitTest::Report(" IsIndefinite(float64_t) FAIL: %g (%" PRIx64")\n", f64, i64);
  319. }
  320. if (IsInfinite(f32) != isInfinite) {
  321. ++nTestsFailed;
  322. EA::UnitTest::Report(" IsInfinite(float32_t) FAIL: %g (%" PRIx32 ")\n", f32, i32);
  323. }
  324. if (IsInfinite(f64) != isInfinite) {
  325. ++nTestsFailed;
  326. EA::UnitTest::Report(" IsInfinite(float64_t) FAIL: %g (%" PRIx64 ")\n", f64, i64);
  327. }
  328. if (IsNAN(f32) != isNAN) {
  329. ++nTestsFailed;
  330. EA::UnitTest::Report(" IsNAN(float32_t) FAIL: %g (%" PRIx32 ")\n", f32, i32);
  331. }
  332. if (IsNAN(f64) != isNAN) {
  333. ++nTestsFailed;
  334. EA::UnitTest::Report(" IsNAN(float64_t) FAIL: %g (%" PRIx64 ")\n", f64, i64);
  335. }
  336. }
  337. return nTestsFailed;
  338. }
  339. static int TestMath()
  340. {
  341. const char* testmode;
  342. #if defined(EAMATHHELP_MODE_SSE) && EAMATHHELP_MODE_SSE
  343. testmode = "SSE";
  344. IsFPUModePP(); // Call this only to prevent compiler warnings related to non-use.
  345. #elif defined(EAMATHHELP_MODE_X86ASM) && EAMATHHELP_MODE_X86ASM
  346. testmode = IsFPUModePP() ? "X86ASM-24" : "X86ASM-53";
  347. #else
  348. testmode = IsFPUModePP() ? "scalar-24" : "scalar-53";
  349. #endif
  350. return TestMathHelpConversions(testmode)
  351. + TestMathHelpDiagnosisFunctions(testmode);
  352. }
  353. int TestMathHelp()
  354. {
  355. int nErrorCount = 0;
  356. EA::UnitTest::Report("TestMathHelp\n");
  357. nErrorCount += TestMath();
  358. #if defined(EA_PLATFORM_WINDOWS) // To consider: Enable this for other platforms.
  359. // Verify that (HUGE_VAL == EA::StdC::kFloat64Infinity). We do this not because
  360. // it's required by the C standard, but because we have code in EAStdC that
  361. // assumes they are equal under VC++. This assumption, if broken, isn't a very
  362. // big deal and no user would likely notice it, but it's a fine detail.
  363. double hugeVal = HUGE_VAL;
  364. double infVal = EA::StdC::kFloat64Infinity;
  365. EATEST_VERIFY(memcmp(&hugeVal, &infVal, sizeof(double)) == 0);
  366. #endif
  367. // To do: Make this work under x64 / Win64.
  368. #if defined(EA_COMPILER_MSVC) && defined(EA_PLATFORM_WIN32) && !EAMATHHELP_MODE_SSE
  369. _controlfp(_PC_24, _MCW_PC);
  370. nErrorCount += TestMath();
  371. _controlfp(_PC_53, _MCW_PC);
  372. #endif
  373. return nErrorCount;
  374. }