Skip to content

[NEP50] Implement NumPy 2.x type promotion for unsigned array + signed scalar#572

Merged
Nucs merged 5 commits intomasterfrom
nep50
Feb 14, 2026
Merged

[NEP50] Implement NumPy 2.x type promotion for unsigned array + signed scalar#572
Nucs merged 5 commits intomasterfrom
nep50

Conversation

@Nucs
Copy link
Member

@Nucs Nucs commented Feb 14, 2026

Summary

Implements NEP 50 type promotion rules for NumSharp, aligning with NumPy 2.x behavior.

Key change: When an unsigned integer array operates with a signed integer scalar, the array dtype now wins (no type widening).

// Before (NumPy 1.x behavior):
np.array(new byte[] {1, 2, 3}) + 5  // → int32

// After (NumPy 2.x NEP 50):
np.array(new byte[] {1, 2, 3}) + 5  // → uint8 (array wins)

Changes

Type Promotion Table (np.find_common_type.cs)

  • Updated 12 entries in _typemap_arr_scalar for unsigned array + signed scalar combinations
  • Added comprehensive documentation explaining the entire type promotion system

Test Coverage (NEP50.cs)

  • 59 tests covering all 12 NEP 50 combinations
  • All operations tested: +, -, *, %
  • Value correctness verification
  • Each test verified against NumPy 2.4.2

Affected Type Combinations (12)

Array Type Scalar Types Old Result New Result
uint8 int16/int32/int64 int16/int32/int64 uint8
uint16 int16/int32/int64 int32/int32/int64 uint16
uint32 int16/int32/int64 int64/int64/int64 uint32
uint64 int16/int32/int64 float64/float64/float64 uint64

Breaking Change

This is a behavioral breaking change for users who relied on NumPy 1.x type widening behavior. This aligns with NumPy 2.x and the project's NumPy alignment goal.

Test Plan

  • All 59 NEP50 tests pass
  • Full test suite passes (2209 succeeded, 11 skipped)
  • Each test verified against NumPy 2.4.2 output

Related Issues

References

Nucs added 5 commits February 14, 2026 12:44
NumPy 2.0 introduced NEP 50 which changed type promotion rules: when an
unsigned integer array operates with a signed integer scalar of the same
or lower "kind", the array dtype now wins (no type widening).

This updates _typemap_arr_scalar for 12 entries:
- uint8 + int16/int32/int64 → uint8 (was int16/int32/int64)
- uint16 + int16/int32/int64 → uint16 (was int32/int32/int64)
- uint32 + int16/int32/int64 → uint32 (was int64/int64/int64)
- uint64 + int16/int32/int64 → uint64 (was float64/float64/float64)

Breaking change for users relying on NumPy 1.x widening behavior.
This aligns with NumPy 2.x and the project's NumPy alignment goal.

Closes #529

See: https://numpy.org/neps/nep-0050-scalar-promotion.html
Adds 44 tests covering NEP 50 type promotion rules, each verified against
NumPy 2.4.2 output via Python execution.

Test categories:
- Unsigned/signed array + Python int (array dtype wins)
- Float array + Python int/float (array dtype wins)
- Int array + Python float (cross-kind → float64)
- All arithmetic operations: +, -, *, /, %
- Scalar-first operations (commutative)
- Direct _FindCommonArrayScalarType table verification
- Array-array operations (unchanged by NEP 50)
- Edge cases: empty arrays, multi-dimensional, scalar arrays

Documents NumSharp behavioral differences (marked [Misaligned]):
- Division uses integer semantics (not true division like NumPy)
- Scalar-scalar uses different promotion path than arr-scalar

Each test includes the NumPy verification command in its docstring:
  Verified: python3 -c "import numpy as np; ..."
Adds comprehensive operation matrix tests:
- 12 type combinations (4 unsigned × 3 signed scalar types)
- 4 operations each (+, -, *, %)
- 48 dtype assertions + value correctness tests

Coverage now includes:
- uint8/uint16/uint32/uint64 arrays
- int16/int32/int64 scalars (short/int/long)
- All arithmetic operations
- Value correctness verification

Total: 59 tests, ~150+ assertions
Replace inline "// NEP 50: array dtype wins" comments with detailed
block documentation explaining:

- NumSharp design decision for scalar handling
- Why C# scalars are treated as "weakly typed" like Python scalars
- Complete table of affected entries (12 combinations)
- NumPy 1.x vs 2.x behavior comparison
- Verification command and NumPy version tested against
- Reference to NEP 50 specification

The documentation block now serves as the single source of truth for
understanding the type promotion behavior in this table.
Add detailed documentation explaining the entire type promotion system:

- Architecture overview (4 lookup tables)
- When each table is used (arr+arr vs arr+scalar)
- Kind hierarchy (boolean < integer < float < complex)
- Within-kind promotion rules
- Cross-kind promotion rules
- Practical examples
- References to NumPy docs, NEP 50, Array API spec

Also document arr_arr table with specific promotion rules:
- Same type, same kind different size
- Signed + unsigned combinations
- Cross-kind and complex promotion
@Nucs Nucs merged commit 1c9d97e into master Feb 14, 2026
3 checks passed
@Nucs Nucs deleted the nep50 branch February 14, 2026 11:31
@Nucs Nucs added NumPy 2.x Compliance Aligns behavior with NumPy 2.x (NEPs, breaking changes) core Internal engine: Shape, Storage, TensorEngine, iterators labels Feb 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core Internal engine: Shape, Storage, TensorEngine, iterators NumPy 2.x Compliance Aligns behavior with NumPy 2.x (NEPs, breaking changes)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant