SDL: Added SDL_modf() and SDL_modff()

From 7f23d71b6afe92555fd535e875b05ef37f0aa6d3 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 29 Dec 2022 21:39:08 -0800
Subject: [PATCH] Added SDL_modf() and SDL_modff()

This function is useful for accumulating relative mouse motion if you want to only handle whole pixel movement.
e.g.
static float dx_frac, dy_frac;
float dx, dy;

/* Accumulate new motion with previous sub-pixel motion */
dx = event.motion.xrel + dx_frac;
dy = event.motion.yrel + dy_frac;

/* Split the integral and fractional motion, dx and dy will contain whole pixel deltas */
dx_frac = SDL_modff(dx, &dx);
dy_frac = SDL_modff(dy, &dy);
if (dx != 0.0f || dy != 0.0f) {
    ...
}
---
 CMakeLists.txt                                |  4 +-
 VisualC-GDK/SDL/SDL.vcxproj                   |  1 +
 VisualC-GDK/SDL/SDL.vcxproj.filters           |  3 +
 VisualC/SDL/SDL.vcxproj                       |  1 +
 VisualC/SDL/SDL.vcxproj.filters               |  3 +
 WhatsNew.txt                                  |  1 +
 include/SDL3/SDL_stdinc.h                     |  2 +
 include/build_config/SDL_build_config.h.cmake |  2 +
 .../build_config/SDL_build_config_android.h   |  1 +
 include/build_config/SDL_build_config_ios.h   |  1 +
 include/build_config/SDL_build_config_macos.h |  1 +
 include/build_config/SDL_build_config_winrt.h |  1 +
 src/dynapi/SDL_dynapi.sym                     |  2 +
 src/dynapi/SDL_dynapi_overrides.h             |  2 +
 src/dynapi/SDL_dynapi_procs.h                 |  2 +
 src/libm/math_libm.h                          | 11 +--
 src/libm/math_private.h                       |  1 +
 src/libm/s_modf.c                             | 68 +++++++++++++++++++
 src/stdlib/SDL_stdlib.c                       | 22 ++++++
 test/testautomation_math.c                    | 27 ++++++++
 20 files changed, 149 insertions(+), 7 deletions(-)
 create mode 100644 src/libm/s_modf.c

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 65385da8eb62..9a4452c73876 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -962,8 +962,8 @@ if(SDL_LIBC)
             _stricmp _strnicmp sscanf
             acos acosf asin asinf atan atanf atan2 atan2f ceil ceilf
             copysign copysignf cos cosf exp expf fabs fabsf floor floorf fmod fmodf
-            log logf log10 log10f lround lroundf pow powf round roundf scalbn scalbnf
-            sin sinf sqrt sqrtf tan tanf trunc truncf)
+            log logf log10 log10f lround lroundf modf modff pow powf round roundf
+            scalbn scalbnf sin sinf sqrt sqrtf tan tanf trunc truncf)
       string(TOUPPER ${_FN} _UPPER)
       set(HAVE_${_UPPER} 1)
     endforeach()
diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj
index 381213fc0782..d6df2314de51 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj
+++ b/VisualC-GDK/SDL/SDL.vcxproj
@@ -666,6 +666,7 @@
     <ClCompile Include="..\..\src\libm\s_cos.c" />
     <ClCompile Include="..\..\src\libm\s_fabs.c" />
     <ClCompile Include="..\..\src\libm\s_floor.c" />
+    <ClCompile Include="..\..\src\libm\s_modf.c" />
     <ClCompile Include="..\..\src\libm\s_scalbn.c" />
     <ClCompile Include="..\..\src\libm\s_sin.c" />
     <ClCompile Include="..\..\src\libm\s_tan.c" />
diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters
index 40c9fd6a77bb..4db42dd26ddd 100644
--- a/VisualC-GDK/SDL/SDL.vcxproj.filters
+++ b/VisualC-GDK/SDL/SDL.vcxproj.filters
@@ -976,6 +976,9 @@
     <ClCompile Include="..\..\src\libm\s_floor.c">
       <Filter>libm</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\libm\s_modf.c">
+      <Filter>libm</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\libm\s_scalbn.c">
       <Filter>libm</Filter>
     </ClCompile>
diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj
index 798033270116..756ea5934c4e 100644
--- a/VisualC/SDL/SDL.vcxproj
+++ b/VisualC/SDL/SDL.vcxproj
@@ -546,6 +546,7 @@
     <ClCompile Include="..\..\src\libm\s_cos.c" />
     <ClCompile Include="..\..\src\libm\s_fabs.c" />
     <ClCompile Include="..\..\src\libm\s_floor.c" />
+    <ClCompile Include="..\..\src\libm\s_modf.c" />
     <ClCompile Include="..\..\src\libm\s_scalbn.c" />
     <ClCompile Include="..\..\src\libm\s_sin.c" />
     <ClCompile Include="..\..\src\libm\s_tan.c" />
diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters
index eb48c1e2dc81..acb558a16627 100644
--- a/VisualC/SDL/SDL.vcxproj.filters
+++ b/VisualC/SDL/SDL.vcxproj.filters
@@ -964,6 +964,9 @@
     <ClCompile Include="..\..\src\libm\s_floor.c">
       <Filter>libm</Filter>
     </ClCompile>
+    <ClCompile Include="..\..\src\libm\s_modf.c">
+      <Filter>libm</Filter>
+    </ClCompile>
     <ClCompile Include="..\..\src\libm\s_scalbn.c">
       <Filter>libm</Filter>
     </ClCompile>
diff --git a/WhatsNew.txt b/WhatsNew.txt
index 10625d97affb..6733ad8d99a0 100644
--- a/WhatsNew.txt
+++ b/WhatsNew.txt
@@ -19,3 +19,4 @@ General:
 * Added SDL_GetTicksNS() to return the number of nanoseconds since the SDL library initialized
 * Added SDL_DelayNS() to specify a delay in nanoseconds, to the highest precision the system will support
 * The timestamp member of the SDL_Event structure is now in nanoseconds, filled in with the time the event was generated, or the time it was queued if that's not available
+* Added SDL_modf() and SDL_modff() to separate the whole and fractional portions of a floating point number
diff --git a/include/SDL3/SDL_stdinc.h b/include/SDL3/SDL_stdinc.h
index 75df76aae00d..75fc0b5eb437 100644
--- a/include/SDL3/SDL_stdinc.h
+++ b/include/SDL3/SDL_stdinc.h
@@ -580,6 +580,8 @@ extern DECLSPEC double SDLCALL SDL_log(double x);
 extern DECLSPEC float SDLCALL SDL_logf(float x);
 extern DECLSPEC double SDLCALL SDL_log10(double x);
 extern DECLSPEC float SDLCALL SDL_log10f(float x);
+extern DECLSPEC double SDLCALL SDL_modf(double x, double *y);
+extern DECLSPEC float SDLCALL SDL_modff(float x, float *y);
 extern DECLSPEC double SDLCALL SDL_pow(double x, double y);
 extern DECLSPEC float SDLCALL SDL_powf(float x, float y);
 extern DECLSPEC double SDLCALL SDL_round(double x);
diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake
index 96eb44e51b9f..87e9c65d7c8b 100644
--- a/include/build_config/SDL_build_config.h.cmake
+++ b/include/build_config/SDL_build_config.h.cmake
@@ -171,6 +171,8 @@
 #cmakedefine HAVE_LOG10F 1
 #cmakedefine HAVE_LROUND 1
 #cmakedefine HAVE_LROUNDF 1
+#cmakedefine HAVE_MODF 1
+#cmakedefine HAVE_MODFF 1
 #cmakedefine HAVE_POW 1
 #cmakedefine HAVE_POWF 1
 #cmakedefine HAVE_ROUND 1
diff --git a/include/build_config/SDL_build_config_android.h b/include/build_config/SDL_build_config_android.h
index dd5ea3efda33..4572f9aa9587 100644
--- a/include/build_config/SDL_build_config_android.h
+++ b/include/build_config/SDL_build_config_android.h
@@ -120,6 +120,7 @@
 #define HAVE_LOG10F 1
 #define HAVE_LROUND 1
 #define HAVE_LROUNDF 1
+#define HAVE_MODF   1
 #define HAVE_POW    1
 #define HAVE_POWF   1
 #define HAVE_ROUND  1
diff --git a/include/build_config/SDL_build_config_ios.h b/include/build_config/SDL_build_config_ios.h
index 0bb63efc3b6d..d4f14c28fb7e 100644
--- a/include/build_config/SDL_build_config_ios.h
+++ b/include/build_config/SDL_build_config_ios.h
@@ -114,6 +114,7 @@
 #define HAVE_LOG10F 1
 #define HAVE_LROUND 1
 #define HAVE_LROUNDF 1
+#define HAVE_MODF   1
 #define HAVE_POW    1
 #define HAVE_POWF   1
 #define HAVE_ROUND  1
diff --git a/include/build_config/SDL_build_config_macos.h b/include/build_config/SDL_build_config_macos.h
index 576c4dc5b80d..f2878db25491 100644
--- a/include/build_config/SDL_build_config_macos.h
+++ b/include/build_config/SDL_build_config_macos.h
@@ -116,6 +116,7 @@
 #define HAVE_LOG10F 1
 #define HAVE_LROUND 1
 #define HAVE_LROUNDF 1
+#define HAVE_MODF   1
 #define HAVE_POW    1
 #define HAVE_POWF   1
 #define HAVE_ROUND  1
