Open-access mathematical research insights
About Contact
Home / Free Stuff

Ulam Spirals (C++)

Visual exploration tool for prime number patterns

Ulam spirals are a favourite topic because I saw them myself by accident when I first ran this code across the number line. It is super cool and was the thing that made me want to make this website.

Ulam Spiral visualization showing 100,000 primes

This version is not particularly dense plotting only 100,000 primes but you can still see the diagonal white lines crossing the page. The more primes the more obvious the pattern (below 1,000,000). You hit a pixelation limit anyway but it just is obvious that there is a diagonal pattern which seems impossible given that primes are supposedly occurring at intervals that aren't known.

Ulam Spiral visualization showing denser prime pattern

The code is perhaps the best code on the site just because it renders something that I believe is significant. Ulam Spirals are an underrated secret sauce.

C++ Implementation

This program uses SDL2 to render the Ulam spiral visualization. You'll need to have SDL2 and SDL2_image installed, along with a primes.h header file containing your prime number data.

C++ (SDL2)
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <iostream>
#include <vector>
#include <cmath>
#include "primes.h"

const int SCREEN_WIDTH = 800;
const int SCREEN_HEIGHT = 600;

// Direction vectors for spiral movement
const int dx[] = {1, 0, -1, 0};  // right, up, left, down
const int dy[] = {0, -1, 0, 1};

int main(int argc, char* args[]) {
    // Initialize SDL
    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        std::cerr << "SDL could not initialize! SDL_Error: " << SDL_GetError() << std::endl;
        return 1;
    }

    // Create window
    SDL_Window* window = SDL_CreateWindow("Ulam Spiral",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);

    if (window == nullptr) {
        std::cerr << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl;
        return 1;
    }

    // Create renderer
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    // Create texture for rendering
    SDL_Texture* texture = SDL_CreateTexture(renderer,
        SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET,
        SCREEN_WIDTH, SCREEN_HEIGHT);

    SDL_SetRenderTarget(renderer, texture);

    // Fill with white background
    SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
    SDL_RenderClear(renderer);

    // Set color for prime points (red)
    SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);

    // Start from center
    int x = SCREEN_WIDTH / 2;
    int y = SCREEN_HEIGHT / 2;
    int direction = 0;
    int steps_in_direction = 1;
    int steps_taken = 0;
    int turns = 0;

    // Plot primes in spiral pattern
    for (int n = 1; n <= 1000000 && x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT; n++) {
        // Check if n is prime
        if (isPrime(n)) {
            SDL_RenderDrawPoint(renderer, x, y);
        }

        // Move in current direction
        x += dx[direction];
        y += dy[direction];
        steps_taken++;

        // Check if we need to turn
        if (steps_taken == steps_in_direction) {
            steps_taken = 0;
            direction = (direction + 1) % 4;
            turns++;

            // Increase step count every two turns
            if (turns % 2 == 0) {
                steps_in_direction++;
            }
        }
    }

    // Draw grid overlay (optional)
    SDL_SetRenderDrawColor(renderer, 200, 200, 200, 128);
    for (int i = 0; i < SCREEN_WIDTH; i += 10) {
        for (int j = 0; j < SCREEN_HEIGHT; j += 10) {
            SDL_RenderDrawPoint(renderer, i, j);
        }
    }

    // Reset render target and display
    SDL_SetRenderTarget(renderer, nullptr);
    SDL_RenderCopy(renderer, texture, nullptr, nullptr);
    SDL_RenderPresent(renderer);

    // Save to PNG
    SDL_Surface* surface = SDL_CreateRGBSurface(0, SCREEN_WIDTH, SCREEN_HEIGHT, 32,
        0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000);
    SDL_RenderReadPixels(renderer, nullptr, SDL_PIXELFORMAT_ARGB8888,
        surface->pixels, surface->pitch);
    IMG_SavePNG(surface, "ulam_spiral.png");
    SDL_FreeSurface(surface);

    std::cout << "Ulam spiral saved to ulam_spiral.png" << std::endl;

    // Event loop
    bool quit = false;
    SDL_Event e;
    while (!quit) {
        while (SDL_PollEvent(&e) != 0) {
            if (e.type == SDL_QUIT) {
                quit = true;
            }
        }
    }

    // Cleanup
    SDL_DestroyTexture(texture);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}

Compiling

To compile this code, you'll need SDL2 and SDL2_image installed:

Terminal
# macOS (with Homebrew)
brew install sdl2 sdl2_image

# Compile
g++ -std=c++17 ulam_spiral.cpp -o ulam_spiral \
    -I/usr/local/include -L/usr/local/lib \
    -lSDL2 -lSDL2_image

# Run
./ulam_spiral

Stay Updated

Get weekly digests of new research insights delivered to your inbox.