Skip to content

Fix chop_threshold crashes in the pandas-free complex repr - #620

Open
eriknw wants to merge 2 commits into
38-monoid-guard-udt-fallback-builtinsfrom
39-repr-chop-complex-overflow
Open

Fix chop_threshold crashes in the pandas-free complex repr#620
eriknw wants to merge 2 commits into
38-monoid-guard-udt-fallback-builtinsfrom
39-repr-chop-complex-overflow

Conversation

@eriknw

@eriknw eriknw commented Aug 5, 2026

Copy link
Copy Markdown
Member

Stacked on #619. An overnight differential fuzz of #605's pandas-free renderer against the real pandas renderer (34,618 repr/HTML comparisons across 17,309 case pairs) found a crash family in the display.chop_threshold path for complex dtypes, reachable in the COO (long) display form:

  1. Tip-only crash, pandas renders fine: abs() of a python complex raises OverflowError for finite components near the float max, where numpy's abs (which pandas uses) returns inf and never chops. Fixed by treating an overflowing magnitude as never chopped; the result is byte-identical to the pandas renderer (verified, repr and HTML hashes equal against 6f1eb02).
import numpy as np, pandas as pd, graphblas as gb
big = float(np.finfo(np.float64).max)
v = gb.Vector.from_coo([10_000, 500_000], [complex(big, big), 1e-7], size=10**6, dtype="FC64")
with pd.option_context("display.chop_threshold", 1e-8):
    repr(v)   # OverflowError before this fix; byte-identical to pandas after
  1. Both-sides crash: a complex value that actually chops was replaced with float 0.0, putting a j-less string into the complex column, which _trim_zeros_complex cannot parse (IndexError). pandas 3.0.3 crashes the same way on the same input in its own chop path, so byte-identity would mean reproducing a pandas crash. This fix chops to the value's own type of zero, so the cell renders 0.000000e+00+0.000000e+00j and the object reprs where pandas cannot. A deliberate, documented divergence, reachable only with chop_threshold set on a complex dtype.

Gates: full pinned suite 1114 passed / 145 skipped; pre-commit all hooks pass. The broader fuzz findings (all in non-crash territory) are written up in the overnight stack report for the maintainer meeting.

@eriknw
eriknw force-pushed the 39-repr-chop-complex-overflow branch from 7ee751f to 44033e7 Compare August 5, 2026 17:44
@eriknw
eriknw force-pushed the 39-repr-chop-complex-overflow branch from 44033e7 to 2fa3cb8 Compare August 5, 2026 18:03
@eriknw
eriknw force-pushed the 39-repr-chop-complex-overflow branch from 2fa3cb8 to 376ee94 Compare August 5, 2026 18:05
@eriknw
eriknw force-pushed the 39-repr-chop-complex-overflow branch from 376ee94 to e0eb986 Compare August 6, 2026 07:59
@eriknw
eriknw force-pushed the 39-repr-chop-complex-overflow branch from e0eb986 to 9d99244 Compare August 6, 2026 15:39
@eriknw
eriknw force-pushed the 39-repr-chop-complex-overflow branch 2 times, most recently from 25d557b to dbff5f6 Compare August 6, 2026 20:36
@eriknw
eriknw force-pushed the 39-repr-chop-complex-overflow branch from dbff5f6 to b022fa4 Compare August 6, 2026 20:41
@eriknw
eriknw force-pushed the 39-repr-chop-complex-overflow branch from b022fa4 to b66bc7c Compare August 7, 2026 02:49
eriknw added 2 commits August 7, 2026 00:09
The COO form formats a complex column through _format_float_column, where
display.chop_threshold compares each value's magnitude. Two crashes hid
there, found by an overnight differential fuzz against the pandas
renderer:

- abs() of a python complex raises OverflowError for finite components
  near the float max, where numpy's abs returns inf. pandas therefore
  never chops such a value and renders the object fine; the pandas-free
  path crashed. Treat an overflowing magnitude as never chopped.
  Byte-identity for this case is restored, verified against the pandas
  renderer at 6f1eb02 (repr and HTML hashes equal).

- A value that actually chops was replaced with float 0.0, which puts a
  j-less string into the complex column, and _trim_zeros_complex cannot
  parse that (IndexError). pandas 3.0.3 has the same bug in its own chop
  path (pandas.io.formats.format._trim_zeros_complex raises the same
  IndexError on the same input), so byte-identity here means identity
  with a crash. Chop to the value's own type of zero instead: the
  chopped cell renders 0.000000e+00+0.000000e+00j and the object reprs
  where pandas cannot.

The second point is a deliberate, documented divergence from pandas,
reachable only with display.chop_threshold set on a complex dtype.
On Windows with python-suitesparse-graphblas older than 7.4.3.1 the CFFI
bindings are built without complex support (MSVC lacks C99 _Complex), so
dtypes._supports_complex is False, FC64 is never registered, and
Vector.from_coo(..., dtype="FC64") raises ValueError before the test
gets to the formatting code under test. Guard with the same
skipif("not dtypes._supports_complex") pattern test_scalar.py uses;
the module already imports dtypes, which the string condition needs.

Verified the test still runs and passes on a complex-capable host, and
that the skipif string evaluates to True in the module namespace when
_supports_complex is flipped to False.
@eriknw
eriknw force-pushed the 39-repr-chop-complex-overflow branch from b66bc7c to d09186f Compare August 7, 2026 05:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant