diff --git a/.github/docs/DocumentationGenerator.php b/.github/docs/DocumentationGenerator.php
new file mode 100644
index 0000000..e3910e6
--- /dev/null
+++ b/.github/docs/DocumentationGenerator.php
@@ -0,0 +1,584 @@
+<?php
+
+define('TIME_START', microtime(true));
+
+require __DIR__.'/../../src/Number.php';
+
+/**
+ * @internal This file is used by the Number package to generate documentation for the package, using reflection and source code evaluation.
+ *           It is not intended to be used outside the package, and it's not designed to be pretty or efficient.
+ */
+
+use FriendsOfPhp\Number\Number;
+
+final class DocumentationGenerator
+{
+    private readonly ReadmeData $readme;
+    private readonly MethodExamples $examples;
+    private readonly FrontMatter $matter;
+    private readonly array $composerData;
+
+    private array $markdownSections = [];
+    private string $markdown;
+
+    public function __construct(FrontMatter $matter, MethodExamples $examples)
+    {
+        $this->examples = $examples;
+        $this->matter = $matter;
+    }
+
+    public function generate(): void
+    {
+        $this->loadAndParseReadmeData();
+        $this->loadAndParseComposerData();
+        $this->assembleDocument();
+        $this->compileDocument();
+    }
+
+    public function getMarkdown(): string
+    {
+        return "$this->matter\n$this->markdown";
+    }
+
+    private function loadAndParseReadmeData(): void
+    {
+        $this->readme = new ReadmeData();
+    }
+
+    private function loadAndParseComposerData(): void
+    {
+        $this->composerData = json_decode(file_get_contents(__DIR__.'/../../composer.json'), true);
+    }
+
+    private function assembleDocument(): void
+    {
+        $this->addBlock(
+            new MarkdownBlock(
+                new MarkdownHeading($this->readme->title . ' - by Friends of PHP', 1),
+                $this->readme->description
+            )
+        );
+
+        $this->addBlock(
+            new MarkdownBlock(
+                new MarkdownHeading('Installation', 2),
+                $this->generateInstallationMarkdown(),
+            )
+        );
+
+        $this->addBlock(
+            new MarkdownBlock(
+                new MarkdownHeading('Basic Usage', 2),
+                [
+                    'The Number package provides a single class, `Number`, which can be used to format numbers in a variety of ways. Here are some example, with the full reference below.',
+                    $this->readme->getBlock('basic-usage')->getContent(),
+                ]
+            )
+        );
+
+        $this->addBlock(
+            new MarkdownBlock(
+                new MarkdownHeading('Full Reference', 2),
+                $this->generateMethodDocumentation(),
+            )
+        );
+
+        $this->addBlock(
+            new MarkdownBlock(
+                new MarkdownHeading('License', 2),
+                $this->readme->license,
+            )
+        );
+
+        $this->addBlock(
+            new MarkdownBlock(
+                new MarkdownHeading('Attributions', 2),
+                $this->readme->attributions,
+            )
+        );
+
+        $this->addBlock(
+            new MarkdownBlock(
+                new MarkdownHeading('Contributing', 2),
+                $this->readme->contributing ?? 'Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.',
+            )
+        );
+    }
+
+    private function compileDocument(): void
+    {
+        $this->markdown = implode("\n\n", $this->markdownSections);
+    }
+
+    private function addBlock(MarkdownBlock $block): void
+    {
+        $this->markdownSections[] = $block;
+    }
+
+    private function generateInstallationMarkdown(): array
+    {
+        return [
+            'Install the package using Composer:',
+            new MarkdownCodeBlock("composer require {$this->composerData['name']}", 'bash'),
+        ];
+    }
+
+    private function generateMethodDocumentation(): string
+    {
+        return (new MethodDocumentationGenerator($this->examples))->generate();
+    }
+}
+
+/**
+ * Data object for the Readme
+ *
+ * @property-read string $title
+ * @property-read string $description
+ * @property-read string $license
+ * @property-read string $attributions
+ */
+final class ReadmeData
+{
+    private readonly string $contents;
+    private readonly array $lines;
+    private readonly array $blocks;
+    private array $data = [];
+
+    public function __construct()
+    {
+        $this->contents = file_get_contents(__DIR__.'/../../README.md');
+        $this->lines = explode("\n", $this->contents);
+        $this->parseReadme();
+        $this->parseData();
+    }
+
+    public function __get(string $name)
+    {
+        return $this->data[$name] ?? null;
+    }
+
+    public function getBlock(string $id): MarkdownBlock
+    {
+        return $this->blocks[$id];
+    }
+
+    private function parseReadme(): void
+    {
+        // Each block starts with a heading, and ends with the next heading or the end of the file.
+        $blocks = [];
+        $currentBlock = null;
+        foreach ($this->lines as $line) {
+            if (str_starts_with($line, '#')) {
+                if ($currentBlock) {
+                    $blocks[] = $currentBlock;
+                }
+                $currentBlock = new MarkdownBlock(
+                    new MarkdownHeading(
+                        trim($line, '# '),
+                        substr_count($line, '#')
+                    ),
+                    ''
+                );
+            } else {
+                $currentBlock->addLine($line);
+            }
+        }
+
+        // Add the last block
+        $blocks[] = $currentBlock;
+
+        // Iterate over blocks to set string identifiers
+        $parsedBlocks = [];
+        foreach ($blocks as $block) {
+            // kebab-case of title
+            $id = strtolower(str_replace(' ', '-', $block->getHeading()->getText()));
+            // If id is already in use, append a number
+            if (isset($parsedBlocks[$id])) {
+                $number = 1;
+                while (isset($parsedBlocks[$id.'-'.$number])) {
+                    $number++;
+                }
+                $id .= '-'.$number;
+            }
+            $parsedBlocks[$id] = $block;
+        }
+
+        $this->blocks = $parsedBlocks;
+    }
+
+    private function parseData(): void
+    {
+        $this->data['title'] = $this->blocks[array_key_first($this->blocks)]->getHeading()->getText();
+        $this->data['description'] = $this->blocks[array_key_first($this->blocks)]->getContent();
+        $this->data['license'] = $this->blocks['license']->getContent();
+        $this->data['attributions'] = $this->blocks['attributions']->getContent();
+    }
+}
+
+/** Represents a Markdown section */
+final class MarkdownBlock implements Stringable
+{
+    private MarkdownHeading $heading;
+    private string $content;
+
+    public function __construct(MarkdownHeading $heading, string|array $content)
+    {
+        $this->heading = $heading;
+        $this->content = trim(is_array($content) ? implode("\n\n", $content) : $content);
+    }
+
+    public function __toString(): string
+    {
+        return $this->heading."\n\n".$this->getContent();
+    }
+
+    public function addLine(string $line): void
+    {
+        $this->content .= $line."\n";
+    }
+
+    public function getHeading(): MarkdownHeading
+    {
+        return $this->heading;
+    }
+
+    public function getContent(): string
+    {
+        return trim($this->content);
+    }
+}
+
+/** Represents a Markdown heading */
+final class MarkdownHeading implements Stringable
+{
+    private string $text;
+    private int $level;
+
+    public function __construct(string $text, int $level)
+    {
+        $this->text = $text;
+        $this->level = $level;
+    }
+
+    public function __toString(): string
+    {
+        return str_repeat('#', $this->level).' '.$this->text;
+    }
+
+    public function getText(): string
+    {
+        return $this->text;
+    }
+}
+
+/** Represents a Markdown code block */
+final class MarkdownCodeBlock implements Stringable
+{
+    private string $code;
+    private string $language;
+
+    public function __construct(string|array $code, string $language = '')
+    {
+        $this->code = trim(is_array($code) ? implode("\n", $code) : $code);
+        $this->language = $language;
+    }
+
+    public function __toString(): string
+    {
+        return '```'.$this->language."\n".$this->code."\n```";
+    }
+}
+
+/** Generates method documentation for the Number class */
+final class MethodDocumentationGenerator
+{
+    private readonly ReflectionClass $reflectionClass;
+    private readonly MethodExamples $examples;
+    /** @var array<string, ReflectionMethod> */
+    private array $methodsToDocument;
+    /** @var array<string, MarkdownBlock> */
+    private array $methodDocumentation;
+
+    public function __construct(MethodExamples $examples)
+    {
+        $this->reflectionClass = new ReflectionClass(Number::class);
+        $this->examples = $examples;
+    }
+
+    public function generate(): string
+    {
+        $this->discoverMethodsToDocument();
+        $this->generateMethodsDocumentation();
+
+        return $this->compile();
+    }
+
+    private function discoverMethodsToDocument(): void
+    {
+        $this->methodsToDocument = [];
+        foreach ($this->reflectionClass->getMethods() as $method) {
+            if ($method->isPublic() && !$method->isConstructor() && !$method->isDestructor()) {
+                $this->methodsToDocument[$method->getName()] = $method;
+            }
+        }
+    }
+
+    private function generateMethodsDocumentation(): void
+    {
+        $this->methodDocumentation = [];
+        foreach ($this->methodsToDocument as $methodName => $method) {
+            $this->methodDocumentation[$methodName] = $this->generateMethodDocumentation($method);
+        }
+    }
+
+    private function generateMethodDocumentation(ReflectionMethod $method): MarkdownBlock
+    {
+        $phpDoc = new PHPDoc($method->getDocComment());
+        $examples = $this->examples->getExamplesForMethod($method->getName());
+
+        return new MarkdownBlock(
+            new MarkdownHeading("`Number::{$method->getName()}()`", 3),
+            [
+                $phpDoc->description,
+                new MarkdownCodeBlock($this->generateMethodSignature($method, $phpDoc), 'php'),
+                $examples ? new MarkdownBlock(
+                    new MarkdownHeading('Usage', 4),
+                    new MarkdownCodeBlock($examples, 'php')
+                ) : null,
+            ]
+        );
+    }
+
+    private function generateMethodSignature(ReflectionMethod $method, PHPDoc $phpDoc): string
+    {
+        return sprintf(
+            "Number::%s(%s): %s",
+            $method->getName(),
+            implode(', ', array_map(function (ReflectionParameter $parameter) use ($phpDoc): string {
+                return $this->generateParameterList($parameter, $phpDoc);
+            }, $method->getParameters())),
+            $phpDoc->returnType ?? $method->getReturnType() ?? 'mixed'
+        );
+    }
+
+    private function compile(): string
+    {
+        $markdown = [];
+        foreach ($this->methodDocumentation as $method) {
+            $markdown[] = $method;
+        }
+        return implode("\n\n", $markdown);
+    }
+
+    private function generateParameterList(ReflectionParameter $parameter, PHPDoc $phpDoc): string
+    {
+        $type = $parameter->getType();
+        if ($type) {
+            $type = method_exists($type, 'getTypes') ? implode('|', $type->getTypes()) : $type->getName();
+        }
+        $docParam = $phpDoc->params[$parameter->getName()] ?? null;
+        if ($docParam) {
+            $type = $docParam;
+        }
+
+        $addNullShorthand = ($parameter->isOptional() && $parameter->allowsNull()) && !str_contains($type, 'null');
+        $typeString = ($addNullShorthand ? '?' : '') . $type . ' ';
+
+        return $typeString . '$' . $parameter->getName();
+    }
+}
+
+/**
+ * Represents a PHPDoc comment
+ *
+ * @property-read string $comment
+ * @property-read string $description
+ * @property-read string|null $returnType
+ * @property-read array<string, string> $params
+ * @property-read array<string, string> $extraTags
+ */
+final class PHPDoc
+{
+    private string $comment;
+    private string $description;
+    private ?string $returnType = null;
+    private array $params = [];
+    private array $extraTags = [];
+
+    public function __construct(string $comment)
+    {
+        $this->comment = self::stripCommentDirectives($comment);
+        $this->parseTags();
+    }
+
+    private function parseTags(): void
+    {
+        $lines = explode("\n", $this->comment);
+        $description = '';
+        foreach ($lines as $line) {
+            if (str_starts_with($line, '@')) {
+                $parts = explode(' ', $line);
+                $tag = substr(array_shift($parts), 1);
+
+                if ($tag === 'return') {
+                    $this->returnType = array_shift($parts);
+                    continue;
+                }
+
+                if ($tag === 'param') {
+                    $paramName = trim($parts[1], '$');
+                    $paramType = $parts[0];
+
+                    $this->params[$paramName] = $paramType;
+
+                    continue;
+                }
+
+                $tagId = $tag;
+                if (isset($this->extraTags[$tagId])) {
+                    $count = 1;
+                    while (isset($this->extraTags[$tagId.'-'.$count])) {
+                        $count++;
+                    }
+                    $tagId .= '-'.$count;
+                }
+                $this->extraTags[$tagId] = implode(' ', $parts);
+            } else {
+                $description .= $line."\n";
+            }
+        }
+        if ($description) {
+            $this->description = trim($description);
+        }
+    }
+
+    public function __get(string $name): null|string|array
+    {
+        return $this->{$name} ?? $this->extraTags[$name] ?? null;
+    }
+
+    private static function stripCommentDirectives(string $comment): string
+    {
+        return trim(implode("\n", array_map(function (string $line): string {
+            return trim(str_replace(['*', '/'], '', $line));
+        }, explode("\n", $comment))));
+    }
+}
+
+/** Front matter container */
+class FrontMatter implements Stringable
+{
+    private array $data;
+
+    public function __construct(array $data)
+    {
+        $this->data = $data;
+    }
+
+    public function __toString(): string
+    {
+        return sprintf("---\n%s\n---\n", implode("\n", array_map(
+            fn (string $key, string $value): string => "$key: $value",
+            array_keys($this->data),
+            $this->data
+        )));
+    }
+}
+
+/** Parses the examples returned by the examples function to a more usable format */
+final class MethodExamples
+{
+    private array $examples;
+
+    /** @param array $examples Array of the return values */
+    public function __construct(array $examples)
+    {
+        $this->examples = $this->parseExamples($examples);
+    }
+
+    public function getExamplesForMethod(string $method): array
+    {
+        return array_filter($this->examples, function (Example $example) use ($method) {
+            return str_starts_with($example->source, "Number::$method");
+        });
+    }
+
+    private function parseExamples(array $input): array
+    {
+        $contents = explode("\n", file_get_contents(__FILE__));
+        $functionCallLine = debug_backtrace()[1]['line'] - count($input);
+        $examples = [];
+
+        foreach ($input as $index => $result) {
+            $source = trim($contents[$functionCallLine + $index], "\t\n\r\0\x0B, ");
+            $examples[] = new Example($source, $result);
+        }
+
+        return $examples;
+    }
+}
+
+/** Represents a method example */
+final class Example implements Stringable
+{
+    public readonly string $source;
+    public readonly string $result;
+
+    public function __construct($source, $result)
+    {
+        $this->source = $source;
+        $this->result = $result;
+    }
+
+    public function __toString(): string
+    {
+        return "echo $this->source; // $this->result";
+    }
+}
+
+// Helper functions
+
+function dd(...$data): void
+{
+    var_dump(...$data) && die;
+}
+
+function handleOutput(DocumentationGenerator $generator): void
+{
+    global $argv;
+    if (in_array('--output', $argv)) {
+        $outputIndex = array_search('--output', $argv);
+        $outputPath = $argv[$outputIndex + 1] ?? null;
+
+        file_put_contents($outputPath, $generator->getMarkdown());
+        echo "Wrote documentation to $outputPath\n";
+    } else {
+        echo "No output path specified, displaying raw contents\n\n---\n\n";
+        echo $generator->getMarkdown() . "\n\n";
+    }
+}
+
+function finishUp(DocumentationGenerator $generator): void
+{
+    echo sprintf("\033[32mAll done!\033[0m Generated in: %sms (SHA1: %s)\n", Number::format((microtime(true) - TIME_START) * 1000), sha1($generator->getMarkdown()));
+}
+
+// Run the generator
+
+$generator = new DocumentationGenerator(new FrontMatter([
+    'title' => 'Documentation',
+    'navigation.title' => 'Documentation',
+]), new MethodExamples([
+    Number::format(1234567.89),
+    Number::spell(1234),
+    Number::ordinal(42),
+    Number::percentage(0.75),
+    Number::currency(1234.56, 'EUR'),
+    Number::fileSize(1024),
+    Number::forHumans(1234567.89),
+]));
+
+$generator->generate();
+
+handleOutput($generator);
+finishUp($generator);
diff --git a/.github/docs/hyde.yml b/.github/docs/hyde.yml
new file mode 100644
index 0000000..82fe454
--- /dev/null
+++ b/.github/docs/hyde.yml
@@ -0,0 +1,12 @@
+hyde:
+    output_directories:
+        documentation-page: ''
+    load_app_styles_from_cdn: true
+    pretty_urls: true
+
+docs:
+    sidebar:
+        header: 'PHP Number Utility'
+        footer: '[GitHub Source Code](https://github.com/FriendsOfPHP/number)'
+    table_of_contents:
+        max_heading_level: 3
diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
new file mode 100644
index 0000000..06fd5bf
--- /dev/null
+++ b/.github/workflows/documentation.yml
@@ -0,0 +1,36 @@
+name: Build and Deploy Documentation
+
+on:
+  push:
+    branches:
+      - main
+
+jobs:
+  build-documentation:
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      pages: write
+      id-token: write
+    environment:
+      name: github-pages
+      url: https://friendsofphp.github.io/number
+
+    steps:
+      - uses: actions/checkout@v3
+
+      - name: Generate Documentation
+        run: |
+          mkdir hyde
+          mkdir hyde/_docs
+          cp ./.github/docs/hyde.yml hyde/hyde.yml
+          php ./.github/docs/DocumentationGenerator.php --output hyde/_docs/index.md
+
+      - name: Build and Deploy HydePHP Site
+        uses: hydephp/action@master
+        with:
+          deploy-to: pages
+          env-torchlight-token: ${{ secrets.TORCHLIGHT_TOKEN }}
+          env-site-name: "'PHP Number Utility - by Friends of PHP'"
+          directory: hyde
+          framework-version: dev-develop # Todo: Remove when next version is released
diff --git a/README.md b/README.md
index 0cdc654..d469c6c 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,10 @@ echo Number::currency(1234567.89, 'EUR'); // €1,234.56
 echo Number::fileSize(1234567.89); // 1 KB
 
 // Get a human-readable representation of a number
