@@ -5132,4 +5132,73 @@ def test_large_array_casting(dtype, size):
51325132
51335133 # check roundtrip
51345134 roundtrip = quad_arr .astype (dtype )
5135- np .testing .assert_array_equal (arr , roundtrip )
5135+ np .testing .assert_array_equal (arr , roundtrip )
5136+
5137+
5138+ class TestBinaryOpsEdgeCases :
5139+ """Edge case tests for binary operations - SLEEF 3.8 had inconsistency where (0 + x) != (x + 0).
5140+
5141+ In SLEEF 3.8:
5142+ - (0 + 6.724206286224186953608055004397316e-4932) gave half the expected value
5143+ - (6.724206286224186953608055004397316e-4932 + 0) was correct
5144+
5145+ SLEEF 4.0 fixes this inconsistency.
5146+ """
5147+
5148+ # The exact values from the bug
5149+ SMALL_VALUE = "6.724206286224186953608055004397316e-4932"
5150+ ZERO = 0.0
5151+
5152+ @pytest .mark .parametrize ('op' , [
5153+ ("add" , "fadd" ),
5154+ ("subtract" , "fsub" ),
5155+ ("multiply" , "fmul" ),
5156+ ("divide" , "fdiv" ),
5157+ ("mod" , "fmod" ),
5158+ ("power" , "power" ),
5159+ ("hypot" , "hypot" ),
5160+ ("atan2" , "atan2" ),
5161+ ])
5162+ def test_binary_op_consistency (self , op ):
5163+ """Test that abs(x op y) == abs(y op x) - catches half-value bugs."""
5164+ mp .prec = 113
5165+ small = QuadPrecision (self .SMALL_VALUE )
5166+ zero = QuadPrecision (self .ZERO )
5167+
5168+ mp_small = mp .mpf (self .SMALL_VALUE )
5169+ mp_zero = mp .mpf (self .ZERO )
5170+
5171+ quad_op_str , mpmath_op_str = op
5172+ quad_op , mpmath_op = getattr (np , quad_op_str ), getattr (mp , mpmath_op_str )
5173+ quad_result = quad_op (zero , small )
5174+ mpmath_result = mpmath_op (mp_zero , mp_small )
5175+
5176+ # Use mp.nstr to get full precision (50 digits for quad precision)
5177+ mpmath_result = QuadPrecision (mp .nstr (mpmath_result , 50 ))
5178+
5179+ # Handle NaN cases
5180+ if np .isnan (mpmath_result ):
5181+ assert np .isnan (quad_result ), f"Expected NaN for { quad_op_str } , got { quad_result } "
5182+ return
5183+
5184+ # Handle infinity cases
5185+ if np .isinf (mpmath_result ):
5186+ assert np .isinf (quad_result ), f"Expected inf for { quad_op_str } , got { quad_result } "
5187+ assert np .sign (mpmath_result ) == np .sign (quad_result ), f"Infinity sign mismatch for { quad_op_str } "
5188+ return
5189+
5190+ # For finite non-zero results
5191+ np .testing .assert_allclose (quad_result , mpmath_result , rtol = 1e-32 , atol = 1e-34 ,
5192+ err_msg = f"Value mismatch for { quad_op_str } , expected { mpmath_result } , got { quad_result } " )
5193+
5194+
5195+ def test_add_regression_zero_plus_small (self ):
5196+ """The exact SLEEF 3.8 bug: 0 + small_value gave half the expected."""
5197+ x = QuadPrecision (self .SMALL_VALUE )
5198+ y = QuadPrecision (self .ZERO )
5199+
5200+ result_yx = y + x # 0 + x (this was buggy in SLEEF 3.8)
5201+ result_xy = x + y # x + 0 (this was correct in SLEEF 3.8)
5202+
5203+ assert result_yx == result_xy , f"0 + x = { result_yx } , but x + 0 = { result_xy } "
5204+ assert result_yx == x , f"0 + x = { result_yx } , expected { x } "
0 commit comments