@@ -55,6 +55,27 @@ def test_create_scalar_from_large_int(int_val):
5555 assert float (result ) == float (int_val )
5656
5757
58+ def test_create_scalar_from_int_with_broken_str ():
59+ """Test that QuadPrecision handles errors when __str__ fails on large integers.
60+
61+ This test checks the error handling path in scalar.c where PyObject_Str(py_int)
62+ returns NULL. We simulate this by subclassing int with a __str__ method
63+ that raises an exception.
64+ """
65+ class BrokenInt (int ):
66+ def __str__ (self ):
67+ raise RuntimeError ("Intentionally broken __str__ method" )
68+
69+ # Create an instance with a value that will overflow long long (> 2**63 - 1)
70+ # This triggers the string conversion path in quad_from_py_int
71+ broken_int = BrokenInt (2 ** 1024 )
72+
73+ # When PyLong_AsLongLongAndOverflow returns overflow,
74+ # it tries to convert to string, which should fail and propagate the error
75+ with pytest .raises (RuntimeError , match = "Intentionally broken __str__ method" ):
76+ QuadPrecision (broken_int )
77+
78+
5879class TestQuadPrecisionArrayCreation :
5980 """Test suite for QuadPrecision array creation from sequences and arrays."""
6081
@@ -287,6 +308,68 @@ def test_string_roundtrip():
287308 )
288309
289310
311+ def test_string_subclass_parsing ():
312+ """Test that QuadPrecision handles string subclasses correctly.
313+
314+ This tests the PyUnicode_Check path in scalar.c lines 195-209,
315+ verifying that string subclasses work and that parsing errors
316+ are properly handled.
317+ """
318+ class MyString (str ):
319+ """A custom string subclass"""
320+ pass
321+
322+ # Test valid string subclass - should parse correctly
323+ valid_str = MyString ("3.14159265358979323846" )
324+ result = QuadPrecision (valid_str )
325+ assert isinstance (result , QuadPrecision )
326+ expected = QuadPrecision ("3.14159265358979323846" )
327+ assert result == expected
328+
329+ # Test with scientific notation
330+ sci_str = MyString ("1.23e-100" )
331+ result = QuadPrecision (sci_str )
332+ assert isinstance (result , QuadPrecision )
333+
334+ # Test with negative value
335+ neg_str = MyString ("-42.5" )
336+ result = QuadPrecision (neg_str )
337+ assert float (result ) == - 42.5
338+
339+ # Test invalid string - should raise ValueError
340+ invalid_str = MyString ("not a number" )
341+ with pytest .raises (ValueError , match = "Unable to parse string to QuadPrecision" ):
342+ QuadPrecision (invalid_str )
343+
344+ # Test partially valid string (has trailing garbage)
345+ partial_str = MyString ("3.14abc" )
346+ with pytest .raises (ValueError , match = "Unable to parse string to QuadPrecision" ):
347+ QuadPrecision (partial_str )
348+
349+ # Test empty string
350+ empty_str = MyString ("" )
351+ with pytest .raises (ValueError , match = "Unable to parse string to QuadPrecision" ):
352+ QuadPrecision (empty_str )
353+
354+ # Test string with leading garbage
355+ leading_garbage = MyString ("abc3.14" )
356+ with pytest .raises (ValueError , match = "Unable to parse string to QuadPrecision" ):
357+ QuadPrecision (leading_garbage )
358+
359+ # Test special values
360+ inf_str = MyString ("inf" )
361+ result = QuadPrecision (inf_str )
362+ assert np .isinf (float (result ))
363+
364+ neg_inf_str = MyString ("-inf" )
365+ result = QuadPrecision (neg_inf_str )
366+ assert np .isinf (float (result )) and float (result ) < 0
367+
368+ nan_str = MyString ("nan" )
369+ result = QuadPrecision (nan_str )
370+ assert np .isnan (float (result ))
371+
372+
290373@pytest .mark .parametrize ("name,expected" , [("pi" , np .pi ), ("e" , np .e ), ("log2e" , np .log2 (np .e )), ("log10e" , np .log10 (np .e )), ("ln2" , np .log (2.0 )), ("ln10" , np .log (10.0 ))])
291374def test_math_constant (name , expected ):
292375 assert isinstance (getattr (numpy_quaddtype , name ), QuadPrecision )
0 commit comments