-echo Number::forHumans(1234567.89); // 1 million
+echo Number::format(1234567.89); // 1 million
+
+// Get the abbreviated form of a number
+echo Number::format(1234567.89); // 1M
 ```
 
 ### Advanced usage
diff --git a/src/Number.php b/src/Number.php
index 101c26a..fc6a7e7 100644
--- a/src/Number.php
+++ b/src/Number.php
@@ -120,7 +120,7 @@ public static function fileSize(int|float $bytes, int $precision = 0, ?int $maxP
     }
 
     /**
-     * Convert the number to its human-readable equivalent.
+     * Convert the number to its human-readable equivalent with abbreviated units.
      *
      * @param int|float $number
      * @param int $precision
@@ -133,7 +133,7 @@ public static function abbreviate(int|float $number, int $precision = 0, ?int $m
     }
 
     /**
-     * Convert the number to its human readable equivalent.
+     * Convert the number to its human-readable equivalent.
      *
      * @param  int  $number
      * @param  int  $precision
@@ -158,7 +158,7 @@ public static function forHumans(int|float $number, int $precision = 0, ?int $ma
     }
 
     /**
-     * Convert the number to its human readable equivalent.
+     * Convert the number to its human-readable equivalent.
      *
      * @param  int  $number
      * @param  int  $precision