Fix accidental loss-of-precision for to_datetime(str, unit=...)
State: closed

In Pandas 1.5.3, the `float(val)` cast was inline to the `cast_from_unit` call in `array_with_unit_to_datetime`. This caused the intermediate (unnamed) value to be a Python float.

Since #50301, a temporary variable was added to avoid multiple casts, but with explicit type `cdef float`, which defines a _Cython_ float. This type is 32-bit, and causes a loss of precision, and a regression in parsing from 1.5.3.

Since `cast_from_unit` takes an `object`, not a more specific Cython type, remove the explicit type from the temporary `fval` variable entirely. This will cause it to be a (64-bit) Python float, and thus not lose precision.

Fixes #57051

- [x] closes #xxxx (Replace xxxx with the GitHub issue number)
- [x] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [x] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [n/a] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [x] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.


Labels:
name: Datetime
description: Datetime data dtype
name: Stale

Comments:
Can this be declared as double instead? Using a PyObject is unnecessary overhead
As noted, `cast_from_unit` takes an `object`, so that doesn't seem to save anything. Here's the diff of the generated code when you add the type (vs having it commented out since that reduces line number changes in the diff):
```diff
--- untyped/pandas/_libs/tslib.pyx.c	2024-02-21 18:27:51.686387390 -0500
+++ typed/pandas/_libs/tslib.pyx.c	2024-02-21 18:24:14.824227898 -0500
@@ -25828,9 +25828,9 @@
   int __pyx_v_is_raise;
   PyArrayObject *__pyx_v_iresult = 0;
   PyDateTime_TZInfo *__pyx_v_tz = 0;
+  double __pyx_v_fval;
   PyObject *__pyx_v_result = NULL;
   PyObject *__pyx_v_val = NULL;
-  double __pyx_v_fval;
   PyObject *__pyx_v_err = NULL;
   __Pyx_LocalBuf_ND __pyx_pybuffernd_iresult;
   __Pyx_Buffer __pyx_pybuffer_iresult;
@@ -25919,14 +25919,14 @@
  *         bint is_raise = errors == "raise"
  *         ndarray[int64_t] iresult
  *         tzinfo tz = None             # <<<<<<<<<<<<<<
- *         # double fval
+ *         double fval
  * 
  */
   __Pyx_INCREF(Py_None);
   __pyx_v_tz = ((PyDateTime_TZInfo *)Py_None);
 
   /* "pandas/_libs/tslib.pyx":280
- *         # double fval
+ *         double fval
  * 
  *     assert is_coerce or is_raise             # <<<<<<<<<<<<<<
  * 
@@ -32771,7 +32771,7 @@
  *     ndarray[object] values,
  *     str unit,
  */
-  __pyx_tuple__37 = PyTuple_Pack(13, __pyx_n_s_values, __pyx_n_s_unit, __pyx_n_s_errors, __pyx_n_s_i, __pyx_n_s_n, __pyx_n_s_is_coerce, __pyx_n_s_is_raise, __pyx_n_s_iresult, __pyx_n_s_tz, __pyx_n_s_result, __pyx_n_s_val, __pyx_n_s_fval, __pyx_n_s_err); if (unlikely(!__pyx_tuple__37)) __PYX_ERR(0, 239, __pyx_L1_error)
+  __pyx_tuple__37 = PyTuple_Pack(13, __pyx_n_s_values, __pyx_n_s_unit, __pyx_n_s_errors, __pyx_n_s_i, __pyx_n_s_n, __pyx_n_s_is_coerce, __pyx_n_s_is_raise, __pyx_n_s_iresult, __pyx_n_s_tz, __pyx_n_s_fval, __pyx_n_s_result, __pyx_n_s_val, __pyx_n_s_err); if (unlikely(!__pyx_tuple__37)) __PYX_ERR(0, 239, __pyx_L1_error)
   __Pyx_GOTREF(__pyx_tuple__37);
   __Pyx_GIVEREF(__pyx_tuple__37);
   __pyx_codeobj__38 = (PyObject*)__Pyx_PyCode_New(3, 0, 0, 13, 0, CO_OPTIMIZED|CO_NEWLOCALS, __pyx_empty_bytes, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_tuple__37, __pyx_empty_tuple, __pyx_empty_tuple, __pyx_kp_s_home_elliott_code_pandas_pandas, __pyx_n_s_array_with_unit_to_datetime, 239, __pyx_empty_bytes); if (unlikely(!__pyx_codeobj__38)) __PYX_ERR(0, 239, __pyx_L1_error)
```
Fair point on the existing structure of the code. But if it works should still add it - generally the more typing in Cython the better. And if this gets refactored in the future the next developer won't have to think about it
If you see us using float anywhere else probably worth changing to double everywhere in Cython (in a follow up PR)
Thanks a lot for your investigation and fix for this @QuLogic!
This pull request is stale because it has been open for thirty days with no activity. Please [update](https://pandas.pydata.org/pandas-docs/stable/development/contributing.html#updating-your-pull-request) and respond to this comment if you're still interested in working on this.
Thanks @QuLogic