SciSharp / SciSharp/NumSharp

[NEP50 Core] Type promotion diverges from NumPy 2.x NEP 50 for unsigned int array + signed int scalar

Open
#529 2 comments 0 reactions 1 assignee View on GitHub

@Nucs is already working on this.

Since Feb 18, 2026.

bug core NumPy 2.x Compliance
Dominant language
C#
Stars
1.5k
Forks
205
Avg merge
7d 7h
Merged PRs (30d)
2

Description

Summary

NumSharp's array-scalar type promotion table (_typemap_arr_scalar in Logic/np.find_common_type.cs) follows NumPy 1.x promotion rules for 12 out of 80 comparable entries. These 12 entries diverge from NumPy 2.x behavior introduced by NEP 50 ("Promotion rules for Python scalars and weak typing").

The array-array promotion table (_typemap_arr_arr) is a perfect match — all 100 comparable entries are correct.

What Changed in NumPy 2.x (NEP 50)

NEP 50 introduced "weak typing" for scalars: when an array operates with a scalar, the array dtype wins if the scalar is of the same or lower "kind" (e.g., both are integers). Previously (NumPy 1.x), the scalar's type could force the result to widen to accommodate both value ranges.

Key rule change:

  • NumPy 1.x: uint8_array + int32_scalarint32 (widen to hold both ranges)
  • NumPy 2.x: uint8_array + int32_scalaruint8 (array dtype wins, both are integer kind)

The 12 Divergent Entries

All follow the same pattern: unsigned integer array + signed integer scalar. NumSharp widens the result (1.x behavior), but NumPy 2.x preserves the array's unsigned dtype.

Line Entry NumSharp (1.x) NumPy 2.x (NEP 50)
258 (uint8_arr, int16_scalar) int16 uint8
260 (uint8_arr, int32_scalar) int32 uint8
262 (uint8_arr, int64_scalar) int64 uint8
297 (uint16_arr, int16_scalar) int32 uint16
299 (uint16_arr, int32_scalar) int32 uint16
301 (uint16_arr, int64_scalar) int64 uint16
323 (uint32_arr, int16_scalar) int64 uint32
325 (uint32_arr, int32_scalar) int64 uint32
327 (uint32_arr, int64_scalar) int64 uint32
349 (uint64_arr, int16_scalar) float64 uint64
351 (uint64_arr, int32_scalar) float64 uint64
353 (uint64_arr, int64_scalar) float64 uint64

Verified Against NumPy 2.4.2

>>> import numpy as np
>>> np.__version__
'2.4.2'

# uint8 array + int32 scalar: array dtype wins
>>> (np.array([1], dtype=np.uint8) + 1).dtype
dtype('uint8')

# uint64 array + int64 scalar: array dtype wins (not float64)
>>> (np.array([1], dtype=np.uint64) + 1).dtype
dtype('uint64')

# Contrast: array + array still widens (unchanged)
>>> (np.array([1], dtype=np.uint8) + np.array([1], dtype=np.int32)).dtype
dtype('int32')

Location

File: src/NumSharp.Core/Logic/np.find_common_type.cs
Dictionary: _typemap_arr_scalar (lines 242–425)

The 12 entries are at lines: 258, 260, 262, 297, 299, 301, 323, 325, 327, 349, 351, 353.

Proposed Fix

Update the 12 dictionary entries so the result type equals the array's unsigned type:

// Before (NumPy 1.x):
_typemap_arr_scalar.Add((np.uint8, np.int16), np.int16);
// After (NumPy 2.x NEP 50):
_typemap_arr_scalar.Add((np.uint8, np.int16), np.uint8);

Also update the corresponding _nptypemap_arr_scalar (derived from _typemap_arr_scalar at line 424).

Context

  • NumSharp was originally built targeting NumPy 1.x
  • NumPy 2.0 (June 2024) overhauled type promotion via NEP 50
  • The array-array table (_typemap_arr_arr) already matches NumPy 2.x perfectly — only the array-scalar table needs updating
  • np.find_common_type() itself was removed in NumPy 2.0 (replaced by np.result_type() / np.promote_types()), but the internal _FindCommonType logic is used throughout NumSharp for operator dispatch

Related

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.