Skip to content

[Sad Catapella N1][Numerical correctness] Float histogram binning loses finite near-zero values due to scale overflow/underflow #27

Closed

Description

@guijacquemet

Scope / classification

Normalization Batch 1 at f784c95e3049483f87466150ffc5764375fa631d. Confirmed floating-point arithmetic edge case by source inspection and isolated formula probe, not a package benchmark or test run.

Evidence

Minimal counterexample

An array np.array([0., 1e-308], dtype=np.float64) with 100 bins has half_range=5e-309; the ratio 100 / half_range overflows to infinity, so the bin-index computation can yield NaN/infinity and cast to a sentinel integer before clipping. The two distinct finite values can therefore collapse to bin 0 instead of first and last. For np.nextafter(0.,1.), halving the difference becomes zero. Isolated NumPy arithmetic confirmed this behavior.

Impact

Incorrect histogram counts/range checks for valid subnormal or near-zero floating data without an explicit refusal. Rare in normal raw microscopy but plausible in transformed scientific arrays; the library advertises float64 support without an exclusion.

Fix / acceptance

Develop a scale-safe normalized coordinate calculation that avoids both overflow of n/range and underflow of intermediate halves, retaining established bin boundary/rounding conventions for normal values. Do not add an arbitrary epsilon or coerce small numbers to zero. Compare with an exact/high-precision independent oracle for subnormals, 1e-300, mixed signs, extreme opposite signs, constant planes, and robust-mode coincident limits. Ensure min/max, counts, quantiles, warnings, error bounds and block-vs-whole agreement. Benchmark the stable method before replacing hot loops.

Activity

  1. guijacquemet commented on Nov 11, 2026

    @guijacquemet
    ContributorAuthor

    This does not reproduce on main c952263 (v0.4.0). It was fixed by #85/#87: float_bin_position uses a bounded fraction wherever halving would underflow or n / half_range would overflow.

    I checked it again on c952263 for the review of #91:

    • [0, 1e-308] with 100 bins gives bins 0 and 99. [0, 5e-324] with 2 and 100 bins gives the first and last bin. [-1e-300, 1e-300] with 64 bins gives bins 0 and 63. [0, 1e-310] with 7 bins gives bins 0 and 6. [-5e-324, 5e-324] with 3 bins gives bins 0 and 2. A constant 1e-300 plane gives bin 0 only. Every representative is finite.
    • I compared against an exact Fraction oracle, floor((v - lo) / (hi - lo) * n), using 20,000 uniform values at the scales 1e-308, 1e-300, 1e-320, 7·5e-324 and 1e-310. I used 2, 3, 16, 100 and 257 bins, min-max and robust ranges, and read the data in two blocks through histogram_with_range_check. All 50 cases match bin for bin.
    • test_float_range_of_the_smallest_subnormal_keeps_distinct_bins, test_float_narrow_range_does_not_overflow_the_bin_scale and test_float_range_between_subnormals_keeps_every_pixel_in_its_bin cover these cases as regression tests.

    I'm closing this as completed by #85/#87.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions