Skip to main content

Scalar Types

NumPy provides a rich set of scalar types that correspond to fixed-size memory representations of numbers and other data. These types are the building blocks of NumPy arrays.

Type Hierarchy

All NumPy scalar types inherit from numpy.generic. The hierarchy enables type checking and ensures consistent behavior:
  • numpy.generic - Base class for all scalar types
    • numpy.number - Base for all numeric types
      • numpy.integer - Base for all integer types
      • numpy.floating - Base for floating-point types
      • numpy.complexfloating - Base for complex types
    • numpy.flexible - Base for strings and void
    • numpy.bool_ - Boolean type
    • numpy.object_ - Python object type

Boolean Type

bool_

Boolean type (True or False), stored as a byte.
import numpy as np

b = np.bool_(True)
print(b)  # True
print(type(b))  # <class 'numpy.bool_'>

# Array of booleans
arr = np.array([True, False, True], dtype=np.bool_)
Character code: '?'
numpy.bool_ is not a subclass of Python’s int, unlike Python’s built-in bool. This differs from Python’s default behavior.

Integers

NumPy provides both signed and unsigned integers in various sizes.

Signed Integers

8-bit signed integer: -128 to 127
import numpy as np
x = np.int8(127)
print(x)  # 127
Character code: 'b' Aliases: numpy.byte
16-bit signed integer: -32,768 to 32,767
import numpy as np
x = np.int16(30000)
Character code: 'h' Aliases: numpy.short
32-bit signed integer: -2,147,483,648 to 2,147,483,647
import numpy as np
x = np.int32(1000000)
Character code: 'i' Aliases: numpy.intc (C int)
64-bit signed integer: -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
import numpy as np
x = np.int64(1000000000000)
Character code: 'l' or 'q' Aliases: numpy.long, numpy.int_ (default integer on 64-bit systems)

Unsigned Integers

8-bit unsigned integer: 0 to 255
import numpy as np
x = np.uint8(255)
Character code: 'B' Aliases: numpy.ubyte Common use: Image pixel values, byte data
16-bit unsigned integer: 0 to 65,535
import numpy as np
x = np.uint16(60000)
Character code: 'H' Aliases: numpy.ushort
32-bit unsigned integer: 0 to 4,294,967,295
import numpy as np
x = np.uint32(4000000000)
Character code: 'I' Aliases: numpy.uintc
64-bit unsigned integer: 0 to 18,446,744,073,709,551,615
import numpy as np
x = np.uint64(10000000000000000000)
Character code: 'L' or 'Q' Aliases: numpy.ulong, numpy.uint

Platform-Dependent Integers

  • intp: Integer used for indexing (same as C ssize_t, typically int64)
  • uintp: Unsigned integer for indexing (same as C size_t)
import numpy as np

print(np.dtype(np.intp).itemsize)  # 8 on 64-bit, 4 on 32-bit

Floating-Point Types

Floating-point types follow IEEE 754 standard.

float16 (half)

Half precision float: 16-bit (1 sign, 5 exponent, 10 mantissa)
import numpy as np

x = np.float16(3.14)
print(x)  # 3.14 (limited precision)
Character code: 'e' Range: ~±6.55e4 Precision: ~3-4 decimal digits Use case: ML inference, memory-constrained applications

float32 (single)

Single precision float: 32-bit (1 sign, 8 exponent, 23 mantissa)
import numpy as np

x = np.float32(3.14159265359)
print(x)  # 3.1415927 (limited precision)
Character code: 'f' Aliases: numpy.single Range: ~±3.4e38 Precision: ~7-8 decimal digits Use case: Graphics, ML training, scientific computing when memory matters

float64 (double)

Double precision float: 64-bit (1 sign, 11 exponent, 52 mantissa)
import numpy as np

x = np.float64(3.141592653589793)
print(x)  # 3.141592653589793
Character code: 'd' Aliases: numpy.double, numpy.float_ (default) Range: ~±1.8e308 Precision: ~15-16 decimal digits Use case: Default for most scientific computing

float96, float128 (longdouble)

Extended precision float (platform dependent)
import numpy as np

if hasattr(np, 'float128'):
    x = np.float128(3.14159265358979323846)
Character code: 'g' Aliases: numpy.longdouble Note: Size varies by platform (often 80-bit or 128-bit)

Complex Types

Complex numbers consist of two floating-point numbers (real and imaginary parts).

complex64 (csingle)

Complex number with 2x float32
import numpy as np

z = np.complex64(3 + 4j)
print(z.real)  # 3.0
print(z.imag)  # 4.0
Character code: 'F' Aliases: numpy.csingle Size: 64 bits (32 bits each for real and imaginary)

complex128 (cdouble)

Complex number with 2x float64
import numpy as np

