Skip to main content

Numeric Validation and Constraints

Numeric validation in pydantic-core is handled through specialized schema types for integers, floating-point numbers, decimals, and complex numbers. These schemas provide a consistent interface for mathematical constraints while accounting for the unique precision and representation requirements of each type.

Core Numeric Schemas

The codebase defines four primary numeric schemas in pydantic_core.core_schema:

  • IntSchema: Validates integer values.
  • FloatSchema: Validates floating-point numbers.
  • DecimalSchema: Validates decimal.Decimal objects, offering high precision and fixed-point arithmetic.
  • ComplexSchema: Validates complex numbers.

Shared Mathematical Constraints

IntSchema, FloatSchema, and DecimalSchema support a common set of mathematical constraints:

  • gt: Greater than
  • ge: Greater than or equal to
  • lt: Less than
  • le: Less than or equal to
    • multiple_of: The value must be a multiple of the provided number.

These constraints are applied during validation. For example, an integer schema with multiple constraints:

from pydantic_core import SchemaValidator, core_schema

# Schema requiring an even integer between 2 and 6 (inclusive)
schema = core_schema.int_schema(multiple_of=2, le=6, ge=2)
v = SchemaValidator(schema)

assert v.validate_python(4) == 4
assert v.validate_python('6') == 6

Integer Validation

IntSchema handles the conversion of various inputs into Python int objects.

Lax vs. Strict Mode

In lax mode (the default), pydantic-core attempts to coerce types like strings, booleans, and even "exact" floats into integers.

  • Exact Floats: A float like 42.0 is accepted because it has no fractional part.
  • Fractional Floats: A float like 42.5 is rejected with an int_from_float error.

In strict mode, only actual int instances are accepted.

# Lax mode (default)
v_lax = SchemaValidator(core_schema.int_schema())
assert v_lax.validate_python(42.0) == 42
assert v_lax.validate_python('42') == 42

# Strict mode
v_strict = SchemaValidator(core_schema.int_schema(strict=True))
# v_strict.validate_python(42.0) # Raises ValidationError (int_type)
# v_strict.validate_python('42') # Raises ValidationError (int_type)

Large Integers and Formatting

The validator supports arbitrarily large integers and strings containing underscores for readability (e.g., '1_000_000'). However, it enforces a maximum size for string-to-integer parsing (defaulting to 4300 characters) to prevent potential DoS attacks.

Floating-Point Validation

FloatSchema manages float validation and supports scientific notation and non-finite values.

Non-Finite Values

By default, FloatSchema allows NaN, inf, and -inf. This behavior is controlled by the allow_inf_nan flag, which defaults to True.

v = SchemaValidator(core_schema.float_schema(allow_inf_nan=True))
assert v.validate_python('NaN').is_nan() # Validates to float('nan')
assert v.validate_python('inf') == float('inf')

Precision and Coercion

In lax mode, FloatSchema accepts integers, strings, and booleans (where True becomes 1.0). In strict mode, it only accepts float and int instances (converting the int to a float).

Decimal Validation

DecimalSchema is designed for scenarios requiring exact decimal representation, such as financial calculations.

Precision Constraints

In addition to standard numeric constraints, DecimalSchema supports:

  • max_digits: Total number of allowed digits.
  • decimal_places: Maximum number of allowed decimal places.
from decimal import Decimal
from pydantic_core import SchemaValidator, core_schema as cs

v = SchemaValidator(cs.decimal_schema(max_digits=5, decimal_places=2))

# Valid: 5 total digits, 2 decimal places
assert v.validate_python('123.45') == Decimal('123.45')

# Invalid: 6 total digits
# v.validate_python('1234.56') # Raises ValidationError

Default Behavior Differences

Unlike FloatSchema, DecimalSchema sets allow_inf_nan to False by default. If you need to support non-finite decimals, you must explicitly enable it.

Strict Mode in JSON vs. Python

DecimalSchema exhibits unique behavior in strict mode:

  • Python: Rejects strings and floats; only Decimal instances are accepted.
  • JSON: Accepts numeric strings (e.g., "12.34") and numbers, as JSON has no native Decimal type.

Complex Numbers

ComplexSchema validates Python complex objects. Unlike the other numeric types, it does not support mathematical constraints like gt or le.

It supports parsing complex numbers from strings following Python's standard rules (e.g., '1+2j').

v = SchemaValidator(core_schema.complex_schema())
assert v.validate_python('1+2j') == complex(1, 2)
assert v.validate_python(5) == complex(5, 0)

In strict mode, ComplexSchema only accepts actual complex instances in Python, but it will still accept valid complex strings from JSON.

Handling Non-Finite Values in Unions

When using numeric types in a Union, the order and strictness of the branches are critical. For example, if a str schema precedes a Decimal schema, a numeric string like '1.23' will be validated as a string rather than being coerced to a Decimal.

# Prefers string because it matches first
s = SchemaValidator(core_schema.union_schema([
core_schema.str_schema(),
core_schema.decimal_schema()
]))
assert s.validate_python('1.23') == '1.23'

To ensure numeric coercion, place the numeric schema before the string schema or use strict mode where appropriate.