> ## Documentation Index
> Fetch the complete documentation index at: https://numpyts.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Data Types (dtypes)

> All 14 supported dtypes, type promotion rules, BigInt handling, and complex numbers.

## Overview

numpy-ts supports 14 data types that mirror NumPy's numeric type system. Every NDArray has a single dtype that determines how its elements are stored in memory and how arithmetic behaves.

## Supported dtypes

| dtype        | TypedArray backing                        | Byte size | Range                                                   |
| ------------ | ----------------------------------------- | --------- | ------------------------------------------------------- |
| `float64`    | `Float64Array`                            | 8         | \~-1.8e308 to \~1.8e308 (64-bit IEEE 754)               |
| `float32`    | `Float32Array`                            | 4         | \~-3.4e38 to \~3.4e38 (32-bit IEEE 754)                 |
| `float16`    | `Float16Array` (fallback: `Float32Array`) | 2         | \~-65504 to \~65504 (16-bit IEEE 754)                   |
| `int64`      | `BigInt64Array`                           | 8         | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
| `int32`      | `Int32Array`                              | 4         | -2,147,483,648 to 2,147,483,647                         |
| `int16`      | `Int16Array`                              | 2         | -32,768 to 32,767                                       |
| `int8`       | `Int8Array`                               | 1         | -128 to 127                                             |
| `uint64`     | `BigUint64Array`                          | 8         | 0 to 18,446,744,073,709,551,615                         |
| `uint32`     | `Uint32Array`                             | 4         | 0 to 4,294,967,295                                      |
| `uint16`     | `Uint16Array`                             | 2         | 0 to 65,535                                             |
| `uint8`      | `Uint8Array`                              | 1         | 0 to 255                                                |
| `bool`       | `Uint8Array`                              | 1         | 0 or 1                                                  |
| `complex128` | `Float64Array` (interleaved)              | 16        | Two float64 values (real + imaginary)                   |
| `complex64`  | `Float32Array` (interleaved)              | 8         | Two float32 values (real + imaginary)                   |

## Default dtype

When you create an array without specifying a dtype, numpy-ts uses `float64`, matching NumPy:

```typescript theme={null}
import * as np from 'numpy-ts';

const a = np.array([1, 2, 3]);
a.dtype;  // 'float64'
```

## Creating arrays with specific dtypes

Pass the `dtype` option to any creation function:

```typescript theme={null}
const a = np.array([1, 2, 3], { dtype: 'int32' });
const b = np.zeros([3, 3], 'float32');
const c = np.ones([2, 4], 'uint8');
const d = np.arange(0, 10, 1, 'int16');
const e = np.linspace(0, 1, 100, { dtype: 'float32' });
```

## BigInt for int64 and uint64

JavaScript `number` only has 53 bits of integer precision. For full 64-bit integer support, numpy-ts uses `BigInt64Array` and `BigUint64Array` under the hood. This means `int64` and `uint64` arrays exchange values as `bigint`, not `number`.

<Warning>
  When working with `int64` or `uint64` arrays, `get()` returns `bigint` and `set()` expects `bigint`. Mixing `number` and `bigint` in JavaScript throws a `TypeError`, so be explicit about conversions.
</Warning>

```typescript theme={null}
const a = np.array([1, 2, 3], { dtype: 'int64' });

// Getting values returns bigint
const val = a.get([0]);  // 1n (bigint)

// Setting values requires bigint
a.set([0], 100n);

// toArray() returns bigints
a.toArray();  // [100n, 2n, 3n]

// Converting between number and bigint
const num = Number(val);    // bigint -> number
const big = BigInt(42);     // number -> bigint
```

## Complex numbers

numpy-ts provides a `Complex` class for complex number support, with two complex dtypes:

* **`complex128`** -- each element is a pair of `float64` values (real, imaginary)
* **`complex64`** -- each element is a pair of `float32` values (real, imaginary)

```typescript theme={null}
import { array, Complex } from 'numpy-ts';

// Create a complex array
const z = array([new Complex(1, 2), new Complex(3, 4)], { dtype: 'complex128' });
z.dtype;  // 'complex128'

// Access real and imaginary parts
const elem = z.get([0]);  // Complex { re: 1, im: 2 }
elem.re;  // 1
elem.im;  // 2

// Extract real/imaginary arrays
import { real, imag } from 'numpy-ts';
real(z).toArray();  // [1, 3]
imag(z).toArray();  // [2, 4]
```

The `Complex` class supports arithmetic:

```typescript theme={null}
const a = new Complex(1, 2);
const b = new Complex(3, -1);

a.add(b);    // Complex { re: 4, im: 1 }
a.mul(b);    // Complex { re: 5, im: 5 }
a.abs();     // 2.236...  (magnitude)
a.conj();    // Complex { re: 1, im: -2 }
a.toString(); // "(1+2j)"
```

