58 lines
1.5 KiB
Python
58 lines
1.5 KiB
Python
"""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")
|