@@ -2256,3 +2256,204 @@ def test_direction(self):
22562256 elif expected_dir == "less" :
22572257 assert result < q_x , f"nextafter({ x } , { y } ) should be < { x } , got { result } "
22582258
2259+ class TestSpacing :
2260+ """Test cases for np.spacing function with QuadPrecision dtype"""
2261+
2262+ @pytest .mark .parametrize ("x" , [
2263+ np .nan , - np .nan ,
2264+ np .inf , - np .inf ,
2265+ ])
2266+ def test_special_values_return_nan (self , x ):
2267+ """Test spacing with NaN and infinity inputs returns NaN"""
2268+ q_x = QuadPrecision (x )
2269+ result = np .spacing (q_x )
2270+
2271+ assert isinstance (result , QuadPrecision )
2272+ assert np .isnan (result ), f"spacing({ x } ) should be NaN, got { result } "
2273+
2274+ @pytest .mark .parametrize ("x" , [
2275+ 1.0 , - 1.0 ,
2276+ 10.0 , - 10.0 ,
2277+ 100.0 , - 100.0 ,
2278+ ])
2279+ def test_sign_preservation (self , x ):
2280+ """Test that spacing preserves the sign of the input"""
2281+ q_x = QuadPrecision (x )
2282+ result = np .spacing (q_x )
2283+
2284+ q_zero = QuadPrecision (0 )
2285+ # spacing should have the same sign as x
2286+ if x > 0 :
2287+ assert result > q_zero , f"spacing({ x } ) should be positive, got { result } "
2288+ else :
2289+ assert result < q_zero , f"spacing({ x } ) should be negative, got { result } "
2290+
2291+ # Compare with numpy behavior
2292+ np_result = np .spacing (np .float64 (x ))
2293+ assert np .signbit (result ) == np .signbit (np_result ), \
2294+ f"Sign mismatch for spacing({ x } ): quad signbit={ np .signbit (result )} , numpy signbit={ np .signbit (np_result )} "
2295+
2296+ @pytest .mark .parametrize ("x" , [
2297+ 0.0 ,
2298+ - 0.0 ,
2299+ ])
2300+ def test_zero (self , x ):
2301+ """Test spacing of zero returns smallest_subnormal"""
2302+ q_x = QuadPrecision (x )
2303+ result = np .spacing (q_x )
2304+
2305+ finfo = np .finfo (QuadPrecDType ())
2306+ q_zero = QuadPrecision (0 )
2307+
2308+ # spacing(±0.0) should return smallest_subnormal (positive)
2309+ assert result == finfo .smallest_subnormal , \
2310+ f"spacing({ x } ) should be smallest_subnormal, got { result } "
2311+ assert result > q_zero , f"spacing({ x } ) should be positive, got { result } "
2312+
2313+ @pytest .mark .parametrize ("x" , [
2314+ 1.0 ,
2315+ - 1.0 ,
2316+ ])
2317+ def test_one_and_negative_one (self , x ):
2318+ """Test spacing(±1.0) equals ±machine epsilon"""
2319+ q_x = QuadPrecision (x )
2320+ result = np .spacing (q_x )
2321+
2322+ finfo = np .finfo (QuadPrecDType ())
2323+ q_zero = QuadPrecision (0 )
2324+
2325+ # For binary floating point, spacing(±1.0) = ±eps
2326+ expected = finfo .eps if x > 0 else - finfo .eps
2327+ assert result == expected , \
2328+ f"spacing({ x } ) should equal { expected } , got { result } "
2329+
2330+ if x > 0 :
2331+ assert result > q_zero , "spacing(1.0) should be positive"
2332+ else :
2333+ assert result < q_zero , "spacing(-1.0) should be negative"
2334+
2335+ @pytest .mark .parametrize ("x" , [
2336+ 1.0 , - 1.0 ,
2337+ 2.0 , - 2.0 ,
2338+ 10.0 , - 10.0 ,
2339+ 100.0 , - 100.0 ,
2340+ 1e10 , - 1e10 ,
2341+ 1e-10 , - 1e-10 ,
2342+ 0.5 , - 0.5 ,
2343+ 0.25 , - 0.25 ,
2344+ ])
2345+ def test_spacing_is_non_zero (self , x ):
2346+ """Test that spacing always has positive magnitude"""
2347+ q_x = QuadPrecision (x )
2348+ result = np .spacing (q_x )
2349+
2350+ q_zero = QuadPrecision (0 )
2351+ # The absolute value should be positive
2352+ abs_result = np .abs (result )
2353+ assert abs_result > q_zero , f"|spacing({ x } )| should be positive, got { abs_result } "
2354+
2355+ def test_smallest_subnormal (self ):
2356+ """Test spacing at smallest subnormal value"""
2357+ finfo = np .finfo (QuadPrecDType ())
2358+ smallest = finfo .smallest_subnormal
2359+
2360+ result = np .spacing (smallest )
2361+
2362+ q_zero = QuadPrecision (0 )
2363+ # spacing(smallest_subnormal) should be smallest_subnormal itself
2364+ # (it's the minimum spacing in the subnormal range)
2365+ assert result == smallest , \
2366+ f"spacing(smallest_subnormal) should be smallest_subnormal, got { result } "
2367+ assert result > q_zero , "Result should be positive"
2368+
2369+ @pytest .mark .parametrize ("x" , [
2370+ 1.5 , - 1.5 ,
2371+ 3.7 , - 3.7 ,
2372+ 42.0 , - 42.0 ,
2373+ 1e8 , - 1e8 ,
2374+ ])
2375+ def test_finite_values (self , x ):
2376+ """Test spacing on various finite values"""
2377+ q_x = QuadPrecision (x )
2378+ result = np .spacing (q_x )
2379+
2380+ q_zero = QuadPrecision (0 )
2381+ # Result should be finite
2382+ assert np .isfinite (result ), \
2383+ f"spacing({ x } ) should be finite, got { result } "
2384+
2385+ # Result should be non-zero
2386+ assert result != q_zero , \
2387+ f"spacing({ x } ) should be non-zero, got { result } "
2388+
2389+ # Result should have same sign as input
2390+ assert np .signbit (result ) == np .signbit (q_x ), \
2391+ f"spacing({ x } ) should have same sign as { x } "
2392+
2393+ def test_array_spacing (self ):
2394+ """Test spacing on an array of QuadPrecision values"""
2395+ values = [1.0 , - 1.0 , 2.0 , - 2.0 , 0.0 , 10.0 , - 10.0 ]
2396+ q_array = np .array ([QuadPrecision (v ) for v in values ])
2397+
2398+ result = np .spacing (q_array )
2399+
2400+ q_zero = QuadPrecision (0 )
2401+ # Check each result
2402+ for i , val in enumerate (values ):
2403+ q_val = QuadPrecision (val )
2404+ if val != 0 :
2405+ assert np .signbit (result [i ]) == np .signbit (q_val ), \
2406+ f"Sign mismatch for spacing({ val } )"
2407+ else :
2408+ assert result [i ] > q_zero , \
2409+ f"spacing(0) should be positive, got { result [i ]} "
2410+
2411+ @pytest .mark .parametrize ("x" , [
2412+ 1e-100 , - 1e-100 ,
2413+ 1e-200 , - 1e-200 ,
2414+ ])
2415+ def test_subnormal_range (self , x ):
2416+ """Test spacing in subnormal range"""
2417+ q_x = QuadPrecision (x )
2418+ result = np .spacing (q_x )
2419+
2420+ finfo = np .finfo (QuadPrecDType ())
2421+
2422+ # In subnormal range, spacing should be smallest_subnormal
2423+ # or at least very small
2424+ assert np .abs (result ) >= finfo .smallest_subnormal , \
2425+ f"spacing({ x } ) should be >= smallest_subnormal"
2426+
2427+ q_zero = QuadPrecision (0 )
2428+ # Sign should match (but for very small subnormals, spacing might underflow to zero)
2429+ if result != q_zero :
2430+ assert np .signbit (result ) == np .signbit (q_x ), \
2431+ f"spacing({ x } ) should have same sign as { x } "
2432+
2433+ def test_smallest_normal_spacing (self ):
2434+ """Test spacing for smallest normal value and 2*smallest normal"""
2435+ finfo = np .finfo (QuadPrecDType ())
2436+ smallest_normal = finfo .smallest_normal
2437+
2438+ # Test spacing at smallest normal value
2439+ result1 = np .spacing (smallest_normal )
2440+
2441+ # Test spacing at 2 * smallest normal value
2442+ two_smallest_normal = 2 * smallest_normal
2443+ result2 = np .spacing (two_smallest_normal )
2444+
2445+ q_zero = QuadPrecision ("0" )
2446+
2447+ # spacing(smallest_normal) should be smallest_subnormal
2448+ # (the spacing at the boundary between subnormal and normal range)
2449+ expected1 = finfo .smallest_subnormal
2450+ assert result1 == expected1 , \
2451+ f"spacing(smallest_normal) should be { expected1 } , got { result1 } "
2452+ assert result1 > q_zero , "Result should be positive"
2453+
2454+ # The scaling relationship: spacing(2*x) = 2*spacing(x) for normal numbers
2455+ expected2 = 2 * finfo .smallest_subnormal
2456+ assert result2 == expected2 , \
2457+ f"spacing(2*smallest_normal) should be { expected2 } , got { result2 } "
2458+ assert result2 > q_zero , "Result should be positive"
2459+
0 commit comments