## Float16 support

numpy-ts includes a `float16` dtype with automatic fallback for broad runtime compatibility.

* Uses native `Float16Array` on modern runtimes (Node 23+, Chrome 127+, Firefox 129+, Safari 18.2+).
* Falls back to `Float32Array` on older runtimes — `float16` always works, no runtime errors.
* Use the `hasFloat16` export to check for native support at runtime.

```typescript theme={null}
import * as np from 'numpy-ts';
import { hasFloat16 } from 'numpy-ts';

const a = np.array([1.5, 2.0, 3.25], { dtype: 'float16' });
a.dtype;       // 'float16'
a.itemsize;    // 2

if (hasFloat16) {
  console.log('Native Float16Array — exact NumPy precision');
} else {
  console.log('Float32Array fallback — float32 precision');
}
```

<Tip>
  If you need exact NumPy float16 parity, target Node 23+ or use `hasFloat16` to guard precision-sensitive code paths.
</Tip>

## Type promotion hierarchy

When you combine arrays with different dtypes in an operation, numpy-ts promotes to a common type that can represent both without data loss. The promotion follows NumPy's rules:

```
complex128 > complex64 > float64 > float32 > float16 > int64 > int32 > int16 > int8
                                                        uint64 > uint32 > uint16 > uint8 > bool
```

Some key promotion rules:

| Operation              | Result dtype | Reason                                       |
| ---------------------- | ------------ | -------------------------------------------- |
| `float64 + int32`      | `float64`    | float always wins                            |
| `float32 + int8`       | `float32`    | float32 can hold int8 values                 |
| `float16 + float32`    | `float32`    | wider float wins                             |
| `float16 + int8`       | `float16`    | float16 can hold int8 values                 |
| `float16 + int16`      | `float32`    | float16 lacks precision for all int16 values |
| `float32 + int32`      | `float64`    | float32 lacks precision for all int32 values |
| `int32 + uint32`       | `int64`      | need signed type larger than both            |
| `int64 + uint64`       | `float64`    | no integer type can hold both ranges         |
| `bool + int32`         | `int32`      | bool promotes to the other type              |
| `complex128 + float64` | `complex128` | complex always wins                          |
| `complex64 + float64`  | `complex128` | float64 requires complex128 precision        |

```typescript theme={null}
const a = np.array([1, 2], { dtype: 'float32' });
const b = np.array([3, 4], { dtype: 'int32' });

const c = np.add(a, b);
c.dtype;  // 'float64' -- promoted for precision safety
```

## Integer overflow and wrapping

Integer dtypes wrap on overflow, just like NumPy and C integer types. There is no error or automatic promotion:

```typescript theme={null}
const a = np.array([127], { dtype: 'int8' });
const b = np.add(a, np.array([1], { dtype: 'int8' }));
b.toArray();  // [-128]  -- wraps around

const c = np.array([255], { dtype: 'uint8' });
const d = np.add(c, np.array([1], { dtype: 'uint8' }));
d.toArray();  // [0]  -- wraps around
```

<Tip>
  If you need overflow protection, use a wider dtype (`int16` instead of `int8`) or promote to `float64` first.
</Tip>

## Converting dtypes with `astype()`

Use `astype()` to create a new array with a different dtype:

```typescript theme={null}
const a = np.array([1.7, 2.3, 3.9], { dtype: 'float64' });

// float -> int truncates (no rounding)
const b = a.astype('int32');
b.toArray();  // [1, 2, 3]

// int -> float
const c = np.array([1, 2, 3], { dtype: 'int32' });
c.astype('float32').dtype;  // 'float32'

// Anything -> bool (0 = false, nonzero = true)
const d = np.array([0, 1, -5, 0.0, 3.14]);
d.astype('bool').toArray();  // [0, 1, 1, 0, 1]
```

By default, `astype()` always returns a copy. Pass `copy: false` to return the same array when the dtype already matches:

```typescript theme={null}
const a = np.array([1, 2, 3], { dtype: 'float64' });

const b = a.astype('float64', false);
b === a;  // true  -- same object, no copy

const c = a.astype('float64');
c === a;  // false -- new copy
```

### Cast semantics: float → integer

For in-range finite floats, `astype()` truncates toward zero (matching NumPy). For out-of-range floats, the behavior depends on the target width:

