@@ -2065,3 +2065,194 @@ def test_radians(op, degrees, expected_radians):
20652065 else:
20662066 np.testing.assert_allclose(float(result), expected_radians, rtol=1e-13)
20672067
2068+
2069+ class TestNextAfter:
2070+ """Test cases for np.nextafter function with QuadPrecision dtype"""
2071+
2072+ @pytest.mark.parametrize("x1,x2", [
2073+ # NaN tests
2074+ (np.nan, 1.0),
2075+ (1.0, np.nan),
2076+ (np.nan, np.nan),
2077+ ])
2078+ def test_nan(self, x1, x2):
2079+ """Test nextafter with NaN inputs returns NaN"""
2080+ q_x1 = QuadPrecision(x1)
2081+ q_x2 = QuadPrecision(x2)
2082+
2083+ result = np.nextafter(q_x1, q_x2)
2084+
2085+ assert isinstance(result, QuadPrecision)
2086+ assert np.isnan(float(result))
2087+
2088+ def test_precision(self):
2089+ """Test that nextafter provides the exact next representable value"""
2090+ # Start with 1.0 and move towards 2.0
2091+ x1 = QuadPrecision(1.0)
2092+ x2 = QuadPrecision(2.0)
2093+
2094+ result = np.nextafter(x1, x2)
2095+
2096+ # Get machine epsilon from finfo
2097+ finfo = np.finfo(QuadPrecDType())
2098+ expected = x1 + finfo.eps
2099+
2100+ # result should be exactly 1.0 + eps
2101+ assert result == expected
2102+
2103+ # Moving the other direction should give us back 1.0
2104+ result_back = np.nextafter(result, x1)
2105+ assert result_back == x1
2106+
2107+ def test_smallest_subnormal(self):
2108+ """Test that nextafter(0.0, 1.0) returns the smallest positive subnormal (TRUE_MIN)"""
2109+ zero = QuadPrecision(0.0)
2110+ one = QuadPrecision(1.0)
2111+
2112+ result = np.nextafter(zero, one) # smallest_subnormal
2113+ finfo = np.finfo(QuadPrecDType())
2114+
2115+ assert result == finfo.smallest_subnormal, \
2116+ f"nextafter(0.0, 1.0) should equal smallest_subnormal, got {result} vs {finfo.smallest_subnormal}"
2117+
2118+ # Verify it's positive and very small
2119+ assert result > zero, "nextafter(0.0, 1.0) should be positive"
2120+
2121+ # Moving back towards zero should give us zero
2122+ result_back = np.nextafter(result, zero)
2123+ assert result_back == zero, f"nextafter(smallest_subnormal, 0.0) should be 0.0, got {result_back}"
2124+
2125+ def test_negative_zero(self):
2126+ """Test nextafter with negative zero"""
2127+ neg_zero = QuadPrecision(-0.0)
2128+ pos_zero = QuadPrecision(0.0)
2129+ one = QuadPrecision(1.0)
2130+ neg_one = QuadPrecision(-1.0)
2131+
2132+ finfo = np.finfo(QuadPrecDType())
2133+
2134+ # nextafter(-0.0, 1.0) should return smallest positive subnormal
2135+ result = np.nextafter(neg_zero, one)
2136+ assert result == finfo.smallest_subnormal, \
2137+ f"nextafter(-0.0, 1.0) should be smallest_subnormal, got {result}"
2138+ assert result > pos_zero, "Result should be positive"
2139+
2140+ # nextafter(+0.0, -1.0) should return smallest negative subnormal
2141+ result_neg = np.nextafter(pos_zero, neg_one)
2142+ expected_neg_subnormal = -finfo.smallest_subnormal
2143+ assert result_neg == expected_neg_subnormal, \
2144+ f"nextafter(+0.0, -1.0) should be -smallest_subnormal, got {result_neg}"
2145+ assert result_neg < pos_zero, "Result should be negative"
2146+
2147+ def test_infinity_cases(self):
2148+ """Test nextafter with infinity edge cases"""
2149+ pos_inf = QuadPrecision(np.inf)
2150+ neg_inf = QuadPrecision(-np.inf)
2151+ one = QuadPrecision(1.0)
2152+ neg_one = QuadPrecision(-1.0)
2153+ zero = QuadPrecision(0.0)
2154+
2155+ finfo = np.finfo(QuadPrecDType())
2156+
2157+ # nextafter(+inf, finite) should return max finite value
2158+ result = np.nextafter(pos_inf, zero)
2159+ assert not np.isinf(result), "nextafter(+inf, 0) should be finite"
2160+ assert result < pos_inf, "Result should be less than +inf"
2161+ assert result == finfo.max, f"nextafter(+inf, 0) should be max, got {result} vs {finfo.max}"
2162+
2163+ # nextafter(-inf, finite) should return -max (most negative finite)
2164+ result_neg = np.nextafter(neg_inf, zero)
2165+ assert not np.isinf(result_neg), "nextafter(-inf, 0) should be finite"
2166+ assert result_neg > neg_inf, "Result should be greater than -inf"
2167+ assert result_neg == -finfo.max, f"nextafter(-inf, 0) should be -max, got {result_neg}"
2168+
2169+ # Verify symmetry: nextafter(result, +inf) should give us +inf back
2170+ back_to_inf = np.nextafter(result, pos_inf)
2171+ assert back_to_inf == pos_inf, "nextafter(max_finite, +inf) should be +inf"
2172+
2173+ # nextafter(+inf, +inf) should return +inf
2174+ result_inf = np.nextafter(pos_inf, pos_inf)
2175+ assert result_inf == pos_inf, "nextafter(+inf, +inf) should be +inf"
2176+
2177+ # nextafter(-inf, -inf) should return -inf
2178+ result_neg_inf = np.nextafter(neg_inf, neg_inf)
2179+ assert result_neg_inf == neg_inf, "nextafter(-inf, -inf) should be -inf"
2180+
2181+ def test_max_to_infinity(self):
2182+ """Test nextafter from max finite value to infinity"""
2183+ finfo = np.finfo(QuadPrecDType())
2184+ max_val = finfo.max
2185+ pos_inf = QuadPrecision(np.inf)
2186+ neg_inf = QuadPrecision(-np.inf)
2187+
2188+ # nextafter(max_finite, +inf) should return +inf
2189+ result = np.nextafter(max_val, pos_inf)
2190+ assert np.isinf(result), f"nextafter(max, +inf) should be inf, got {result}"
2191+ assert result > max_val, "Result should be greater than max"
2192+ assert result == pos_inf, "Result should be +inf"
2193+
2194+ # nextafter(-max_finite, -inf) should return -inf
2195+ neg_max_val = -max_val
2196+ result_neg = np.nextafter(neg_max_val, neg_inf)
2197+ assert np.isinf(result_neg), f"nextafter(-max, -inf) should be -inf, got {result_neg}"
2198+ assert result_neg < neg_max_val, "Result should be less than -max"
2199+ assert result_neg == neg_inf, "Result should be -inf"
2200+
2201+ def test_near_max(self):
2202+ """Test nextafter near maximum finite value"""
2203+ finfo = np.finfo(QuadPrecDType())
2204+ max_val = finfo.max
2205+ zero = QuadPrecision(0.0)
2206+ pos_inf = QuadPrecision(np.inf)
2207+
2208+ # nextafter(max, 0) should return a value less than max
2209+ result = np.nextafter(max_val, zero)
2210+ assert result < max_val, "nextafter(max, 0) should be less than max"
2211+ assert not np.isinf(result), "Result should be finite"
2212+
2213+ # The difference should be one ULP at that scale
2214+ # Moving back should give us max again
2215+ result_back = np.nextafter(result, pos_inf)
2216+ assert result_back == max_val, f"Moving back should return max, got {result_back}"
2217+
2218+ def test_symmetry(self):
2219+ """Test symmetry properties of nextafter"""
2220+ values = [0.0, 1.0, -1.0, 1e10, -1e10, 1e-10, -1e-10]
2221+
2222+ for val in values:
2223+ q_val = QuadPrecision(val)
2224+
2225+ # nextafter(x, +direction) then nextafter(result, x) should return x
2226+ if not np.isinf(val):
2227+ result_up = np.nextafter(q_val, QuadPrecision(np.inf))
2228+ result_back = np.nextafter(result_up, q_val)
2229+ assert result_back == q_val, \
2230+ f"Symmetry failed for {val}: nextafter then back should return original"
2231+
2232+ # Same for down direction
2233+ result_down = np.nextafter(q_val, QuadPrecision(-np.inf))
2234+ result_back_down = np.nextafter(result_down, q_val)
2235+ assert result_back_down == q_val, \
2236+ f"Symmetry failed for {val}: nextafter down then back should return original"
2237+
2238+ def test_direction(self):
2239+ """Test that nextafter moves in the correct direction"""
2240+ test_cases = [
2241+ (1.0, 2.0, "greater"), # towards larger
2242+ (2.0, 1.0, "less"), # towards smaller
2243+ (-1.0, -2.0, "less"), # towards more negative
2244+ (-2.0, -1.0, "greater"), # towards less negative
2245+ (1.0, np.inf, "greater"), # towards +inf
2246+ (1.0, -np.inf, "less"), # towards -inf
2247+ ]
2248+
2249+ for x, y, expected_dir in test_cases:
2250+ q_x = QuadPrecision(x)
2251+ q_y = QuadPrecision(y)
2252+ result = np.nextafter(q_x, q_y)
2253+
2254+ if expected_dir == "greater":
2255+ assert result > q_x, f"nextafter({x}, {y}) should be > {x}, got {result}"
2256+ elif expected_dir == "less":
2257+ assert result < q_x, f"nextafter({x}, {y}) should be < {x}, got {result}"
2258+
0 commit comments