z = np.complex128(3.14 + 2.71j)
print(abs(z))  # Magnitude
print(np.angle(z))  # Phase
Character code: 'D' Aliases: numpy.cdouble, numpy.complex_ (default) Size: 128 bits (64 bits each for real and imaginary)

complex192, complex256 (clongdouble)

Extended precision complex (platform dependent)
import numpy as np

if hasattr(np, 'complex256'):
    z = np.complex256(1 + 1j)
Character code: 'G' Aliases: numpy.clongdouble

String Types

str_ (unicode)

Unicode string (UCS-4 encoding)
import numpy as np

# Fixed-length unicode string
s = np.str_('Hello')
print(s)  # 'Hello'

# Array of strings
arr = np.array(['apple', 'banana', 'cherry'], dtype='U10')
print(arr.dtype)  # dtype('<U10')  # Up to 10 characters
Character code: 'U' Note: Size is specified in characters, not bytes

bytes_

Byte string (fixed-length)
import numpy as np

s = np.bytes_(b'Hello')
print(s)  # b'Hello'

# Array of byte strings
arr = np.array([b'cat', b'dog'], dtype='S3')
print(arr.dtype)  # dtype('S3')  # Up to 3 bytes
Character code: 'S' Note: Size is in bytes

Other Types

object_

Arbitrary Python object
import numpy as np

# Array can hold any Python objects
arr = np.array([1, 'hello', [1, 2, 3], {'key': 'value'}], dtype=object)
print(arr.dtype)  # dtype('O')
Character code: 'O' Warning: Operations are slower, no vectorization benefits

void

Raw data (unstructured)
import numpy as np

# 10-byte void type
dt = np.dtype('V10')
Character code: 'V' Use case: Raw byte buffers, custom structured types

datetime64

Date and time type
import numpy as np

# Various precisions
date = np.datetime64('2024-01-15')
datetime = np.datetime64('2024-01-15T12:30:00')
nanosec = np.datetime64('2024-01-15T12:30:00.123456789')

print(date)  # 2024-01-15
Character code: 'M' Units: Y, M, W, D, h, m, s, ms, us, ns, ps, fs, as

timedelta64

Time duration type
import numpy as np

# Time differences
days = np.timedelta64(7, 'D')
hours = np.timedelta64(24, 'h')

print(days + hours)  # 8 days
Character code: 'm' (lowercase) Units: Same as datetime64

Type Relationships

Type Hierarchy Checks

import numpy as np

# Check if type is integer
print(np.issubdtype(np.int32, np.integer))  # True

# Check if type is floating
print(np.issubdtype(np.float64, np.floating))  # True

# Check if type is signed integer
print(np.issubdtype(np.int32, np.signedinteger))  # True
print(np.issubdtype(np.uint32, np.signedinteger))  # False

Type Promotion

import numpy as np

# Result dtype follows promotion rules
result = np.int32(1) + np.float32(2.5)
print(result.dtype)  # dtype('float64')

result = np.int8(1) + np.int16(2)
print(result.dtype)  # dtype('int16')

Examples

Choosing Appropriate Type

import numpy as np

# Image data: uint8 (0-255)
image = np.zeros((100, 100, 3), dtype=np.uint8)

# Indices: intp
indices = np.arange(1000, dtype=np.intp)

# Scientific data: float64
temperatures = np.array([20.5, 21.3, 19.8], dtype=np.float64)

# Counts: uint32 or uint64
counts = np.zeros(1000, dtype=np.uint32)

Memory Efficiency

import numpy as np

# Large array of small integers
large_data = np.random.randint(0, 100, size=1000000, dtype=np.uint8)
print(f"Memory: {large_data.nbytes / 1e6:.2f} MB")

# vs default (int64)
large_data_default = np.random.randint(0, 100, size=1000000)
print(f"Memory: {large_data_default.nbytes / 1e6:.2f} MB")
# 8x more memory!

Type Checking in Functions

import numpy as np

def process_array(arr):
    if not np.issubdtype(arr.dtype, np.integer):
        raise TypeError("Array must contain integers")
    
    if np.issubdtype(arr.dtype, np.signedinteger):
        # Handle signed integers
        return arr * 2
    else:
        # Handle unsigned integers
        return arr // 2

Common Pitfalls

Integer Overflow
x = np.int8(127)
y = x + 1  # Wraps to -128!
Always check ranges when using fixed-size integers.
Float Precision
x = np.float32(1e10)
y = x + 1
print(x == y)  # True! Lost precision
Use float64 for calculations requiring precision.
Type Mixing
arr_int = np.array([1, 2, 3], dtype=np.int32)
arr_float = np.array([1.5, 2.5, 3.5], dtype=np.float32)
result = arr_int + arr_float  # Promotes to float64, not float32!

See Also

Build docs developers (and LLMs) love