Skip to content

BUG: np.percentile fails with internal overflow when using float16 input on large arrays #29003

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
friedlerido opened this issue May 19, 2025 · 5 comments
Labels

Comments

@friedlerido
Copy link

Describe the issue:

When using np.percentile or np.nanpercentile on a large float16 array from real data (≈9 million elements), I get:
ValueError: kth(=-9223372036845518721) out of bounds (9257087)

This happens even for low percentiles like 20 or 50, and is resolved by casting the array to float64.

⚠️ Expected Behavior:
Either: NumPy automatically upcasts float16 to float64
Or: raises a UserWarning if float16 is passed to percentile

Reproduce the code example:

# This does not happen with random arrays of similar shape, but does happen consistently  #with my real-world data:
arr = np.load("example.npy")  # I can share this privately if needed
print(arr.dtype)  # float16
print(arr.shape)  # (9257087,)

np.percentile(arr, 50)  # → raises ValueError

# ✅ This works:
np.percentile(arr.astype(np.float64), 50)  # works fine

Error message:

/opt/venv/lib/python3.11/site-packages/numpy/lib/_function_base_impl.py:107: RuntimeWarning: overflow encountered in cast
  get_virtual_index=lambda n, quantiles: (n - 1) * quantiles,
/opt/venv/lib/python3.11/site-packages/numpy/lib/_function_base_impl.py:4750: RuntimeWarning: overflow encountered in cast
  indexes_above_bounds = virtual_indexes >= valid_values_count - 1
/opt/venv/lib/python3.11/site-packages/numpy/lib/_function_base_impl.py:4655: RuntimeWarning: invalid value encountered in multiply
  lerp_interpolation = asanyarray(add(a, diff_b_a * t, out=out))
/opt/venv/lib/python3.11/site-packages/numpy/lib/_function_base_impl.py:4656: RuntimeWarning: invalid value encountered in scalar multiply
  subtract(b, diff_b_a * (1 - t), out=lerp_interpolation, where=t >= 0.5,
np.float16(nan)

Python and NumPy Versions:

2.2.4
3.11.3 (main, May 23 2023, 13:34:03) [GCC 10.2.1 20210110]

Runtime Environment:

[{'numpy_version': '2.2.4',
'python': '3.11.3 (main, May 23 2023, 13:34:03) [GCC 10.2.1 20210110]',
'uname': uname_result(system='Linux', , release='5.15.0-1039-aws', version='#44~20.04.1-Ubuntu SMP Thu Jun 22 12:21:12 UTC 2023', machine='x86_64')},
{'simd_extensions': {'baseline': ['SSE', 'SSE2', 'SSE3'],
'found': ['SSSE3',
'SSE41',
'POPCNT',
'SSE42',
'AVX',
'F16C',
'FMA3',
'AVX2'],
'not_found': ['AVX512F',
'AVX512CD',
'AVX512_KNL',
'AVX512_KNM',
'AVX512_SKX',
'AVX512_CLX',
'AVX512_CNL',
'AVX512_ICL']}},
{'architecture': 'Haswell',
'filepath': '/opt/venv/lib/python3.11/site-packages/numpy.libs/libscipy_openblas64_-6bb31eeb.so',
'internal_api': 'openblas',
'num_threads': 64,
'prefix': 'libscipy_openblas',
'threading_layer': 'pthreads',
'user_api': 'blas',
'version': '0.3.28'}]

Context for the issue:

No response

@eendebakpt
Copy link
Contributor

@friedlerido Could you share the example.npy? If possible try to reduce the example data to a minimal case that can be generated by plain python. That will help us to investigate.

@friedlerido
Copy link
Author

example.zip

@eendebakpt
Copy link
Contributor

For the example above we end up with values_count=n equal to the shape of the input array and quantiles equal to np.array([50], dtype=arr.dtype) inside _quantile. There a call is made to the get_virtual_index of the linear method which is defined as

get_virtual_index=lambda n, quantiles: (n - 1) * quantiles,

and the overflow occurs.

The issue might have been introduced in #23912 @seberg. There the dtype of the input array is set on the quantiles, e.g.

# Use dtype of array if possible (e.g., if q is a python int or float)
# by making the divisor have the dtype of the data array.
q = np.true_divide(q, a.dtype.type(100) if a.dtype.kind == "f" else 100, out=...)

Here is a small reproducer:

import numpy as np
arr = np.zeros(65521, dtype=np.float16)
arr[:10] = 1
z = np.percentile(arr, 50)
print(z)

@seberg
Copy link
Member

seberg commented May 21, 2025

Likely NEP 50 itself that changed the dtype, not so much the specific code change? I am not sure immediately if there are some internal calculations that should maybe always use float64 at least (because they are related to the length of an array, so need full float64 mantissa to be pretty correct).

To some degree, float16 tends to overflow, but this may also not be ideal for float32. The other thing is that a lerp might do a better job (e.g. by being a ufunc), but then I am not sure there is a hot-fix.

@eendebakpt
Copy link
Contributor

This indeed seems tricky to get right: the output of get_virtual_index=lambda n, quantiles: (n - 1) * quantiles is an array with virtual indices virtual_indexes that is used in two ways (roughly):

  • floor(virtual_indexes) and ceil(virtual_indices) are indices of the two data points (in the sorted data) used for interpolation
  • g= virtual_indexes % 1 is used as a factor to interpolate between the two data points

We can use np.interp (or some other way) to calculate the value of (n - 1) * quantiles without overflows, but the result has to be in float64 (at least higher than float16 which is not enough). Then also g will be float64 and the final result of np.quantile is (1-g)*y[j] + g*y[j+1] which will also be float64. This is not ideal since we would like to have the output of np.quantile(float16_array, [0, .5, .99]) to be float16. On the other hand, np.quantile([0, 1, 3], [0]) is also promoted to float64.

We could downcast g to the dtype of the input array. (since g is in the 0 to 1 range that seems fine), but that might break cases with integer input.

Several other methods also give incorrect results:

import numpy as np
arr = np.zeros(65521, dtype=np.float16)
arr[:10] = 1
methods = ['inverted_cdf','averaged_inverted_cdf','closest_observation','interpolated_inverted_cdf','hazen','weibull',
           'linear', 'median_unbiased','normal_unbiased']

for method in methods:
    z = np.percentile(arr, 50, method=method)
    print(f'{method=} output={z} {z.dtype=}')

Output:

method='inverted_cdf' output=0.0 z.dtype=dtype('float16')
method='averaged_inverted_cdf' output=1.0 z.dtype=dtype('float16')
method='closest_observation' output=0.0 z.dtype=dtype('float16')
method='interpolated_inverted_cdf' output=nan z.dtype=dtype('float16')
method='hazen' output=nan z.dtype=dtype('float16')
method='weibull' output=nan z.dtype=dtype('float16')
method='linear' output=nan z.dtype=dtype('float16')
method='median_unbiased' output=nan z.dtype=dtype('float16')
method='normal_unbiased' output=nan z.dtype=dtype('float16')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy