TestMathHelp.cpp 16 KB

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