@@ -837,6 +837,135 @@ def test_floor_divide_special_properties():
837837 np .testing .assert_allclose (float (result_floor_divide ), float (result_floor_true_divide ), rtol = 1e-30 )
838838
839839
840+ @pytest .mark .parametrize ("x_val,y_val" , [
841+ (x , y ) for x in [- 1e10 , - 100.0 , - 7.0 , - 1.0 , - 0.5 , - 0.0 , 0.0 , 0.5 , 1.0 , 7.0 , 100.0 , 1e10 ,
842+ float ('inf' ), float ('-inf' ), float ('nan' ),
843+ - 6.0 , 6.0 , - 0.1 , 0.1 , - 3.14159 , 3.14159 ]
844+ for y in [- 1e10 , - 100.0 , - 3.0 , - 1.0 , - 0.5 , - 0.0 , 0.0 , 0.5 , 1.0 , 3.0 , 100.0 , 1e10 ,
845+ float ('inf' ), float ('-inf' ), float ('nan' ),
846+ - 2.0 , 2.0 , - 0.25 , 0.25 , - 1.5 , 1.5 ]
847+ ])
848+ def test_fmod (x_val , y_val ):
849+ """Test fmod ufunc with comprehensive edge cases"""
850+ x_quad = QuadPrecision (str (x_val ))
851+ y_quad = QuadPrecision (str (y_val ))
852+
853+ # Compute using QuadPrecision
854+ result_quad = np .fmod (x_quad , y_quad )
855+
856+ # Compute using float64 for comparison
857+ result_float64 = np .fmod (np .float64 (x_val ), np .float64 (y_val ))
858+
859+ # Compare results
860+ if np .isnan (result_float64 ):
861+ assert np .isnan (float (result_quad )), f"Expected NaN for fmod({ x_val } , { y_val } )"
862+ elif np .isinf (result_float64 ):
863+ assert np .isinf (float (result_quad )), f"Expected inf for fmod({ x_val } , { y_val } )"
864+ assert np .sign (float (result_quad )) == np .sign (result_float64 ), f"Sign mismatch for fmod({ x_val } , { y_val } )"
865+ else :
866+ # For finite results, check relative tolerance
867+ atol = max (1e-10 , abs (result_float64 ) * 1e-9 ) if abs (result_float64 ) > 1e6 else 1e-10
868+ np .testing .assert_allclose (
869+ float (result_quad ), result_float64 , rtol = 1e-12 , atol = atol ,
870+ err_msg = f"Mismatch for fmod({ x_val } , { y_val } )"
871+ )
872+
873+ # Critical: Check sign preservation for zero results
874+ if result_float64 == 0.0 :
875+ assert np .signbit (result_quad ) == np .signbit (result_float64 ), \
876+ f"Sign mismatch for zero result: fmod({ x_val } , { y_val } ), " \
877+ f"expected signbit={ np .signbit (result_float64 )} , got signbit={ np .signbit (result_quad )} "
878+
879+
880+ def test_fmod_special_properties ():
881+ """Test special mathematical properties of fmod"""
882+ # fmod(x, 1) gives fractional part of x (with sign preserved)
883+ x = QuadPrecision ("42.7" )
884+ result = np .fmod (x , QuadPrecision ("1.0" ))
885+ np .testing .assert_allclose (float (result ), 0.7 , rtol = 1e-15 , atol = 1e-15 )
886+
887+ # fmod(0, non-zero) = 0 with correct sign
888+ result = np .fmod (QuadPrecision ("0.0" ), QuadPrecision ("5.0" ))
889+ assert float (result ) == 0.0 and not np .signbit (result )
890+
891+ result = np .fmod (QuadPrecision ("-0.0" ), QuadPrecision ("5.0" ))
892+ assert float (result ) == 0.0 and np .signbit (result )
893+
894+ # fmod by 0 gives NaN
895+ result = np .fmod (QuadPrecision ("1.0" ), QuadPrecision ("0.0" ))
896+ assert np .isnan (float (result ))
897+
898+ result = np .fmod (QuadPrecision ("-1.0" ), QuadPrecision ("0.0" ))
899+ assert np .isnan (float (result ))
900+
901+ # 0 fmod 0 = NaN
902+ result = np .fmod (QuadPrecision ("0.0" ), QuadPrecision ("0.0" ))
903+ assert np .isnan (float (result ))
904+
905+ # inf fmod x = NaN
906+ result = np .fmod (QuadPrecision ("inf" ), QuadPrecision ("100.0" ))
907+ assert np .isnan (float (result ))
908+
909+ result = np .fmod (QuadPrecision ("-inf" ), QuadPrecision ("100.0" ))
910+ assert np .isnan (float (result ))
911+
912+ # x fmod inf = x (for finite x)
913+ result = np .fmod (QuadPrecision ("100.0" ), QuadPrecision ("inf" ))
914+ np .testing .assert_allclose (float (result ), 100.0 , rtol = 1e-30 )
915+
916+ result = np .fmod (QuadPrecision ("-100.0" ), QuadPrecision ("inf" ))
917+ np .testing .assert_allclose (float (result ), - 100.0 , rtol = 1e-30 )
918+
919+ # inf fmod inf = NaN
920+ result = np .fmod (QuadPrecision ("inf" ), QuadPrecision ("inf" ))
921+ assert np .isnan (float (result ))
922+
923+ # fmod uses truncated division (rounds toward zero)
924+ # Result has same sign as dividend (first argument)
925+ result = np .fmod (QuadPrecision ("7.0" ), QuadPrecision ("3.0" ))
926+ assert float (result ) == 1.0 # 7 - trunc(7/3)*3 = 7 - 2*3 = 1
927+
928+ result = np .fmod (QuadPrecision ("-7.0" ), QuadPrecision ("3.0" ))
929+ assert float (result ) == - 1.0 # -7 - trunc(-7/3)*3 = -7 - (-2)*3 = -1
930+
931+ result = np .fmod (QuadPrecision ("7.0" ), QuadPrecision ("-3.0" ))
932+ assert float (result ) == 1.0 # 7 - trunc(7/-3)*(-3) = 7 - (-2)*(-3) = 1
933+
934+ result = np .fmod (QuadPrecision ("-7.0" ), QuadPrecision ("-3.0" ))
935+ assert float (result ) == - 1.0 # -7 - trunc(-7/-3)*(-3) = -7 - 2*(-3) = -1
936+
937+ # Sign preservation when result is exactly zero
938+ result = np .fmod (QuadPrecision ("6.0" ), QuadPrecision ("3.0" ))
939+ assert float (result ) == 0.0 and not np .signbit (result )
940+
941+ result = np .fmod (QuadPrecision ("-6.0" ), QuadPrecision ("3.0" ))
942+ assert float (result ) == 0.0 and np .signbit (result )
943+
944+ result = np .fmod (QuadPrecision ("6.0" ), QuadPrecision ("-3.0" ))
945+ assert float (result ) == 0.0 and not np .signbit (result )
946+
947+ result = np .fmod (QuadPrecision ("-6.0" ), QuadPrecision ("-3.0" ))
948+ assert float (result ) == 0.0 and np .signbit (result )
949+
950+ # Difference from mod/remainder (which uses floor division)
951+ # fmod result has sign of dividend, mod result has sign of divisor
952+ x = QuadPrecision ("-7.0" )
953+ y = QuadPrecision ("3.0" )
954+ fmod_result = np .fmod (x , y )
955+ mod_result = np .remainder (x , y )
956+
957+ assert float (fmod_result ) == - 1.0 # sign of dividend (negative)
958+ assert float (mod_result ) == 2.0 # sign of divisor (positive)
959+
960+ # Relationship: x = trunc(x/y) * y + fmod(x, y)
961+ x = QuadPrecision ("10.5" )
962+ y = QuadPrecision ("3.2" )
963+ quotient = np .trunc (np .true_divide (x , y ))
964+ remainder = np .fmod (x , y )
965+ reconstructed = np .add (np .multiply (quotient , y ), remainder )
966+ np .testing .assert_allclose (float (reconstructed ), float (x ), rtol = 1e-30 )
967+
968+
840969def test_inf ():
841970 assert QuadPrecision ("inf" ) > QuadPrecision ("1e1000" )
842971 assert np .signbit (QuadPrecision ("inf" )) == 0
0 commit comments