diff --git a/include/build_config/SDL_build_config_winrt.h b/include/build_config/SDL_build_config_winrt.h
index 4256cea42dd8..01bff80e1416 100644
--- a/include/build_config/SDL_build_config_winrt.h
+++ b/include/build_config/SDL_build_config_winrt.h
@@ -134,6 +134,7 @@
 #define HAVE_LOG10F 1
 #define HAVE_LROUND 1
 #define HAVE_LROUNDF 1
+#define HAVE_MODF   1
 #define HAVE_POW    1
 #define HAVE_POWF   1
 #define HAVE_ROUND 1
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 2876dea5d32c..540c00ebf001 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -859,6 +859,8 @@ SDL3_0.0.0 {
     SDL_wcsncasecmp;
     SDL_wcsncmp;
     SDL_wcsstr;
+    SDL_modf;
+    SDL_modff;
     # extra symbols go here (don't modify this line)
   local: *;
 };
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 1228b3ba7390..2bc07ba4fff5 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -887,3 +887,5 @@
 #define SDL_wcsstr SDL_wcsstr_REAL
 
 /* New API symbols are added at the end */
+#define SDL_modf SDL_modf_REAL
+#define SDL_modff SDL_modff_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index f4cf4b731170..62bf7dd3992f 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -932,3 +932,5 @@ SDL_DYNAPI_PROC(int,SDL_wcsncmp,(const wchar_t *a, const wchar_t *b, size_t c),(
 SDL_DYNAPI_PROC(wchar_t*,SDL_wcsstr,(const wchar_t *a, const wchar_t *b),(a,b),return)
 
 /* New API symbols are added at the end */
+SDL_DYNAPI_PROC(double,SDL_modf,(double a, double *b),(a,b),return)
+SDL_DYNAPI_PROC(float,SDL_modff,(float a, float *b),(a,b),return)
diff --git a/src/libm/math_libm.h b/src/libm/math_libm.h
index f88c73bf30b7..5ed5f21a3220 100644
--- a/src/libm/math_libm.h
+++ b/src/libm/math_libm.h
@@ -27,16 +27,17 @@
 /* Math routines from uClibc: http://www.uclibc.org */
 
 double SDL_uclibc_atan(double x);
-double SDL_uclibc_atan2(double y, double x);    
-double SDL_uclibc_copysign(double x, double y);       
-double SDL_uclibc_cos(double x);         
+double SDL_uclibc_atan2(double y, double x);
+double SDL_uclibc_copysign(double x, double y);
+double SDL_uclibc_cos(double x);
 double SDL_uclibc_exp(double x);
-double SDL_uclibc_fabs(double x);        
+double SDL_uclibc_fabs(double x);
 double SDL_uclibc_floor(double x);
 double SDL_uclibc_fmod(double x, double y);
 double SDL_uclibc_log(double x);
 double SDL_uclibc_log10(double x);
-double SDL_uclibc_pow(double x, double y);    
+double SDL_uclibc_modf(double x, double *y);
+double SDL_uclibc_pow(double x, double y);
 double SDL_uclibc_scalbn(double x, int n);
 double SDL_uclibc_sin(double x);
 double SDL_uclibc_sqrt(double x);
diff --git a/src/libm/math_private.h b/src/libm/math_private.h
index 8a468bf474cf..ba5d83468369 100644
--- a/src/libm/math_private.h
+++ b/src/libm/math_private.h
@@ -40,6 +40,7 @@ typedef unsigned int u_int32_t;
 #define __ieee754_fmod  SDL_uclibc_fmod
 #define __ieee754_log   SDL_uclibc_log
 #define __ieee754_log10 SDL_uclibc_log10
+#define modf            SDL_uclibc_modf
 #define __ieee754_pow   SDL_uclibc_pow
 #define scalbln         SDL_uclibc_scalbln
 #define scalbn          SDL_uclibc_scalbn
