Monte Carlo Simulation in C++26 with std::philox_engine

by cppforquants
montecarlo C++

Monte Carlo simulation is one of the core tools of quantitative finance. It is used to price derivatives, estimate future exposures, generate market scenarios, and model problems that are difficult to solve analytically. What role plays std::philox_engine in this story?

At the heart of every Monte Carlo simulation is a random number generator. In C++26, the standard library introduces std::philox_engine, a counter-based random number engine designed with large-scale and parallel simulation in mind.A good place to start is the documentation of the library.

In this article, we look at what Philox is, why it is interesting for quantitative finance, and how to use it in a simple Monte Carlo option pricing example. We simulate one million possible terminal stock prices, calculate the payoff of a European call option under each scenario, and estimate the option price from the average discounted payoff.

The goal is not to build a production-grade pricing engine, but to show how a new C++26 feature can fit naturally into a familiar quantitative finance workflow.

1. What Is std::philox_engine?

std::philox_engine is a counter-based pseudo-random number generator introduced in C++26 as part of the <random> library. Unlike traditional generators such as std::mt19937, which evolve a relatively large internal state from one random number to the next, Philox generates values from a combination of a counter and a key. This difference is particularly useful in quantitative finance because counter-based generators are naturally suited to large-scale Monte Carlo simulations:

See the full video here:

Conceptually, the generator works like this:

counter + key
     ↓
 Philox rounds
     ↓
pseudo-random values

Instead of relying entirely on a long sequence of state transitions, Philox transforms a counter through several deterministic mixing rounds. Incrementing the counter produces the next block of pseudo-random numbers.

C++26 provides two predefined Philox engines:

std::philox4x32
std::philox4x64

The first generates blocks based on four 32-bit words, while the second uses four 64-bit words. Both predefined versions use 10 Philox rounds.

For example:

#include <iostream>
#include <random>

int main()
{
    std::philox4x32 rng{42};

    std::cout << rng() << '\n';
    std::cout << rng() << '\n';
    std::cout << rng() << '\n';
}

Here, 42 is used to seed the engine. Each call to rng() returns the next pseudo-random integer from the Philox sequence.

The main advantage of Philox is not simply that it generates random numbers. Its design makes independent parts of the random-number space much easier to access and distribute across different computations.

This makes it especially attractive for workloads such as:

  • Monte Carlo option pricing
  • exposure simulation
  • Value at Risk calculations
  • scenario generation
  • multi-threaded simulations
  • GPU-based quantitative workloads

The C++ standard library specifically describes Philox as suitable for Monte Carlo workloads requiring massively parallel random-number generation, and notes that its design is easy to vectorize and parallelize.

Another important property is reproducibility. Given the same seed and the same counter state, Philox produces the same sequence of pseudo-random values. This is extremely useful when debugging or reproducing quantitative simulations.

It is important, however, to distinguish pseudo-randomness from cryptographic randomness. std::philox_engine is designed for numerical simulation and is not a cryptographically secure random-number generator.

For quantitative developers, the key idea is therefore:

Philox is a random-number engine designed around counters rather than a large sequential state, making it particularly well suited to reproducible and highly parallel Monte Carlo simulation.

2. A Simple Monte Carlo Option Pricing Example

To see std::philox_engine in a quantitative finance context, we can use it to price a simple European call option with Monte Carlo simulation.

Assume:

  • spot price (S_0 = 100)
  • strike (K = 100)
  • risk-free rate (r = 5%)
  • volatility (\sigma = 20%)
  • maturity (T = 1) year
  • N simulated paths (e.g: 1,000,000)

Under the Black–Scholes model, the terminal stock price can be simulated as:

For each simulated terminal stock price STS_T, the payoff of a European call option is:

where KK is the strike price.

After simulating NN paths, we estimate the expected payoff by averaging across all simulations:

Finally, we discount the expected payoff back to today:

where C0C_0 is the estimated option price, rr is the risk-free rate, and TT is the time to maturity.

3. A C++ Implementation Using std::philox_engine?

Here is a suggestion of implementation:

#include <algorithm>
#include <cmath>
#include <iostream>
#include <random>

int main()
{
    constexpr int paths = 1'000'000;

    const double S0 = 100.0;
    const double K = 100.0;
    const double r = 0.05;
    const double sigma = 0.20;
    const double T = 1.0;

    std::philox4x32 rng{42};
    std::normal_distribution<double> normal(0.0, 1.0);

    double payoff_sum = 0.0;

    for (int i = 0; i < paths; ++i)
    {
        const double z = normal(rng);

        const double ST =
            S0 * std::exp(
                (r - 0.5 * sigma * sigma) * T +
                sigma * std::sqrt(T) * z
            );

        payoff_sum += std::max(ST - K, 0.0);
    }

    const double price =
        std::exp(-r * T) *
        payoff_sum / paths;

    std::cout << "Option price: "
              << price << '\n';
}

Here we create the C++26 predefined 32-bit Philox engine and seed it with 42. std::philox4x32 is one of the predefined specializations of std::philox_engine.

We then combine the engine with:

std::normal_distribution<double> normal(0.0, 1.0);

std::normal_distribution transforms the pseudo-random numbers produced by the engine into normally distributed values with mean (0) and standard deviation (1).

Each call to:

const double z = normal(rng);

therefore gives us one random shock (Z), which we use to generate one possible stock price at maturity.

3. Install C++26, Compile and Execute

If you didn’t install a C++26 compiler yet: it’s the moment! With macOS, a simple brew install will do:

➜ brew install gcc

Then, run a quick check:

➜  g++-16 --version
                                             
g++-16 (Homebrew GCC 16.2.0) 16.2.0
Copyright (C) 2026 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.


Yes, you can now compile C++26 code.

Once done, put our implementation above in a montecarlo.cpp, and run:

g++-16 -std=c++26 -O3 -Wall -Wextra montecarlo.cpp -o montecarlo


Then run the binary:

➜  ./montecarlo 
                                                   
Option price: 10.4644

So, what did we just do? A visual summary:

Once the Monte Carlo simulation has produced an average payoff at maturity, that value still represents money received in the future rather than money today. To convert it into a present fair value, we discount it using the risk-free rate which is the exponential term applied to the average payoff.

The key intuition is:

So going the other way:

where rr is the continuously compounded risk-free interest rate and TT is the time to maturity, expressed in years.

4. Why choosing std::philox_engine

std::philox_engine is an interesting addition to C++26 because it brings a modern counter-based random number generator directly into the standard library. Unlike traditional stateful generators, Philox derives its output from a counter and key, making different regions of the random sequence easy to address independently. This makes the design particularly suitable for large Monte Carlo workloads, where simulations can eventually be split across CPU threads, vector units or GPUs without requiring a single shared random-number-generator state.

std::philox_engine also combines a small internal state, a very long period, strong statistical properties and hardware-friendly integer operations. C++26 additionally exposes set_counter(), allowing an application to position the generator at a specific counter rather than sequentially advancing through the random stream. This is useful for constructing deterministic independent streams and for reproducing individual simulation paths.

Another advantage of standardization is interoperability. std::philox4x32 and std::philox4x64 behave like other C++ random-number engines and can therefore be passed directly to existing facilities such as std::normal_distribution. A Monte Carlo implementation can adopt Philox without redesigning the rest of its simulation pipeline.

In our European option example we use Philox single-threaded, so these parallelism benefits are not yet being exploited. The purpose is to introduce the C++26 engine in the simplest possible setting while using an RNG design that naturally scales toward much larger parallel Monte Carlo simulations.

You may also like