"""Reglas numéricas (entero, redondeo).""" from __future__ import annotations import math import numpy as np import pandas as pd from ..nullish import _is_nullish_scalar from ..numbers import _to_numeric_lenient def _rule_to_integer(series: pd.Series) -> pd.Series: """Convierte a entero (Int64 nullable) con parser tolerante.""" numeric = _to_numeric_lenient(series.astype(str)) return numeric.round().astype("Int64") def _round_to_int_half_tie_le(x: float) -> int: """Fracc. > 0,5 hacia arriba; 0,5 o menos hacia abajo. Banda 1e-9 en 0,5 (float). Misma noción que en la descripción de la regla. Trabaja sobre |x| y recompone el signo. """ sign = -1 if x < 0 else 1 if x == 0.0 or x == -0.0: return 0 ax = abs(float(x)) ip = math.trunc(ax) frac = ax - ip if frac < 0.5 - 1e-9: out = ip elif frac > 0.5 + 1e-9: out = ip + 1 else: out = ip return -out if sign < 0 else out def _rule_round_decimals_custom(series: pd.Series) -> pd.Series: """Mismo parser tolerante que convertir a entero; aprox. con fracc. >0,5 arriba.""" numeric = _to_numeric_lenient(series.astype(str)) def _fn(x): if _is_nullish_scalar(x) or (isinstance(x, (float, np.floating)) and (not np.isfinite(x) or np.isnan(x))): return pd.NA try: f = float(x) except (TypeError, ValueError, OverflowError): return pd.NA if not np.isfinite(f): return pd.NA if f == 0.0 or f == -0.0: return 0 return _round_to_int_half_tie_le(f) return numeric.map(_fn).astype("Int64")