@@ -5338,6 +5338,115 @@ def test_pickle_fortran_order(self, backend):
53385338 assert unpickled .dtype == original .dtype
53395339 assert unpickled .flags .f_contiguous == original .flags .f_contiguous
53405340
5341+
5342+ class TestScalarPickle :
5343+ """Regression tests for issue #99: bare QuadPrecision scalars (not wrapped
5344+ in an array) must round-trip through pickle.dumps / pickle.loads without
5345+ raising and must preserve value, type, and backend."""
5346+
5347+ @pytest .mark .parametrize ("backend" , ["sleef" , "longdouble" ])
5348+ def test_pickle_scalar_issue_repro (self , backend ):
5349+ """The exact repro from #99: was RuntimeError on loads()."""
5350+ import pickle
5351+ original = QuadPrecision ("123.456" , backend = backend )
5352+ loaded = pickle .loads (pickle .dumps (original ))
5353+ assert isinstance (loaded , QuadPrecision )
5354+ assert loaded == original
5355+ assert str (loaded ) == str (original )
5356+
5357+ @pytest .mark .parametrize ("backend" , ["sleef" , "longdouble" ])
5358+ @pytest .mark .parametrize ("value" , [
5359+ "0.0" , "-0.0" , "1.0" , "-1.0" , "42.0" , "-42.0" ,
5360+ "3.141592653589793238462643383279502884197" , # ~quad-precision pi
5361+ "2.718281828459045235360287471352662497757" ,
5362+ "1e100" , "1e-100" , "-1e100" , "-1e-100" ,
5363+ "1.23456789012345678901234567890e30" ,
5364+ ])
5365+ def test_pickle_scalar_finite_roundtrip (self , backend , value ):
5366+ """Finite values must round-trip exactly (Dragon4-Unique is lossless)."""
5367+ import pickle
5368+ original = QuadPrecision (value , backend = backend )
5369+ loaded = pickle .loads (pickle .dumps (original ))
5370+ assert isinstance (loaded , QuadPrecision )
5371+ assert loaded .dtype == QuadPrecDType (backend = backend )
5372+ # Exact equality: pickle/unpickle should not lose any bits.
5373+ assert loaded == original
5374+ # And the canonical repr should match.
5375+ assert str (loaded ) == str (original )
5376+
5377+ @pytest .mark .parametrize ("backend" , ["sleef" , "longdouble" ])
5378+ def test_pickle_scalar_inf (self , backend ):
5379+ import pickle
5380+ for s in ["inf" , "-inf" ]:
5381+ original = QuadPrecision (s , backend = backend )
5382+ loaded = pickle .loads (pickle .dumps (original ))
5383+ assert isinstance (loaded , QuadPrecision )
5384+ assert loaded == original # inf == inf, -inf == -inf
5385+ assert float (loaded ) == float (original )
5386+
5387+ @pytest .mark .parametrize ("backend" , ["sleef" , "longdouble" ])
5388+ def test_pickle_scalar_nan (self , backend ):
5389+ import pickle
5390+ original = QuadPrecision ("nan" , backend = backend )
5391+ loaded = pickle .loads (pickle .dumps (original ))
5392+ assert isinstance (loaded , QuadPrecision )
5393+ # NaN != NaN, so we check isnan instead.
5394+ import math
5395+ assert math .isnan (float (loaded ))
5396+ assert loaded .dtype == QuadPrecDType (backend = backend )
5397+
5398+ @pytest .mark .parametrize ("backend" , ["sleef" , "longdouble" ])
5399+ @pytest .mark .parametrize ("protocol" , [0 , 1 , 2 , 3 , 4 , 5 ])
5400+ def test_pickle_scalar_all_protocols (self , backend , protocol ):
5401+ """Round-trip must work across every pickle protocol version."""
5402+ import pickle
5403+ original = QuadPrecision ("3.14159265358979323846" , backend = backend )
5404+ data = pickle .dumps (original , protocol = protocol )
5405+ loaded = pickle .loads (data )
5406+ assert isinstance (loaded , QuadPrecision )
5407+ assert loaded == original
5408+ assert loaded .dtype == QuadPrecDType (backend = backend )
5409+
5410+ def test_pickle_scalar_preserves_type (self ):
5411+ import pickle
5412+ loaded = pickle .loads (pickle .dumps (QuadPrecision ("1.0" )))
5413+ assert type (loaded ) is QuadPrecision
5414+
5415+ def test_pickle_scalar_preserves_backend_across_mix (self ):
5416+ """Each backend pickle must come back as the same backend, not silently
5417+ defaulting to sleef."""
5418+ import pickle
5419+ ld = QuadPrecision ("1.5" , backend = "longdouble" )
5420+ sl = QuadPrecision ("1.5" , backend = "sleef" )
5421+ ld_loaded = pickle .loads (pickle .dumps (ld ))
5422+ sl_loaded = pickle .loads (pickle .dumps (sl ))
5423+ assert ld_loaded .dtype == QuadPrecDType (backend = "longdouble" )
5424+ assert sl_loaded .dtype == QuadPrecDType (backend = "sleef" )
5425+
5426+ def test_pickle_scalar_in_list (self ):
5427+ """Composite container of scalars also pickles cleanly."""
5428+ import pickle
5429+ original = [QuadPrecision ("1.5" ), QuadPrecision ("2.5" ),
5430+ QuadPrecision ("nan" ), QuadPrecision ("inf" )]
5431+ loaded = pickle .loads (pickle .dumps (original ))
5432+ import math
5433+ assert len (loaded ) == 4
5434+ assert loaded [0 ] == original [0 ]
5435+ assert loaded [1 ] == original [1 ]
5436+ assert math .isnan (float (loaded [2 ]))
5437+ assert loaded [3 ] == original [3 ]
5438+
5439+ @pytest .mark .parametrize ("backend" , ["sleef" , "longdouble" ])
5440+ def test_pickle_scalar_loads_does_not_raise (self , backend ):
5441+ """Direct regression on the exact failure mode in the bug report
5442+ (RuntimeError from numpy's legacy SETITEM path)."""
5443+ import pickle
5444+ try :
5445+ pickle .loads (pickle .dumps (QuadPrecision ("123.456" , backend = backend )))
5446+ except RuntimeError as exc :
5447+ pytest .fail (f"pickle.loads raised RuntimeError: { exc } " )
5448+
5449+
53415450@pytest .mark .parametrize ("dtype" , [
53425451 "bool" ,
53435452 "byte" , "int8" , "ubyte" , "uint8" ,
0 commit comments