diff --git a/src/libm/s_modf.c b/src/libm/s_modf.c
new file mode 100644
index 000000000000..55f83ba8deda
--- /dev/null
+++ b/src/libm/s_modf.c
@@ -0,0 +1,68 @@
+#include "SDL_internal.h"
+/*
+ * ====================================================
+ * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved.
+ *
+ * Developed at SunPro, a Sun Microsystems, Inc. business.
+ * Permission to use, copy, modify, and distribute this
+ * software is freely granted, provided that this notice
+ * is preserved.
+ * ====================================================
+ */
+
+/*
+ * modf(double x, double *iptr)
+ * return fraction part of x, and return x's integral part in *iptr.
+ * Method:
+ *	Bit twiddling.
+ *
+ * Exception:
+ *	No exception.
+ */
+
+#include "math_libm.h"
+#include "math_private.h"
+
+static const double one = 1.0;
+
+double modf(double x, double *iptr)
+{
+	int32_t i0,i1,_j0;
+	u_int32_t i;
+	EXTRACT_WORDS(i0,i1,x);
+	_j0 = ((i0>>20)&0x7ff)-0x3ff;	/* exponent of x */
+	if(_j0<20) {			/* integer part in high x */
+	    if(_j0<0) {			/* |x|<1 */
+	        INSERT_WORDS(*iptr,i0&0x80000000,0);	/* *iptr = +-0 */
+		return x;
+	    } else {
+		i = (0x000fffff)>>_j0;
+		if(((i0&i)|i1)==0) {		/* x is integral */
+		    *iptr = x;
+		    INSERT_WORDS(x,i0&0x80000000,0);	/* return +-0 */
+		    return x;
+		} else {
+		    INSERT_WORDS(*iptr,i0&(~i),0);
+		    return x - *iptr;
+		}
+	    }
+	} else if (_j0>51) {		/* no fraction part */
+	    *iptr = x*one;
+	    /* We must handle NaNs separately.  */
+	    if (_j0 == 0x400 && ((i0 & 0xfffff) | i1))
+	      return x*one;
+	    INSERT_WORDS(x,i0&0x80000000,0);	/* return +-0 */
+	    return x;
+	} else {			/* fraction part in low x */
+	    i = ((u_int32_t)(0xffffffff))>>(_j0-20);
+	    if((i1&i)==0) { 		/* x is integral */
+		*iptr = x;
+		INSERT_WORDS(x,i0&0x80000000,0);	/* return +-0 */
+		return x;
+	    } else {
+	        INSERT_WORDS(*iptr,i0,i1&(~i));
+		return x - *iptr;
+	    }
+	}
+}
+libm_hidden_def(modf)
diff --git a/src/stdlib/SDL_stdlib.c b/src/stdlib/SDL_stdlib.c
index 7663423fa410..8c567cf5bc18 100644
--- a/src/stdlib/SDL_stdlib.c
+++ b/src/stdlib/SDL_stdlib.c
@@ -322,6 +322,28 @@ float SDL_log10f(float x)
 #endif
 }
 
+double
+SDL_modf(double x, double *y)
+{
+#if defined(HAVE_MODF)
+    return modf(x, y);
+#else
+    return SDL_uclibc_modf(x, y);
+#endif
+}
+
+float SDL_modff(float x, float *y)
+{
+#if defined(HAVE_MODFF)
+    return modff(x, y);
+#else
+    double double_result, double_y;
+    double_result = SDL_modf((double)x, &double_y);
+    *y = (float)double_y;
+    return (float)double_result;
+#endif
+}
+
 double
 SDL_pow(double x, double y)
 {
diff --git a/test/testautomation_math.c b/test/testautomation_math.c
index 7103854e011a..4bdc2f7e93d7 100644
--- a/test/testautomation_math.c
+++ b/test/testautomation_math.c
@@ -1272,6 +1272,24 @@ log10_regularCases(void *args)
     return helper_dtod_inexact("Log10", SDL_log10, regular_cases, SDL_arraysize(regular_cases));
 }
 
+/* SDL_modf tests functions */
+
+static int
+modf_baseCases(void *args)
+{
+    double fractional, integral;
+
+    fractional = SDL_modf(1.25, &integral);
+    SDLTest_AssertCheck(integral == 1.0,
+                        "modf(%f), expected integral %f, got %f",
+                        1.25, 1.0, integral);
+    SDLTest_AssertCheck(fractional == 0.25,
+                        "modf(%f), expected fractional %f, got %f",
+                        1.25, 0.25, fractional);
+
+    return TEST_COMPLETED;
+}
+
 /* SDL_pow tests functions */
 
 /* Tests with positive and negative infinities as exponents */
@@ -3004,6 +3022,13 @@ static const SDLTest_TestCaseReference log10TestRegular = {
     "Checks a set of regular values", TEST_ENABLED
 };
 
+/* SDL_modf test cases */
+
+static const SDLTest_TestCaseReference modfTestBase = {
+    (SDLTest_TestCaseFp)modf_baseCases, "modf_baseCases",
+    "Checks the base cases", TEST_ENABLED
+};
+
 /* SDL_pow test cases */
 
 static const SDLTest_TestCaseReference powTestExpInf1 = {
@@ -3315,6 +3340,8 @@ static const SDLTest_TestCaseReference *mathTests[] = {
     &log10TestLimit, &log10TestNan,
     &log10TestBase, &log10TestRegular,
 
+    &modfTestBase,
+
     &powTestExpInf1, &powTestExpInf2, &powTestExpInf3,
     &powTestBaseInf1, &powTestBaseInf2,
     &powTestNan1, &powTestNan2, &powTestNan3, &powTestNan4,