@@ -121,7 +121,10 @@ def _validate_trusted_real_scalar(value: object) -> None:
121121 raise ValueError ("data must be real-valued" )
122122 if value_type is np .bool_ or any (
123123 value_type is scalar_type
124- for scalar_type in (* _TRUSTED_NUMPY_INTEGER_TYPES , * _TRUSTED_NUMPY_FLOAT_TYPES )
124+ for scalar_type in (
125+ * _TRUSTED_NUMPY_INTEGER_TYPES ,
126+ * _TRUSTED_NUMPY_FLOAT_TYPES ,
127+ )
125128 ):
126129 return
127130 raise ValueError ("data must be numeric and convertible to float64" )
@@ -148,13 +151,45 @@ def _preflight_real_matrix(data: object) -> None:
148151 if row_type is list or row_type is tuple :
149152 for column_index in range (len (row )):
150153 cell = row [column_index ]
151- if type (cell ) is list or type (cell ) is tuple or type (cell ) is np .ndarray :
154+ if (
155+ type (cell ) is list
156+ or type (cell ) is tuple
157+ or type (cell ) is np .ndarray
158+ ):
152159 raise ValueError ("data must be a 2-D persons x items array" )
153160 _validate_trusted_real_scalar (cell )
154161 continue
155162 _validate_trusted_real_scalar (row )
156163
157164
165+ def _lossless_float64_matrix (raw : np .ndarray ) -> np .ndarray :
166+ """Narrow trusted evidence only when every finite numeric identity survives."""
167+ try :
168+ with np .errstate (over = "ignore" , invalid = "ignore" ):
169+ narrowed = np .ascontiguousarray (raw , dtype = np .float64 )
170+ except (TypeError , ValueError , OverflowError ):
171+ raise ValueError ("data must be numeric and convertible to float64" ) from None
172+
173+ if raw .dtype .kind in ("i" , "u" ):
174+ info = np .iinfo (raw .dtype )
175+ bits = raw .dtype .itemsize * 8
176+ lower = 0.0 if raw .dtype .kind == "u" else float (- (1 << (bits - 1 )))
177+ upper = float (1 << bits ) if raw .dtype .kind == "u" else float (1 << (bits - 1 ))
178+ if np .any (narrowed < lower ) or np .any (narrowed >= upper ):
179+ raise ValueError ("data must be exactly representable as float64" )
180+ restored = narrowed .astype (raw .dtype )
181+ if not np .array_equal (restored , raw ):
182+ raise ValueError ("data must be exactly representable as float64" )
183+ del info
184+ elif raw .dtype .kind == "f" and raw .dtype .itemsize > np .dtype (np .float64 ).itemsize :
185+ restored = narrowed .astype (raw .dtype )
186+ finite = np .isfinite (raw )
187+ if np .any (restored [finite ] != raw [finite ]):
188+ raise ValueError ("data must be exactly representable as float64" )
189+
190+ return narrowed
191+
192+
158193def _real_numeric_matrix (data : object ) -> np .ndarray :
159194 """Validate inert real evidence before narrowing it to contiguous ``float64``."""
160195 _preflight_real_matrix (data )
@@ -168,10 +203,7 @@ def _real_numeric_matrix(data: object) -> np.ndarray:
168203 raise ValueError ("data must be real-valued" )
169204 if raw .dtype .kind not in ("b" , "i" , "u" , "f" ):
170205 raise ValueError ("data must be numeric and convertible to float64" )
171- try :
172- return np .ascontiguousarray (raw , dtype = np .float64 )
173- except (TypeError , ValueError , OverflowError ):
174- raise ValueError ("data must be numeric and convertible to float64" ) from None
206+ return _lossless_float64_matrix (raw )
175207
176208
177209def parallel_analysis (
@@ -204,10 +236,11 @@ def parallel_analysis(
204236 scalar evidence; arbitrary array/container/numeric subclasses and
205237 conversion providers are rejected before NumPy protocols execute. The
206238 known 2-D carrier structure is preflighted without unbounded recursive
207- container traversal. Complex and non-real storage is rejected before the
208- accepted matrix is marshalled to contiguous ``float64``. The random-
209- eigenvalue benchmark workspace is bounded to 128 MiB before compiled
210- dispatch.
239+ container traversal. Finite integer and extended-precision floating
240+ observations must preserve their numeric identity through the Rust `f64`
241+ boundary. Complex and non-real storage is rejected before the accepted
242+ matrix is marshalled to contiguous ``float64``. The random-eigenvalue
243+ benchmark workspace is bounded to 128 MiB before compiled dispatch.
211244
212245 """
213246 explicit_iterations = (
0 commit comments