| Source value                 | Target   | Result       | Rule                                       |
| ---------------------------- | -------- | ------------ | ------------------------------------------ |
| `1.7`                        | `int32`  | `1`          | truncate toward zero                       |
| `-1.7`                       | `int32`  | `-1`         | truncate toward zero                       |
| `2**31` (out of int32 range) | `int32`  | `2147483647` | saturate at target bound                   |
| `2**31`                      | `int16`  | `-1`         | saturate to int32 first, then bit-truncate |
| `2**31`                      | `uint8`  | `255`        | saturate to int32 first, then bit-truncate |
| `-2**31 - 1`                 | `uint16` | `0`          | saturate to int32 first, then bit-truncate |

The two-step rule for narrow targets (8/16-bit) matches NumPy 2.x: saturate to int32 range, then bit-truncate via the target's `ToIntN`. Earlier numpy-ts versions saturated against the final target width directly and could return `0` where NumPy returns `-1` / `255` / `65535`.

### Cast semantics: int64 / uint64 → narrower integer

Casting a 64-bit integer to a narrower width preserves the **low N bits** of the source, matching NumPy:

```typescript theme={null}
const a = np.array([0n], { dtype: 'uint64' });
a.set([0], 18446744073709551615n);     // u64 max = 2^64 - 1

a.astype('int32').get([0]);   // -1 (low 32 bits)
a.astype('int16').get([0]);   // -1 (low 16 bits)
a.astype('uint8').get([0]);   // 255 (low 8 bits)
```

This requires care internally — naive `Number(bigint)` rounds 2^63-class values to a multiple of 2^11 before any truncation can happen. numpy-ts masks in BigInt-space (`v & 0xFFFFFFFFn`) before converting, so the low bits survive into the TypedArray store.

### Caveat: out-of-range float → integer is platform-dependent in NumPy

NumPy 2.x routes float-to-integer conversion through the CPU's native fp-to-int intrinsic, and **the two major intrinsics disagree**:

* x86 `cvttsd2si` wraps modularly and returns `INT_MIN` for invalid (NaN, Inf, out-of-range).
* arm64 `fcvtzs` saturates and returns `0` for NaN.

The same NumPy version produces different results on different CPUs for the same input. This covers three pattern classes:

1. `NaN` / `±Inf` → any integer.
2. Negative float → unsigned integer (e.g. `-1.7` → `uint32`: arm64 gives `0`, x86 gives `4294967295`).
3. Finite float whose truncated value falls outside the target's range (e.g. `2**31` → `int32`: arm64 saturates to `2147483647`, x86 wraps to `-2147483648`).

numpy-ts picks the **arm64 convention** as a stable, predictable rule across all runtimes:

| Source              | Target      | numpy-ts result                   |
| ------------------- | ----------- | --------------------------------- |
| `NaN`               | any integer | `0`                               |
| `+Inf`              | signed      | target max (e.g. `127` for int8)  |
| `+Inf`              | unsigned    | target max (e.g. `255` for uint8) |
| `-Inf`              | signed      | target min                        |
| `-Inf`              | unsigned    | `0`                               |
| negative finite     | unsigned    | `0`                               |
| out-of-range finite | signed      | saturated to target min/max       |

In-range finite casts (`1.7 → int32`, etc.) match every platform.

<Tip>
  If you cast pathological float values to an integer dtype in code compared against NumPy, expect divergence on x86 NumPy. Filter, clamp, or replace these values explicitly before `astype()` if cross-platform parity matters.
</Tip>

## Special cases

### Comparisons always return `bool`

Regardless of input dtypes, comparison operations always produce `bool` arrays:

```typescript theme={null}
const a = np.array([1.0, 2.0, 3.0]);      // float64
const b = np.array([2.0, 2.0, 2.0]);      // float64

const result = np.greater(a, b);
result.dtype;     // 'bool'
result.toArray(); // [0, 0, 1]  (0 = false, 1 = true)
```

### `mean()` promotes integers to `float64`

Just like NumPy, `mean()` converts integer arrays to `float64` to avoid truncation:

```typescript theme={null}
const a = np.array([1, 2, 3, 4], { dtype: 'int32' });
const m = np.mean(a);  // 2.5 (float64, not truncated to 2)
```

### Boolean arrays

Boolean arrays store `0` (false) and `1` (true) as `uint8` values. They participate in arithmetic as integers:

```typescript theme={null}
const mask = np.array([true, false, true, false], { dtype: 'bool' });
mask.toArray();  // [1, 0, 1, 0]

// Sum counts the number of true values
np.sum(mask);  // 2
```

## Next steps

<CardGroup cols={2}>
  <Card title="Array Basics" icon="cube" href=".//array-basics">
    NDArray properties, creation, and conversion.
  </Card>

  <Card title="Broadcasting" icon="arrows-up-down-left-right" href=".//broadcasting">
    How arrays with different shapes combine in operations.
  </Card>
</CardGroup>
