Repository: SethMMorton/fastnumbers (language: python)

Issue:
Re-write using C++ and pybind11
While attempting #37 I ran into some troubles with how cumbersome changing things was. Maintenance has become a problem. This is mostly because the code is written in pure C.

When `fastnumbers` was started, the landscape of packaging and distributing extensions was *much* poorer than it is today. There were no wheels. Extensions basically had to be compiled by the user when installed every time. I wanted to minimize potential troubles for the user so I chose to write in pure C using the raw Python API. C compilers are more likely to be installed, and compilation typically takes less time than C++. I had considered Cython, but at the time there was no way to define build-time dependencies so users would face problems if they had not pre-installed it on their machine before installing `fastnumbers`.

Today, things look much better. With the advent of wheels, it basically makes no difference what language or framework I choose. C++ is far more expressive than C, and with templates and OOP I can reduce the maintenance burden. Doing this will make it easier to address problems going forward.


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml
index 282bfdcb..92c6e5d3 100644
--- a/.github/workflows/code-quality.yml
+++ b/.github/workflows/code-quality.yml
@@ -20,15 +20,14 @@ jobs:
       - name: Set up Python
         uses: actions/setup-python@v2
         with:
-          python-version: '3.6'
+          python-version: 3.8
 
       - name: Install deps
         run: |
-          sudo apt-get install astyle
-          pip install black
+          pip install black clang-format
 
       - name: Run formatting checks
-        run: python dev/check-formatting.py
+        run: python dev/formatting.py --check
 
   static-analysis:
     name: Static Analysis
@@ -40,7 +39,7 @@ jobs:
       - name: Set up Python
         uses: actions/setup-python@v2
         with:
-          python-version: '3.6'
+          python-version: 3.8
 
       - name: Install Flake8
         run: pip install flake8 flake8-import-order flake8-bugbear pep8-naming
@@ -58,7 +57,7 @@ jobs:
       - name: Set up Python
         uses: actions/setup-python@v2
         with:
-          python-version: '3.6'
+          python-version: 3.8
 
       - name: Install MyPy
         run: pip install mypy hypothesis pytest typing_extensions
@@ -78,7 +77,7 @@ jobs:
       - name: Set up Python
         uses: actions/setup-python@v2
         with:
-          python-version: '3.6'
+          python-version: 3.8
 
       - name: Install Validators
         run: pip install twine check-manifest
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c87ee7c5..f64f1912 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
 Unreleased
 ---
 
+### Changed
+- Complete rewrite into C++ (Issues [#45](https://github.com/SethMMorton/fastnumbers/issues/45)
+  and [#56](https://github.com/SethMMorton/fastnumbers/issues/56))
+- Removed support for Python 3.6
+
 [3.2.1] - 2021-11-02
 ---
 
diff --git a/MANIFEST.in b/MANIFEST.in
index c1708ed5..40888a52 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -8,6 +8,7 @@ include setup.py
 include setup.cfg
 include tox.ini
 include *.pyi
+include pyproject.toml
 graft src
 graft include
 graft tests
diff --git a/README.rst b/README.rst
index 6379ad0d..7243bc14 100644
--- a/README.rst
+++ b/README.rst
@@ -44,7 +44,7 @@ of decreasing importance as to why the module was created):
        - **PLEASE** read the quick start for these functions to fully
          understand the caveats before using them.
 
-**NOTICE**: As of ``fastnumbers`` version 3.0.0, only Python >= 3.5 is
+**NOTICE**: As of ``fastnumbers`` version X.Y.Z, only Python >= 3.7 is
 supported.
 
 Quick Start
@@ -417,18 +417,17 @@ Please note that ``fastnumbers`` is NOT set-up to support
 
 The recommended way to run tests is with
 `tox <https://tox.readthedocs.io/en/latest/>`_.
-Suppose you want to run tests for Python 3.6 - you can run tests by simply
+Suppose you want to run tests for Python 3.8 - you can run tests by simply
 executing the following:
 
 .. code-block:: sh
 
-    $ tox -e py36
+    $ tox -e py38
 
 ``tox`` will create virtual a virtual environment for your tests and install
 all the needed testing requirements for you.
 
-If you want to run testing on all of Python 3.5, 3.6, 3.7, and 3.8 you can
-simply execute
+If you want to run testing on all supported Python versions you can simply execute
 
 .. code-block:: sh
 
diff --git a/dev/astyle.cfg b/dev/astyle.cfg
deleted file mode 100644
index 21ce7331..00000000
--- a/dev/astyle.cfg
+++ /dev/null
@@ -1,20 +0,0 @@
-# PEP7 formatting style.
---break-closing-braces
---break-one-line-headers
---attach-closing-while
---add-braces
---break-return-type
---indent=spaces=4
---attach-extern-c
---max-code-length=79
---max-continuation-indent=70
---break-after-logical
---mode=c
-
-# Personal preferences.
---indent-preproc-define
---pad-comma
---pad-header
---pad-oper
---unpad-paren
---align-pointer=name
diff --git a/dev/check-formatting.py b/dev/check-formatting.py
deleted file mode 100755
index f98d4c6e..00000000
--- a/dev/check-formatting.py
+++ /dev/null
@@ -1,61 +0,0 @@
-#! /usr/bin/env python
-
-"""
-Cross-platform checking if code is appropriately formatted.
-INTENDED TO BE CALLED FROM PROJECT ROOT, NOT FROM dev/!
-"""
-
-import shlex
-import subprocess
-import sys
-
-# Check that python code is formatted
-black = ["black", "--quiet", "--check", "--diff", "."]
-print(*map(shlex.quote, black))
-black_ret = subprocess.run(["black", "--quiet", "--check", "--diff", "."])
-
-# Check that C code is formatted
-astyle = [
-    "astyle",
-    "--recursive",
-    "--options=dev/astyle.cfg",
-    "--suffix=none",
-    "--dry-run",
-]
-astyle_c = astyle + ["src/*.c"]
-print(*map(shlex.quote, astyle_c))
-astyle_c_ret = subprocess.run(
-    astyle_c, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True
-)
-print(astyle_c_ret.stdout)
-
-# Check that headers are formatted
-astyle_h = astyle + [
-    "--exclude=docstrings.h",
-    "--exclude=fn_bool.h",
-    "--exclude=options.h",
-    "--exclude=pstdint.h",
-    "include/fastnumbers/*.h",
-]
-print(*map(shlex.quote, astyle_h))
-astyle_h_ret = subprocess.run(
-    astyle_h, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True
-)
-print(astyle_h_ret.stdout)
-
-# Did any C files get formatted?
-any_c_formatting = any(
-    line.startswith("Formatted") for line in astyle_c_ret.stdout.splitlines()
-)
-any_h_formatting = any(
-    line.startswith("Formatted") for line in astyle_h_ret.stdout.splitlines()
-)
-
-# "Roll up" what happened into a single exit code.
-all_return_zero = all(
-    ret.returncode == 0 for ret in [black_ret, astyle_c_ret, astyle_h_ret]
-)
-if any_c_formatting or not all_return_zero:
-    sys.exit("Not all files are formatted correctly. Run 'tox -e format'.")
-else:
-    sys.exit(0)
diff --git a/dev/clang-format.cfg b/dev/clang-format.cfg
new file mode 100644
index 00000000..0f166da5
--- /dev/null
+++ b/dev/clang-format.cfg
@@ -0,0 +1,25 @@
+BasedOnStyle: WebKit
+ColumnLimit: 89
+AlignAfterOpenBracket: BlockIndent
+AllowShortBlocksOnASingleLine: Empty
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortLambdasOnASingleLine: Empty
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: false
+BinPackParameters: false
+BreakBeforeBraces: Custom
+BraceWrapping:
+  AfterCaseLabel: false
+  AfterClass: false
+  AfterControlStatement: false
+  AfterEnum: false
+  AfterFunction: true
+  AfterNamespace: false
+  AfterUnion: false
+  BeforeCatch: false
+  BeforeElse: false
+  BeforeLambdaBody: false
+  BeforeWhile: false
+  SplitEmptyFunction: false
+  SplitEmptyRecord: false
+  SplitEmptyNamespace: false
diff --git a/dev/formatting.py b/dev/formatting.py
new file mode 100755
index 00000000..569570e0
--- /dev/null
+++ b/dev/formatting.py
@@ -0,0 +1,57 @@
+#! /usr/bin/env python
+
+"""
+Cross-platform checking if code is appropriately formatted.
+INTENDED TO BE CALLED FROM PROJECT ROOT, NOT FROM dev/!
+"""
+
+import glob
+import shlex
+import subprocess
+import sys
+
+check = "--check" in sys.argv
+
+# Check that python code is formatted
+black = ["black"]
+if check:
+    black.extend(["--quiet", "--check", "--diff"])
+black.append(".")
+print(*map(shlex.quote, black))
+black_ret = subprocess.run(black)
+
+# Check that C++ code is formatted
+clang_format = [
+    "clang-format",
+    "--style=file:dev/clang-format.cfg",
+    "--dry-run" if check else "-i",
+]
+cpp = glob.glob("src/cpp/*.cpp")
+hpp = glob.glob("include/fastnumbers/*.hpp")
+hpp += glob.glob("include/fastnumbers/parser/*.hpp")
+
+clang_format = clang_format + cpp + hpp
+print(*map(shlex.quote, clang_format))
+clang_format_ret = subprocess.run(
+    clang_format,
+    stdout=subprocess.PIPE,
+    stderr=subprocess.STDOUT,
+    universal_newlines=True,
+)
+print(clang_format_ret.stdout)
+
+# Stop here if not checking
+if not check:
+    sys.exit(0)
+
+# Did any C++ files get formatted?
+any_cpp_formatting = any(
+    "clang-format-violations" in line for line in clang_format_ret.stdout.splitlines()
+)
+
+# "Roll up" what happened into a single exit code.
+all_return_zero = all(ret.returncode == 0 for ret in [black_ret, clang_format_ret])
+if any_cpp_formatting or not all_return_zero:
+    sys.exit("Not all files are formatted correctly. Run 'tox -e format'.")
+else:
+    sys.exit(0)
diff --git a/include/fastnumbers/c_str_parsing.hpp b/include/fastnumbers/c_str_parsing.hpp
new file mode 100644
index 00000000..5e45f40c
--- /dev/null
+++ b/include/fastnumbers/c_str_parsing.hpp
@@ -0,0 +1,267 @@
+#pragma once
+
+#include <cctype>
+#include <cstring>
+#include <limits>
+
+/// Largest allowed value for an ASCII character
+constexpr std::size_t ASCII_MAX = 127;
+
+/**
+ * \brief Convert a string to a long type
+ *
+ * Assumes no sign or whitespace. No overflow checking is performed.
+ *
+ * \param str The string to parse, assumed to be non-NULL
+ * \param end The end of the string being checked
+ * \param error Flag to indicate if there was a parsing error
+ */
+long parse_int(const char* str, const char* end, bool& error);
+
+/**
+ * \brief Convert a string to a double type
+ *
+ * Assumes no sign or whitespace. No overflow checking is performed.
+ *
+ * \param str The string to parse, assumed to be non-NULL
+ * \param end The end of the string being checked
+ * \param error Flag to indicate if there was a parsing error
+ */
+double parse_float(const char* str, const char* end, bool& error);
+
+/**
+ * \brief Check if a string could be converted to an integer
+ *
+ * Assumes no sign or whitespace.
+ *
+ * \param str The string to parse, assumed to be non-NULL
+ * \param end The end of the string being checked
+ * \param base The base to assume when checking the integer
+ */
+bool string_contains_int(const char* str, const char* end, int base);
+
+/**
+ * \brief Check if a string could be converted to a float
+ *
+ * Assumes no sign or whitespace.
+ *
+ * \param str The string to parse, assumed to be non-NULL
+ * \param end The end of the string being checked
+ */
+bool string_contains_float(const char* str, const char* end);
+
+/**
+ * \brief Check if a string could be converted to a float that
+ *        could be losslessly converted to an integer
+ *
+ * Assumes no sign or whitespace.
+ *
+ * \param str The string to parse, assumed to be non-NULL
+ * \param end The end of the string being checked
+ */
+bool string_contains_intlike_float(const char* str, const char* end);
+
+/**
+ * \brief Remove underscores in a numeric-representing string
+ *
+ * Assumes the input can ba modified.
+ *
+ * In a generic number, valid underscores are between two digits.
+ * A "based" number is necessarily an int, and in those cases the
+ * definition of a valid underscore is a bit less well-defined.
+ *
+ * \param str The string from which to remove underscores
+ * \param end Reference to the end of the string - after processing will
+ *            point to the new end of the string
+ * \param based Whether or not the string contains a non-base-10 integer
+ */
+void remove_valid_underscores(char* str, const char*& end, const bool based);
+
+/**
+ * \brief Maximum number of digits we allow in an integer for conversion
+ *
+ * The number of digits in an int that fastnumbers is willing
+ * to attempt to convert itself. This is entirely based on the size
+ * of a this compiler's long, since the long is what Python uses to
+ * represent an int under the hood - use one less than the maximum
+ * length of a long.
+ *
+ * 64-bit int max == 9223372036854775807; len('9223372036854775807') - 1 == 18
+ * 32-bit int max == 2147483647; len('2147483647') - 1 == 9
+ */
+constexpr long FN_MAX_INT_LEN
+    = std::numeric_limits<unsigned long>::max() == 9223372036854775807 ? 18 : 9;
+
+/**
+ * \brief Determine if a character is whitespace
+ */
+inline bool is_whitespace(const char c)
+{
+    const unsigned char cu = static_cast<unsigned char>(c);
+    return ::isspace(cu) && cu < ASCII_MAX;
+}
+
+/**
+ * \brief Determine if a character represents a digit
+ */
+inline bool is_valid_digit(const char c)
+{
+    const unsigned char cu = static_cast<unsigned char>(c);
+    return ::isdigit(cu) && cu < ASCII_MAX;
+}
+
+/**
+ * \brief Determine if a character is '-' or '+'
+ */
+inline bool is_sign(const char c)
+{
+    return c == '-' || c == '+';
+}
+
+/**
+ * \brief Determine if a character a prefix for base 2, 8, or 16
+ */
+inline bool is_base_prefix(const char c)
+{
+    return c == 'x' || c == 'X' || c == 'o' || c == 'O' || c == 'b' || c == 'B';
+}
+
+/**
+ * \brief Determine if a character a prefix for a specific base 2, 8, or 16
+ */
+inline bool is_base_prefix(const char c, const int base)
+{
+    return (base == 16 && (c == 'x' || c == 'X'))
+        || (base == 8 && (c == 'o' || c == 'O'))
+        || (base == 2 && (c == 'b' || c == 'B'));
+}
+
+/**
+ * \brief Detect if a string contains infinity
+ *
+ * This is done without loops in the hope of being optimized
+ * by the compiler
+ *
+ * \param str The string to check, assumed to be non-NULL
+ * \param len The length of the string
+ */
+inline bool quick_detect_infinity(const char* str, const std::size_t len)
+{
+    switch (len) {
+    case 3:
+        return (str[0] == 'i' || str[0] == 'I') && (str[1] == 'n' || str[1] == 'N')
+            && (str[2] == 'f' || str[2] == 'F');
+    case 8:
+        return (str[0] == 'i' || str[0] == 'I') && (str[1] == 'n' || str[1] == 'N')
+            && (str[2] == 'f' || str[2] == 'F') && (str[3] == 'i' || str[3] == 'I')
+            && (str[4] == 'n' || str[4] == 'N') && (str[5] == 'i' || str[5] == 'I')
+            && (str[6] == 't' || str[6] == 'T') && (str[7] == 'y' || str[7] == 'Y');
+    default:
+        return false;
+    }
+}
+
+/**
+ * \brief Detect if a string contains NaN
+ *
+ * This is done without loops in the hope of being optimized
+ * by the compiler
+ *
+ * \param str The string to check, assumed to be non-NULL
+ * \param len The length of the string
+ */
+inline bool quick_detect_nan(const char* str, const std::size_t len)
+{
+    return len == 3 && (str[0] == 'n' || str[0] == 'N')
+        && (str[1] == 'a' || str[1] == 'A') && (str[2] == 'n' || str[2] == 'N');
+}
+
+/**
+ * \brief Detect if a string probably contains an integer
+ *
+ * This is not a 100% assurance, but it is a gross check that
+ * makes sure the first character is a digit.
+ *
+ * \param str The string to check, assumed to be non-NULL
+ * \param len The length of the string
+ */
+inline bool is_likely_int(const char* str, std::size_t len)
+{
+    return len > 0 && is_valid_digit(*str);
+}
+
+/**
+ * \brief Detect if a string probably contains a float
+ *
+ * This is not a 100% assurance, but it is a gross check that
+ * makes sure the string starts with a digit or '.' followed
+ * by a digit..
+ *
+ * \param str The string to check, assumed to be non-NULL
+ * \param len The length of the string
+ */
+inline bool is_likely_float(const char* str, std::size_t len)
+{
+    return len > 0
+        && (is_valid_digit(*str)
+            || (*str == '.' && len > 1 && is_valid_digit(*(str + 1))));
+}
+
+// Define the number of digits in a float that fastnumbers is willing
+// to attempt to convert itself. For Clang and GNUC, it is known
+// that DBL_DIG - 4 works. It is known that DBL_DIG - 6 works for MSVC.
+// To be safe, any compiler not known during fastnumbers testing will
+// use DBL_DIG - 6.
+// Also define the largest and smallest exponent that
+// fastnumbers is willing attempt to convert itself. For Clang and GNUC,
+// it is known that larger numbers work than MSVC.
+// To be safe, any compiler not known during fastnumbers testing will
+// use the smaller numbers.
+#if defined(__clang__) || defined(__GNUC__)
+
+/// Number of float digits fastnumbers will convert itself
+constexpr long FN_DBL_DIG = std::numeric_limits<double>::digits10 - 4;
+
+/// Most positive exponent fastumbers will convert itself
+constexpr long FN_MAX_EXP = 99;
+
+/// Most negative exponent fastumbers will convert itself
+constexpr long FN_MIN_EXP = -98;
+
+/// Allow the preprocessor to be able to dispatch on this decision
+#define FASTNUMBERS_WIDE_EXP_RANGE
+
+#else
+
+/// Number of float digits fastnumbers will convert itself
+constexpr long FN_DBL_DIG = std::numeric_limits<double>::digits10 - 6;
+
+/// Most positive exponent fastumbers will convert itself
+constexpr long FN_MAX_EXP = 19;
+
+/// Most negative exponent fastumbers will convert itself
+constexpr long FN_MIN_EXP = -19;
+
+/// Allow the preprocessor to be able to dispatch on this decision
+#undef FASTNUMBERS_WIDE_EXP_RANGE
+
+#endif
+
+/**
+ * \brief Guess if an int will overflow.
+ *
+ * \param start The string to check, assumed to be non-NULL
+ * \param len The length of the string
+ */
+inline bool int_might_overflow(const char*, const std::size_t len)
+{
+    return len > FN_MAX_INT_LEN;
+}
+
+/**
+ * \brief Guess if a float will overflow.
+ *
+ * \param start The string to check, assumed to be non-NULL
+ * \param len The length of the string
+ */
+bool float_might_overflow(const char* start, const std::size_t len);
diff --git a/include/fastnumbers/docstrings.h b/include/fastnumbers/docstrings.h
deleted file mode 100644
index 80a1fbd1..00000000
--- a/include/fastnumbers/docstrings.h
+++ /dev/null
@@ -1,997 +0,0 @@
-#ifndef __DOCSTRINGS
-#define __DOCSTRINGS
-
-/*
- * Docstrings for the fastnumbers functions.
- *
- * Author: Seth M. Morton, August 2, 2014
- */
-#include <Python.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-
-PyDoc_STRVAR(fastnumbers__doc__, "Quickly convert strings to numbers.\n");
-
-
-PyDoc_STRVAR(fast_real__doc__,
-"fast_real(x, default=None, raise_on_invalid=False, on_fail=None, nan=None, inf=None, coerce=True, allow_underscores=True)\n"
-"Quickly convert input to an `int` or `float` depending on value.\n"
-"\n"
-"Any input that is valid for the built-in `float` or `int` functions will\n"
-"be converted to either a `float` or `int`. An input of a single numeric\n"
-"unicode character is also valid. The return value is guaranteed\n"
-"to be of type `str`, `int`, or `float`.\n"
-"\n"
-"If the given input is a string and cannot be converted to a `float` or\n"
-"`int`, it will be returned as-is unless `default` or `raise_on_invalid`\n"
-"is given.\n"
-"\n"
-"Parameters\n"
-"----------\n"
-"input : {str, float, int, long}\n"
-"    The input you wish to convert to a real number.\n"
-"default : optional\n"
-"    This value will be returned instead of the input\n"
-"    when the input cannot be converted. Has no effect if\n"
-"    *raise_on_invalid* is *True*.\n"
-"raise_on_invalid : bool, optional\n"
-"    If *True*, a `ValueError` will be raised if string input cannot be\n"
-"    converted to a `float` or `int`. If *False*, the string will be\n"
-"    returned as-is. The default is *False*.\n"
-"on_fail : callable, optional\n"
-"    If given and the *input* cannot be converted, the input will be\n"
-"    passed to the callable object and its return value will be returned.\n"
-"    The function expect only one positional argument.\n"
-"    For backwards-compatability, you may call this option `key` instead\n"
-"    of `on_fail`, but this is deprecated behavior.\n"
-"nan : optional\n"
-"    If the input value is NAN or can be parsed as NAN, return this\n"
-"    value instead of NAN.\n"
-"inf : optional\n"
-"    If the input value is INF or can be parsed as INF, return this\n"
-"    value instead of INF.\n"
-"coerce : bool, optional\n"
-"    If the input can be converted to an `int` without loss of precision\n"
-"    (even if the input was a `float` or float-containing `str`)\n"
-"    coerce to an *int* rather than returning a `float`.\n"
-"allow_underscores : bool, optional\n"
-"    Starting with Python 3.6, underscores are allowed in numeric literals\n"
-"    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
-"    what is and is not allowed). You can disable that behavior by setting\n"
-"    this option to *False* - the default is *True*.\n"
-"\n"
-"Returns\n"
-"-------\n"
-"out : {str, float, int}\n"
-"    If the input could be converted to an `int`, the return type will be\n"
-"    `int`.\n"
-"    If the input could be converted to a `float` but not an `int`, the\n"
-"    return type will be `float`.  Otherwise, the input `str` will be\n"
-"    returned as-is (if `raise_on_invalid` is *False*) or whatever value\n"
-"    is assigned to `default` if `default` is not *None*.\n"
-"\n"
-"Raises\n"
-"------\n"
-"TypeError\n"
-"    If the input is not one of `str`, `float`, or `int`.\n"
-"ValueError\n"
-"    If `raise_on_invalid` is *True*, this will be raised if the input\n"
-"    string cannot be converted to a `float` or `int`.\n"
-"\n"
-"See Also\n"
-"--------\n"
-"isreal\n"
-"real\n"
-"\n"
-"Examples\n"
-"--------\n"
-"\n"
-"    >>> from fastnumbers import fast_real\n"
-"    >>> fast_real('56')\n"
-"    56\n"
-"    >>> fast_real('56.0')\n"
-"    56\n"
-"    >>> fast_real('56.0', coerce=False)\n"
-"    56.0\n"
-"    >>> fast_real('56.07')\n"
-"    56.07\n"
-"    >>> fast_real('56.07 lb')\n"
-"    '56.07 lb'\n"
-"    >>> fast_real(56.07)\n"
-"    56.07\n"
-"    >>> fast_real(56.0)\n"
-"    56\n"
-"    >>> fast_real(56.0, coerce=False)\n"
-"    56.0\n"
-"    >>> fast_real(56)\n"
-"    56\n"
-"    >>> fast_real('invalid', default=50)\n"
-"    50\n"
-"    >>> fast_real('invalid', 50)  # 'default' is first optional positional arg\n"
-"    50\n"
-"    >>> fast_real('nan')\n"
-"    nan\n"
-"    >>> fast_real('nan', nan=0)\n"
-"    0\n"
-"    >>> fast_real('56.07', nan=0)\n"
-"    56.07\n"
-"    >>> fast_real('56.07 lb', raise_on_invalid=True) #doctest: +IGNORE_EXCEPTION_DETAIL\n"
-"    Traceback (most recent call last):\n"
-"      ...\n"
-"    ValueError: could not convert string to float: '56.07 lb'\n"
-"    >>> fast_real('invalid', on_fail=len)\n"
-"    7\n"
-"\n"
-"Notes\n"
-"-----\n"
-"It is roughly equivalent to (but much faster than)\n"
-"\n"
-"    >>> def py_fast_real(input, default=None, raise_on_invalid=False,\n"
-"    ...                  on_fail=None, nan=None, inf=None):\n"
-"    ...     import math\n"
-"    ...     try:\n"
-"    ...         a = float(input)\n"
-"    ...     except ValueError:\n"
-"    ...         if raise_on_invalid:\n"
-"    ...             raise\n"
-"    ...         elif on_fail is not None:\n"
-"    ...             return on_fail(input)\n"
-"    ...         elif default is not None:\n"
-"    ...             return default\n"
-"    ...         else:\n"
-"    ...             return input\n"
-"    ...     else:\n"
-"    ...         if nan is not None and math.isnan(a):\n"
-"    ...             return nan\n"
-"    ...         elif inf is not None and math.isinf(a):\n"
-"    ...             return inf\n"
-"    ...         else:\n"
-"    ...             return int(a) if a.is_integer() else a\n"
-"    ... \n"
-"\n");
-
-
-PyDoc_STRVAR(fast_float__doc__,
-"fast_float(x, default=None, raise_on_invalid=False, on_fail=None, nan=None, inf=None, allow_underscores=True)\n"
-"Quickly convert input to a `float`.\n"
-"\n"
-"Any input that is valid for the built-in `float` function will\n"
-"be converted to a `float`. An input of a single numeric\n"
-"unicode character is also valid. The return value is guaranteed\n"
-"to be of type `str` or `float`.\n"
-"\n"
-"If the given input is a string and cannot be converted to a `float`\n"
-"it will be returned as-is unless `default` or `raise_on_invalid`\n"
-"is given.\n"
-"\n"
-"Parameters\n"
-"----------\n"
-"input : {str, float, int, long}\n"
-"    The input you wish to convert to a `float`.\n"
-"default : optional\n"
-"    This value will be returned instead of the input\n"
-"    when the input cannot be converted. Has no effect if\n"
-"    *raise_on_invalid* is *True*.\n"
-"raise_on_invalid : bool, optional\n"
-"    If *True*, a `ValueError` will be raised if string input cannot be\n"
-"    converted to a `float`.  If *False*, the string will be\n"
-"    returned as-is.  The default is *False*.\n"
-"on_fail : callable, optional\n"
-"    If given and the *input* cannot be converted, the input will be\n"
-"    passed to the callable object and its return value will be returned.\n"
-"    The function expect only one positional argument.\n"
-"    For backwards-compatability, you may call this option `key` instead\n"
-"    of `on_fail`, but this is deprecated behavior.\n"
-"nan : optional\n"
-"    If the input value is NAN or can be parsed as NAN, return this\n"
-"    value instead of NAN.\n"
-"inf : optional\n"
-"    If the input value is INF or can be parsed as INF, return this\n"
-"    value instead of INF.\n"
-"allow_underscores : bool, optional\n"
-"    Starting with Python 3.6, underscores are allowed in numeric literals\n"
-"    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
-"    what is and is not allowed). You can disable that behavior by setting\n"
-"    this option to *False* - the default is *True*.\n"
-"\n"
-"Returns\n"
-"-------\n"
-"out : {str, float}\n"
-"    If the input could be converted to a `float` the return type will\n"
-"    be `float`. Otherwise, the input `str` will be returned as-is\n"
-"    (if `raise_on_invalid` is *False*) or whatever value\n"
-"    is assigned to *default* if *default* is not *None*.\n"
-"\n"
-"Raises\n"
-"------\n"
-"TypeError\n"
-"    If the input is not one of `str`, `float`, or `int`.\n"
-"ValueError\n"
-"    If `raise_on_invalid` is *True*, this will be raised if the input\n"
-"    string cannot be converted to a `float`.\n"
-"\n"
-"See Also\n"
-"--------\n"
-"isfloat\n"
-"float\n"
-"\n"
-"Examples\n"
-"--------\n"
-"\n"
-"    >>> from fastnumbers import fast_float\n"
-"    >>> fast_float('56')\n"
-"    56.0\n"
-"    >>> fast_float('56.0')\n"
-"    56.0\n"
-"    >>> fast_float('56.07')\n"
-"    56.07\n"
-"    >>> fast_float('56.07 lb')\n"
-"    '56.07 lb'\n"
-"    >>> fast_float(56.07)\n"
-"    56.07\n"
-"    >>> fast_float(56)\n"
-"    56.0\n"
-"    >>> fast_float('invalid', default=50)\n"
-"    50\n"
-"    >>> fast_float('invalid', 50)  # 'default' is first optional positional arg\n"
-"    50\n"
-"    >>> fast_float('nan')\n"
-"    nan\n"
-"    >>> fast_float('nan', nan=0.0)\n"
-"    0.0\n"
-"    >>> fast_float('56.07', nan=0.0)\n"
-"    56.07\n"
-"    >>> fast_float('56.07 lb', raise_on_invalid=True) #doctest: +IGNORE_EXCEPTION_DETAIL\n"
-"    Traceback (most recent call last):\n"
-"      ...\n"
-"    ValueError: could not convert string to float: '56.07 lb'\n"
-"    >>> fast_float('invalid', on_fail=len)\n"
-"    7\n"
-"\n"
-"Notes\n"
-"-----\n"
-"It is roughly equivalent to (but much faster than)\n"
-"\n"
-"    >>> def py_fast_float(input, default=None, raise_on_invalid=False,\n"
-"    ...                   on_fail=None, nan=None, inf=None):\n"
-"    ...     try:\n"
-"    ...         x = float(input)\n"
-"    ...     except ValueError:\n"
-"    ...         if raise_on_invalid:\n"
-"    ...             raise\n"
-"    ...         elif on_fail is not None:\n"
-"    ...             return on_fail(input)\n"
-"    ...         elif default is not None:\n"
-"    ...             return default\n"
-"    ...         else:\n"
-"    ...             return input\n"
-"    ...     else:\n"
-"    ...         if nan is not None and math.isnan(x):\n"
-"    ...             return nan\n"
-"    ...         elif inf is not None and math.isinf(x):\n"
-"    ...             return inf\n"
-"    ...         else:\n"
-"    ...             return x\n"
-"    ... \n"
-"\n");
-
-
-PyDoc_STRVAR(fast_int__doc__,
-"fast_int(x, default=None, raise_on_invalid=False, on_fail=None, base=10, allow_underscores=True)\n"
-"Quickly convert input to an `int`.\n"
-"\n"
-"Any input that is valid for the built-in `int`\n"
-"function will be converted to a `int`. An input\n"
-"of a single digit unicode character is also valid. The return value\n"
-"is guaranteed to be of type `str` or `int`.\n"
-"\n"
-"If the given input is a string and cannot be converted to an `int`\n"
-"it will be returned as-is unless `default` or `raise_on_invalid`\n"
-"is given.\n"
-"\n"
-"Parameters\n"
-"----------\n"
-"input : {str, float, int, long}\n"
-"    The input you wish to convert to an `int`.\n"
-"default : optional\n"
-"    This value will be returned instead of the input\n"
-"    when the input cannot be converted. Has no effect if\n"
-"    *raise_on_invalid* is *True*.\n"
-"raise_on_invalid : bool, optional\n"
-"    If *True*, a `ValueError` will be raised if string input cannot be\n"
-"    converted to an `int`. If *False*, the string will be\n"
-"    returned as-is. The default is *False*.\n"
-"on_fail : callable, optional\n"
-"    If given and the *input* cannot be converted, the input will be\n"
-"    passed to the callable object and its return value will be returned.\n"
-"    The function expect only one positional argument.\n"
-"    For backwards-compatability, you may call this option `key` instead\n"
-"    of `on_fail`, but this is deprecated behavior.\n"
-"base : int, optional\n"
-"    Follows the rules of Python's built-in :func:`int`; see it's\n"
-"    documentation for your Python version. If given, the input\n"
-"    **must** be of type `str`.\n"
-"allow_underscores : bool, optional\n"
-"    Starting with Python 3.6, underscores are allowed in numeric literals\n"
-"    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
-"    what is and is not allowed). You can disable that behavior by setting\n"
-"    this option to *False* - the default is *True*.\n"
-"\n"
-"Returns\n"
-"-------\n"
-"out : {str, int}\n"
-"    If the input could be converted to an `int`, the return type will be\n"
-"    `int`.\n"
-"    Otherwise, the input `str` will be returned as-is\n"
-"    (if `raise_on_invalid` is *False*) or whatever value\n"
-"    is assigned to *default* if *default* is not *None*.\n"
-"\n"
-"Raises\n"
-"------\n"
-"TypeError\n"
-"    If the input is not one of `str`, `float`, or `int`.\n"
-"ValueError\n"
-"    If `raise_on_invalid` is *True*, this will be raised if the input\n"
-"    string cannot be converted to an `int`.\n"
-"\n"
-"See Also\n"
-"--------\n"
-"fast_forceint\n"
-"isint\n"
-"int\n"
-"\n"
-"Examples\n"
-"--------\n"
-"\n"
-"    >>> from fastnumbers import fast_int\n"
-"    >>> fast_int('56')\n"
-"    56\n"
-"    >>> fast_int('56.0')\n"
-"    '56.0'\n"
-"    >>> fast_int('56.07 lb')\n"
-"    '56.07 lb'\n"
-"    >>> fast_int(56.07)\n"
-"    56\n"
-"    >>> fast_int(56)\n"
-"    56\n"
-"    >>> fast_int('invalid', default=50)\n"
-"    50\n"
-"    >>> fast_int('invalid', 50)  # 'default' is first optional positional arg\n"
-"    50\n"
-"    >>> fast_int('56.07 lb', raise_on_invalid=True) #doctest: +IGNORE_EXCEPTION_DETAIL\n"
-"    Traceback (most recent call last):\n"
-"      ...\n"
-"    ValueError: could not convert string to int: '56.07 lb'\n"
-"    >>> fast_int('invalid', on_fail=len)\n"
-"    7\n"
-"\n"
-"Notes\n"
-"-----\n"
-"It is roughly equivalent to (but much faster than)\n"
-"\n"
-"    >>> def py_fast_int(input, default=None, raise_on_invalid=False, on_fail=None):\n"
-"    ...     try:\n"
-"    ...         return int(input)\n"
-"    ...     except ValueError:\n"
-"    ...         if raise_on_invalid:\n"
-"    ...             raise\n"
-"    ...         elif on_fail is not None:\n"
-"    ...             return on_fail(input)\n"
-"    ...         elif default is not None:\n"
-"    ...             return default\n"
-"    ...         else:\n"
-"    ...             return input\n"
-"    ... \n"
-"\n");
-
-
-PyDoc_STRVAR(fast_forceint__doc__,
-"fast_forceint(x, default=None, raise_on_invalid=False, on_fail=None, allow_underscores=True)\n"
-"Quickly convert input to an `int`, truncating if is a `float`.\n"
-"\n"
-"Any input that is valid for the built-in `int`\n"
-"function will be converted to a `int`. An input\n"
-"of a single numeric unicode character is also valid. The return value\n"
-"is guaranteed to be of type `str` or `int`.\n"
-"\n"
-"In addition to the above, any input valid for the built-in `float` will\n"
-"be parsed and the truncated to the nearest integer; for example, '56.07'\n"
-"will be converted to `56`.\n"
-"\n"
-"If the given input is a string and cannot be converted to an `int`\n"
-"it will be returned as-is unless `default` or `raise_on_invalid`\n"
-"is given.\n"
-"\n"
-"Parameters\n"
-"----------\n"
-"input : {str, float, int, long}\n"
-"    The input you wish to convert to an `int`.\n"
-"default : optional\n"
-"    This value will be returned instead of the input\n"
-"    when the input cannot be converted. Has no effect if\n"
-"    *raise_on_invalid* is *True*\n"
-"raise_on_invalid : bool, optional\n"
-"    If *True*, a `ValueError` will be raised if string input cannot be\n"
-"    converted to an `int`.  If *False*, the string will be\n"
-"    returned as-is.  The default is *False*.\n"
-"on_fail : callable, optional\n"
-"    If given and the *input* cannot be converted, the input will be\n"
-"    passed to the callable object and its return value will be returned.\n"
-"    The function expect only one positional argument.\n"
-"    For backwards-compatability, you may call this option `key` instead\n"
-"    of `on_fail`, but this is deprecated behavior.\n"
-"allow_underscores : bool, optional\n"
-"    Starting with Python 3.6, underscores are allowed in numeric literals\n"
-"    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
-"    what is and is not allowed). You can disable that behavior by setting\n"
-"    this option to *False* - the default is *True*.\n"
-"\n"
-"Returns\n"
-"-------\n"
-"out : {str, int}\n"
-"    If the input could be converted to an `int`, the return type will be\n"
-"    `int`.\n"
-"    Otherwise, the input `str` will be returned as-is\n"
-"    (if `raise_on_invalid` is *False*) or whatever value\n"
-"    is assigned to *default* if *default* is not *None*.\n"
-"\n"
-"Raises\n"
-"------\n"
-"TypeError\n"
-"    If the input is not one of `str`, `float`, or `int`.\n"
-"ValueError\n"
-"    If `raise_on_invalid` is *True*, this will be raised if the input\n"
-"    string cannot be converted to an `int`.\n"
-"\n"
-"See Also\n"
-"--------\n"
-"fast_int\n"
-"isintlike\n"
-"\n"
-"Examples\n"
-"--------\n"
-"\n"
-"    >>> from fastnumbers import fast_forceint\n"
-"    >>> fast_forceint('56')\n"
-"    56\n"
-"    >>> fast_forceint('56.0')\n"
-"    56\n"
-"    >>> fast_forceint('56.07')\n"
-"    56\n"
-"    >>> fast_forceint('56.07 lb')\n"
-"    '56.07 lb'\n"
-"    >>> fast_forceint(56.07)\n"
-"    56\n"
-"    >>> fast_forceint(56)\n"
-"    56\n"
-"    >>> fast_forceint('invalid', default=50)\n"
-"    50\n"
-"    >>> fast_forceint('invalid', 50)  # 'default' is first optional positional arg\n"
-"    50\n"
-"    >>> fast_forceint('56.07 lb', raise_on_invalid=True) #doctest: +IGNORE_EXCEPTION_DETAIL\n"
-"    Traceback (most recent call last):\n"
-"      ...\n"
-"    ValueError: could not convert string to float: '56.07 lb'\n"
-"    >>> fast_forceint('invalid', on_fail=len)\n"
-"    7\n"
-"\n"
-"Notes\n"
-"-----\n"
-"It is roughly equivalent to (but much faster than)\n"
-"\n"
-"    >>> def py_fast_forceint(input, default=None, raise_on_invalid=False, on_fail=None):\n"
-"    ...     try:\n"
-"    ...         return int(input)\n"
-"    ...     except ValueError:\n"
-"    ...         try:\n"
-"    ...             return int(float(input))\n"
-"    ...         except ValueError:\n"
-"    ...             if raise_on_invalid:\n"
-"    ...                 raise\n"
-"    ...             elif on_fail is not None:\n"
-"    ...                 return on_fail(input)\n"
-"    ...             elif default is not None:\n"
-"    ...                 return default\n"
-"    ...             else:\n"
-"    ...                 return input\n"
-"    ... \n"
-"\n");
-
-
-PyDoc_STRVAR(isreal__doc__,
-"isreal(x, str_only=False, num_only=False, allow_inf=False, allow_nan=False, allow_underscores=True)\n"
-"Quickly determine if a string is a real number.\n"
-"\n"
-"Returns *True* if the input is valid input for the built-in `float` or\n"
-"`int` functions, or is a single valid numeric unicode character.\n"
-"\n"
-"The input may be whitespace-padded.\n"
-"\n"
-"Parameters\n"
-"----------\n"
-"input :\n"
-"    The input you wish to test if it is a real number.\n"
-"str_only : bool, optional\n"
-"    If *True*, then any non-`str` input will cause this function to return\n"
-"    *False*. The default is *False*.\n"
-"num_only : bool, optional\n"
-"    If *True*, then any `str` input will cause this function to return\n"
-"    *False*. The default is *False*.\n"
-"allow_inf : bool, optional\n"
-"    If *True*, then the strings 'inf' and 'infinity' will also return *True*.\n"
-"    This check is case-insensitive, and the string may be signed (i.e. '+/-').\n"
-"    The default is *False*.\n"
-"allow_nan : bool, optional\n"
-"    If *True*, then the string 'nan' will also return *True*.\n"
-"    This check is case-insensitive, and the string may be signed (i.e. '+/-').\n"
-"    The default is *False*.\n"
-"allow_underscores : bool, optional\n"
-"    Starting with Python 3.6, underscores are allowed in numeric literals\n"
-"    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
-"    what is and is not allowed). You can disable that behavior by setting\n"
-"    this option to *False* - the default is *True*.\n"
-"\n"
-"Returns\n"
-"-------\n"
-"result : bool\n"
-"    Whether or not the input is a real number.\n"
-"\n"
-"See Also\n"
-"--------\n"
-"fast_real\n"
-"\n"
-"Examples\n"
-"--------\n"
-"\n"
-"    >>> from fastnumbers import isreal\n"
-"    >>> isreal('56')\n"
-"    True\n"
-"    >>> isreal('56.07')\n"
-"    True\n"
-"    >>> isreal('56.07', num_only=True)\n"
-"    False\n"
-"    >>> isreal('56.07 lb')\n"
-"    False\n"
-"    >>> isreal(56.07)\n"
-"    True\n"
-"    >>> isreal(56.07, str_only=True)\n"
-"    False\n"
-"    >>> isreal(56)\n"
-"    True\n"
-"    >>> isreal('nan')\n"
-"    False\n"
-"    >>> isreal('nan', allow_nan=True)\n"
-"    True\n"
-"\n"
-"Notes\n"
-"-----\n"
-"It is roughly equivalent to (but much faster than)\n"
-"\n"
-"    >>> import re\n"
-"    >>> def py_isreal(input, str_only=False, num_only=False,\n"
-"    ...               allow_nan=False, allow_inf=False):\n"
-"    ...     if str_only and type(input) != str:\n"
-"    ...         return False\n"
-"    ...     if num_only and type(input) not in (float, int):\n"
-"    ...         return False\n"
-"    ...     try:\n"
-"    ...         x = bool(re.match(r'[-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?$', input))\n"
-"    ...     except TypeError:\n"
-"    ...         return type(input) in (float, int)\n"
-"    ...     else:\n"
-"    ...         if x:\n"
-"    ...             return True\n"
-"    ...         elif allow_inf and input.lower().strip().lstrip('-+') in ('inf', 'infinity'):\n"
-"    ...             return True\n"
-"    ...         elif allow_nan and input.lower().strip().lstrip('-+') == 'nan':\n"
-"    ...             return True\n"
-"    ...         else:\n"
-"    ...             return False\n"
-"    ... \n"
-"\n");
-
-
-PyDoc_STRVAR(isfloat__doc__,
-"isfloat(x, str_only=False, num_only=False, allow_inf=False, allow_nan=False, allow_underscores=True)\n"
-"Quickly determine if a string is a `float`.\n"
-"\n"
-"Returns *True* if the input is valid input for the built-in `float`\n"
-"function, is already a valid `float`, or is a single valid numeric unicode\n"
-"character. It differs from `isreal` in that an `int` input will return\n"
-"*False*.\n"
-"\n"
-"The input may be whitespace-padded.\n"
-"\n"
-"Parameters\n"
-"----------\n"
-"input :\n"
-"    The input you wish to test if it is a `float`.\n"
-"str_only : bool, optional\n"
-"    If *True*, then any non-`str` input will cause this function to return\n"
-"    *False*. The default is *False*.\n"
-"num_only : bool, optional\n"
-"    If *True*, then any `str` input will cause this function to return\n"
-"    *False*. The default is *False*.\n"
-"allow_inf : bool, optional\n"
-"    If *True*, then the strings 'inf' and 'infinity' will also return *True*.\n"
-"    This check is case-insensitive, and the string may be signed (i.e. '+/-').\n"
-"    The default is *False*.\n"
-"allow_nan : bool, optional\n"
-"    If *True*, then the string 'nan' will also return *True*.\n"
-"    This check is case-insensitive, and the string may be signed (i.e. '+/-').\n"
-"    The default is *False*.\n"
-"allow_underscores : bool, optional\n"
-"    Starting with Python 3.6, underscores are allowed in numeric literals\n"
-"    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
-"    what is and is not allowed). You can disable that behavior by setting\n"
-"    this option to *False* - the default is *True*.\n"
-"\n"
-"Returns\n"
-"-------\n"
-"result : bool\n"
-"    Whether or not the input is a `float`.\n"
-"\n"
-"See Also\n"
-"--------\n"
-"fast_float, isreal\n"
-"\n"
-"Examples\n"
-"--------\n"
-"\n"
-"    >>> from fastnumbers import isfloat\n"
-"    >>> isfloat('56')\n"
-"    True\n"
-"    >>> isfloat('56.07')\n"
-"    True\n"
-"    >>> isfloat('56.07', num_only=True)\n"
-"    False\n"
-"    >>> isfloat('56.07 lb')\n"
-"    False\n"
-"    >>> isfloat(56.07)\n"
-"    True\n"
-"    >>> isfloat(56.07, str_only=True)\n"
-"    False\n"
-"    >>> isfloat(56)\n"
-"    False\n"
-"    >>> isfloat('nan')\n"
-"    False\n"
-"    >>> isfloat('nan', allow_nan=True)\n"
-"    True\n"
-"\n"
-"Notes\n"
-"-----\n"
-"It is roughly equivalent to (but much faster than)\n"
-"\n"
-"    >>> import re\n"
-"    >>> def py_isfloat(input, str_only=False, num_only=False,\n"
-"    ...                allow_nan=False, allow_inf=False):\n"
-"    ...     if str_only and type(input) != str:\n"
-"    ...         return False\n"
-"    ...     if num_only and type(input) != float:\n"
-"    ...         return False\n"
-"    ...     try:\n"
-"    ...         x = bool(re.match(r'[-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?$', input))\n"
-"    ...     except TypeError:\n"
-"    ...         return type(input) == float\n"
-"    ...     else:\n"
-"    ...         if x:\n"
-"    ...             return True\n"
-"    ...         elif allow_inf and input.lower().strip().lstrip('-+') in ('inf', 'infinity'):\n"
-"    ...             return True\n"
-"    ...         elif allow_nan and input.lower().strip().lstrip('-+') == 'nan':\n"
-"    ...             return True\n"
-"    ...         else:\n"
-"    ...             return False\n"
-"\n");
-
-
-PyDoc_STRVAR(isint__doc__,
-"isint(x, str_only=False, num_only=False, allow_underscores=True)\n"
-"Quickly determine if a string is an `int`.\n"
-"\n"
-"Returns *True* if the input is valid input for the built-in `int`\n"
-"function, is already a valid `int`, or is a single valid digit unicode\n"
-"character. It differs from `isintlike` in that a `float` input will\n"
-"return *False* and that `int`-like strings (i.e. '45.0') will return\n"
-"*False*.\n"
-"\n"
-"The input may be whitespace-padded.\n"
-"\n"
-"Parameters\n"
-"----------\n"
-"input :\n"
-"    The input you wish to test if it is an `int`.\n"
-"str_only : bool, optional\n"
-"    If *True*, then any non-`str` input will cause this function to return\n"
-"    *False*. The default is *False*.\n"
-"num_only : bool, optional\n"
-"    If *True*, then any `str` input will cause this function to return\n"
-"    *False*. The default is *False*.\n"
-"allow_underscores : bool, optional\n"
-"    Starting with Python 3.6, underscores are allowed in numeric literals\n"
-"    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
-"    what is and is not allowed). You can disable that behavior by setting\n"
-"    this option to *False* - the default is *True*.\n"
-"\n"
-"Returns\n"
-"-------\n"
-"result : bool\n"
-"    Whether or not the input is an `int`.\n"
-"\n"
-"See Also\n"
-"--------\n"
-"fast_int, isintlike\n"
-"\n"
-"Examples\n"
-"--------\n"
-"\n"
-"    >>> from fastnumbers import isint\n"
-"    >>> isint('56')\n"
-"    True\n"
-"    >>> isint('56', num_only=True)\n"
-"    False\n"
-"    >>> isint('56.07')\n"
-"    False\n"
-"    >>> isint('56.07 lb')\n"
-"    False\n"
-"    >>> isint(56.07)\n"
-"    False\n"
-"    >>> isint(56)\n"
-"    True\n"
-"    >>> isint(56, str_only=True)\n"
-"    False\n"
-"\n"
-"Notes\n"
-"-----\n"
-"It is roughly equivalent to (but much faster than)\n"
-"\n"
-"    >>> import re\n"
-"    >>> def py_isint(input, str_only=False, num_only=False):\n"
-"    ...     if str_only and type(input) != str:\n"
-"    ...         return False\n"
-"    ...     if num_only and type(input) != int:\n"
-"    ...         return False\n"
-"    ...     try:\n"
-"    ...         return bool(re.match(r'[-+]?\\d+$', input))\n"
-"    ...     except TypeError:\n"
-"    ...         return False\n"
-"    ... \n"
-"\n");
-
-
-PyDoc_STRVAR(isintlike__doc__,
-"isintlike(x, str_only=False, num_only=False, allow_underscores=True)\n"
-"Quickly determine if a string (or object) is an `int` or `int`-like.\n"
-"\n"
-"Returns *True* if the input is valid input for the built-in `int`\n"
-"function, is already a valid `int` or `float`, or is a single valid\n"
-"numeric unicode character. It differs from `isintlike` in that `int`-like\n"
-"floats or strings (i.e. '45.0') will return *True*.\n"
-"\n"
-"The input may be whitespace-padded.\n"
-"\n"
-"Parameters\n"
-"----------\n"
-"input :\n"
-"    The input you wish to test if it is a `int`-like.\n"
-"\n"
-"Returns\n"
-"-------\n"
-"result : bool\n"
-"    Whether or not the input is an `int`.\n"
-"str_only : bool, optional\n"
-"    If *True*, then any non-`str` input will cause this function to return\n"
-"    *False*. The default is *False*.\n"
-"num_only : bool, optional\n"
-"    If *True*, then any `str` input will cause this function to return\n"
-"    *False*. The default is *False*.\n"
-"allow_underscores : bool, optional\n"
-"    Starting with Python 3.6, underscores are allowed in numeric literals\n"
-"    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
-"    what is and is not allowed). You can disable that behavior by setting\n"
-"    this option to *False* - the default is *True*.\n"
-"\n"
-"See Also\n"
-"--------\n"
-"fast_forceint\n"
-"\n"
-"Examples\n"
-"--------\n"
-"\n"
-"    >>> from fastnumbers import isintlike\n"
-"    >>> isintlike('56')\n"
-"    True\n"
-"    >>> isintlike('56', num_only=True)\n"
-"    False\n"
-"    >>> isintlike('56.07')\n"
-"    False\n"
-"    >>> isintlike('56.0')\n"
-"    True\n"
-"    >>> isintlike('56.07 lb')\n"
-"    False\n"
-"    >>> isintlike(56.07)\n"
-"    False\n"
-"    >>> isintlike(56.0)\n"
-"    True\n"
-"    >>> isintlike(56.0, str_only=True)\n"
-"    False\n"
-"    >>> isintlike(56)\n"
-"    True\n"
-"\n"
-"Notes\n"
-"-----\n"
-"It is roughly equivalent to (but much faster than)\n"
-"\n"
-"    >>> import re\n"
-"    >>> def py_isintlike(input, str_only=False, num_only=False):\n"
-"    ...     if str_only and type(input) != str:\n"
-"    ...         return False\n"
-"    ...     if num_only and type(input) not in (int, float):\n"
-"    ...         return False\n"
-"    ...     try:\n"
-"    ...         if re.match(r'[-+]?\\d+$', input):\n"
-"    ...             return True\n"
-"    ...         elif re.match(r'[-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?$', input):\n"
-"    ...             return float(input).is_integer()\n"
-"    ...         else:\n"
-"    ...             return False\n"
-"    ...     except TypeError:\n"
-"    ...         if type(input) == float:\n"
-"    ...             return input.is_integer()\n"
-"    ...         elif type(input) == int:\n"
-"    ...             return True\n"
-"    ...         else:\n"
-"    ...             return False\n"
-"    ... \n"
-"\n");
-
-
-PyDoc_STRVAR(query_type__doc__,
-"query_type(x, allow_inf=False, allow_nan=False, coerce=False, allowed_types=*, allow_underscores=True)\n"
-"Quickly determine the type that fastnumbers would return for a given input.\n"
-"\n"
-"For string or bytes-like input, the contents of the string will be examined and\n"
-"the type *int* or *float* will be returned if the object contains a representation\n"
-"of an *int* or *float*, respectively. For all other cases, the type of the input\n"
-"object is returned, just like the built-in function *type*.\n"
-"\n"
-"The input may be whitespace-padded.\n"
-"\n"
-"Parameters\n"
-"----------\n"
-"input :\n"
-"    The input of which you wish to query the type fastnumbers might return.\n"
-"allow_inf : bool, optional\n"
-"    If *True*, then the strings 'inf' and 'infinity' will also return *float*.\n"
-"    This check is case-insensitive, and the string may be signed (i.e. '+/-').\n"
-"    The default is *False*.\n"
-"allow_nan : bool, optional\n"
-"    If *True*, then the string 'nan' will also return *float*.\n"
-"    This check is case-insensitive, and the string may be signed (i.e. '+/-').\n"
-"    The default is *False*.\n"
-"coerce : bool, optional\n"
-"    If *True*, then numbers that are given as *float* but could be converted to\n"
-"    an *int* without loss of precision will return type *int* instead of *float*.\n"
-"allowed_types : sequence of types, optional\n"
-"    If given, then only the given types may be returned, and anything else will\n"
-"    return *None*.\n"
-"allow_underscores : bool, optional\n"
-"    Starting with Python 3.6, underscores are allowed in numeric literals\n"
-"    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
-"    what is and is not allowed). You can disable that behavior by setting\n"
-"    this option to *False* - the default is *True*.\n"
-"\n"
-"Returns\n"
-"-------\n"
-"result : type\n"
-"    The type that fastnumbers might return for the given input.\n"
-"\n"
-"See Also\n"
-"--------\n"
-"isreal\n"
-"isfloat\n"
-"isint\n"
-"isintlike\n"
-"\n"
-"Examples\n"
-"--------\n"
-"\n"
-"    >>> from fastnumbers import query_type\n"
-"    >>> query_type('56')\n"
-"    <class 'int'>\n"
-"    >>> query_type('56.07')\n"
-"    <class 'float'>\n"
-"    >>> query_type('56.07 lb')\n"
-"    <class 'str'>\n"
-"    >>> query_type('56.07 lb', allowed_types=(float, int))  # returns None\n"
-"    >>> query_type('56.0')\n"
-"    <class 'float'>\n"
-"    >>> query_type('56.0', coerce=True)\n"
-"    <class 'int'>\n"
-"    >>> query_type(56.07)\n"
-"    <class 'float'>\n"
-"    >>> query_type(56)\n"
-"    <class 'int'>\n"
-"    >>> query_type('nan')\n"
-"    <class 'str'>\n"
-"    >>> query_type('nan', allow_nan=True)\n"
-"    <class 'float'>\n"
-"\n");
-
-
-PyDoc_STRVAR(fastnumbers_int__doc__,
-"int(x=0, base=10)\n"
-"Drop-in but faster replacement for the built-in *int*.\n"
-"\n"
-"Behaves identically to the built-in *int* except for the following:\n"
-"\n"
-"    - Cannot convert from the ``__trunc__`` special method of an object.\n"
-"    - Is implemented as a function, not a class, which means it cannot be\n"
-"      sub-classed, and has no *from_bytes* classmethod.\n"
-"    - You cannot use this function in ``isinstance``.\n"
-"\n"
-"If you need any of the above functionality you can still access the original\n"
-"*int* class through ``builtins``.\n"
-"\n"
-"    >>> from fastnumbers import int\n"
-"    >>> isinstance(9, int) # doctest: +ELLIPSIS\n"
-"    Traceback (most recent call last):\n"
-"        ...\n"
-"    TypeError: ...\n"
-"    >>> import builtins\n"
-"    >>> isinstance(9, builtins.int)\n"
-"    True\n"
-"\n"
-"\n");
-
-
-PyDoc_STRVAR(fastnumbers_float__doc__,
-"float(x=0)\n"
-"Drop-in but faster replacement for the built-in *float*.\n"
-"\n"
-"Behaves identically to the built-in *float* except for the following:\n"
-"\n"
-"    - Is implemented as a function, not a class, which means it cannot be\n"
-"      sub-classed, and has no *fromhex* classmethod.\n"
-"    - A *ValueError* will be raised instead of a *UnicodeEncodeError*\n"
-"      if a partial surrogate is given as input.\n"
-"    - You cannot use this function in ``isinstance``.\n"
-"\n"
-"If you need any of the above functionality you can still access the original\n"
-"*float* class through ``builtins``.\n"
-"\n"
-"    >>> from fastnumbers import float\n"
-"    >>> isinstance(9.4, float)  # doctest: +ELLIPSIS\n"
-"    Traceback (most recent call last):\n"
-"        ...\n"
-"    TypeError: ...\n"
-"    >>> import builtins\n"
-"    >>> isinstance(9.4, builtins.float)\n"
-"    True\n"
-"\n"
-"\n");
-
-
-PyDoc_STRVAR(fastnumbers_real__doc__,
-"real(x=0.0, coerce=True)\n"
-"Convert to *float* or *int*, whichever is most appropriate.\n"
-"\n"
-"If an *int* literal or string containing an *int* is provided,\n"
-"then an *int* will be returned. If a *float* literal or a string\n"
-"containing a non-*int* and non-*complex* number is provided,\n"
-"a *float* will be returned.\n"
-"\n"
-"If *coerce* is *True* (the default), then if a *float* is given\n"
-"that has no decimal places after conversion or only zeros after\n"
-"the decimal point, it will be returned as an *int* instead of a\n"
-"*float*.\n"
-"\n");
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* __DOCSTRINGS */
diff --git a/include/fastnumbers/docstrings.hpp b/include/fastnumbers/docstrings.hpp
new file mode 100644
index 00000000..c6a921a1
--- /dev/null
+++ b/include/fastnumbers/docstrings.hpp
@@ -0,0 +1,1011 @@
+#pragma once
+
+#include <Python.h>
+
+PyDoc_STRVAR(fastnumbers__doc__, "Quickly convert strings to numbers.\n");
+
+PyDoc_STRVAR(
+    fast_real__doc__,
+    "fast_real(x, default=None, raise_on_invalid=False, on_fail=None, nan=None, "
+    "inf=None, coerce=True, allow_underscores=True)\n"
+    "Quickly convert input to an `int` or `float` depending on value.\n"
+    "\n"
+    "Any input that is valid for the built-in `float` or `int` functions will\n"
+    "be converted to either a `float` or `int`. An input of a single numeric\n"
+    "unicode character is also valid. The return value is guaranteed\n"
+    "to be of type `str`, `int`, or `float`.\n"
+    "\n"
+    "If the given input is a string and cannot be converted to a `float` or\n"
+    "`int`, it will be returned as-is unless `default` or `raise_on_invalid`\n"
+    "is given.\n"
+    "\n"
+    "Parameters\n"
+    "----------\n"
+    "input : {str, float, int, long}\n"
+    "    The input you wish to convert to a real number.\n"
+    "default : optional\n"
+    "    This value will be returned instead of the input\n"
+    "    when the input cannot be converted. Has no effect if\n"
+    "    *raise_on_invalid* is *True*.\n"
+    "raise_on_invalid : bool, optional\n"
+    "    If *True*, a `ValueError` will be raised if string input cannot be\n"
+    "    converted to a `float` or `int`. If *False*, the string will be\n"
+    "    returned as-is. The default is *False*.\n"
+    "on_fail : callable, optional\n"
+    "    If given and the *input* cannot be converted, the input will be\n"
+    "    passed to the callable object and its return value will be returned.\n"
+    "    The function expect only one positional argument.\n"
+    "    For backwards-compatability, you may call this option `key` instead\n"
+    "    of `on_fail`, but this is deprecated behavior.\n"
+    "nan : optional\n"
+    "    If the input value is NAN or can be parsed as NAN, return this\n"
+    "    value instead of NAN.\n"
+    "inf : optional\n"
+    "    If the input value is INF or can be parsed as INF, return this\n"
+    "    value instead of INF.\n"
+    "coerce : bool, optional\n"
+    "    If the input can be converted to an `int` without loss of precision\n"
+    "    (even if the input was a `float` or float-containing `str`)\n"
+    "    coerce to an *int* rather than returning a `float`.\n"
+    "allow_underscores : bool, optional\n"
+    "    Starting with Python 3.6, underscores are allowed in numeric literals\n"
+    "    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
+    "    what is and is not allowed). You can disable that behavior by setting\n"
+    "    this option to *False* - the default is *True*.\n"
+    "\n"
+    "Returns\n"
+    "-------\n"
+    "out : {str, float, int}\n"
+    "    If the input could be converted to an `int`, the return type will be\n"
+    "    `int`.\n"
+    "    If the input could be converted to a `float` but not an `int`, the\n"
+    "    return type will be `float`.  Otherwise, the input `str` will be\n"
+    "    returned as-is (if `raise_on_invalid` is *False*) or whatever value\n"
+    "    is assigned to `default` if `default` is not *None*.\n"
+    "\n"
+    "Raises\n"
+    "------\n"
+    "TypeError\n"
+    "    If the input is not one of `str`, `float`, or `int`.\n"
+    "ValueError\n"
+    "    If `raise_on_invalid` is *True*, this will be raised if the input\n"
+    "    string cannot be converted to a `float` or `int`.\n"
+    "\n"
+    "See Also\n"
+    "--------\n"
+    "isreal\n"
+    "real\n"
+    "\n"
+    "Examples\n"
+    "--------\n"
+    "\n"
+    "    >>> from fastnumbers import fast_real\n"
+    "    >>> fast_real('56')\n"
+    "    56\n"
+    "    >>> fast_real('56.0')\n"
+    "    56\n"
+    "    >>> fast_real('56.0', coerce=False)\n"
+    "    56.0\n"
+    "    >>> fast_real('56.07')\n"
+    "    56.07\n"
+    "    >>> fast_real('56.07 lb')\n"
+    "    '56.07 lb'\n"
+    "    >>> fast_real(56.07)\n"
+    "    56.07\n"
+    "    >>> fast_real(56.0)\n"
+    "    56\n"
+    "    >>> fast_real(56.0, coerce=False)\n"
+    "    56.0\n"
+    "    >>> fast_real(56)\n"
+    "    56\n"
+    "    >>> fast_real('invalid', default=50)\n"
+    "    50\n"
+    "    >>> fast_real('invalid', 50)  # 'default' is first optional positional arg\n"
+    "    50\n"
+    "    >>> fast_real('nan')\n"
+    "    nan\n"
+    "    >>> fast_real('nan', nan=0)\n"
+    "    0\n"
+    "    >>> fast_real('56.07', nan=0)\n"
+    "    56.07\n"
+    "    >>> fast_real('56.07 lb', raise_on_invalid=True) #doctest: "
+    "+IGNORE_EXCEPTION_DETAIL\n"
+    "    Traceback (most recent call last):\n"
+    "      ...\n"
+    "    ValueError: could not convert string to float: '56.07 lb'\n"
+    "    >>> fast_real('invalid', on_fail=len)\n"
+    "    7\n"
+    "\n"
+    "Notes\n"
+    "-----\n"
+    "It is roughly equivalent to (but much faster than)\n"
+    "\n"
+    "    >>> def py_fast_real(input, default=None, raise_on_invalid=False,\n"
+    "    ...                  on_fail=None, nan=None, inf=None):\n"
+    "    ...     import math\n"
+    "    ...     try:\n"
+    "    ...         a = float(input)\n"
+    "    ...     except ValueError:\n"
+    "    ...         if raise_on_invalid:\n"
+    "    ...             raise\n"
+    "    ...         elif on_fail is not None:\n"
+    "    ...             return on_fail(input)\n"
+    "    ...         elif default is not None:\n"
+    "    ...             return default\n"
+    "    ...         else:\n"
+    "    ...             return input\n"
+    "    ...     else:\n"
+    "    ...         if nan is not None and math.isnan(a):\n"
+    "    ...             return nan\n"
+    "    ...         elif inf is not None and math.isinf(a):\n"
+    "    ...             return inf\n"
+    "    ...         else:\n"
+    "    ...             return int(a) if a.is_integer() else a\n"
+    "    ... \n"
+    "\n"
+);
+
+PyDoc_STRVAR(
+    fast_float__doc__,
+    "fast_float(x, default=None, raise_on_invalid=False, on_fail=None, nan=None, "
+    "inf=None, allow_underscores=True)\n"
+    "Quickly convert input to a `float`.\n"
+    "\n"
+    "Any input that is valid for the built-in `float` function will\n"
+    "be converted to a `float`. An input of a single numeric\n"
+    "unicode character is also valid. The return value is guaranteed\n"
+    "to be of type `str` or `float`.\n"
+    "\n"
+    "If the given input is a string and cannot be converted to a `float`\n"
+    "it will be returned as-is unless `default` or `raise_on_invalid`\n"
+    "is given.\n"
+    "\n"
+    "Parameters\n"
+    "----------\n"
+    "input : {str, float, int, long}\n"
+    "    The input you wish to convert to a `float`.\n"
+    "default : optional\n"
+    "    This value will be returned instead of the input\n"
+    "    when the input cannot be converted. Has no effect if\n"
+    "    *raise_on_invalid* is *True*.\n"
+    "raise_on_invalid : bool, optional\n"
+    "    If *True*, a `ValueError` will be raised if string input cannot be\n"
+    "    converted to a `float`.  If *False*, the string will be\n"
+    "    returned as-is.  The default is *False*.\n"
+    "on_fail : callable, optional\n"
+    "    If given and the *input* cannot be converted, the input will be\n"
+    "    passed to the callable object and its return value will be returned.\n"
+    "    The function expect only one positional argument.\n"
+    "    For backwards-compatability, you may call this option `key` instead\n"
+    "    of `on_fail`, but this is deprecated behavior.\n"
+    "nan : optional\n"
+    "    If the input value is NAN or can be parsed as NAN, return this\n"
+    "    value instead of NAN.\n"
+    "inf : optional\n"
+    "    If the input value is INF or can be parsed as INF, return this\n"
+    "    value instead of INF.\n"
+    "allow_underscores : bool, optional\n"
+    "    Starting with Python 3.6, underscores are allowed in numeric literals\n"
+    "    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
+    "    what is and is not allowed). You can disable that behavior by setting\n"
+    "    this option to *False* - the default is *True*.\n"
+    "\n"
+    "Returns\n"
+    "-------\n"
+    "out : {str, float}\n"
+    "    If the input could be converted to a `float` the return type will\n"
+    "    be `float`. Otherwise, the input `str` will be returned as-is\n"
+    "    (if `raise_on_invalid` is *False*) or whatever value\n"
+    "    is assigned to *default* if *default* is not *None*.\n"
+    "\n"
+    "Raises\n"
+    "------\n"
+    "TypeError\n"
+    "    If the input is not one of `str`, `float`, or `int`.\n"
+    "ValueError\n"
+    "    If `raise_on_invalid` is *True*, this will be raised if the input\n"
+    "    string cannot be converted to a `float`.\n"
+    "\n"
+    "See Also\n"
+    "--------\n"
+    "isfloat\n"
+    "float\n"
+    "\n"
+    "Examples\n"
+    "--------\n"
+    "\n"
+    "    >>> from fastnumbers import fast_float\n"
+    "    >>> fast_float('56')\n"
+    "    56.0\n"
+    "    >>> fast_float('56.0')\n"
+    "    56.0\n"
+    "    >>> fast_float('56.07')\n"
+    "    56.07\n"
+    "    >>> fast_float('56.07 lb')\n"
+    "    '56.07 lb'\n"
+    "    >>> fast_float(56.07)\n"
+    "    56.07\n"
+    "    >>> fast_float(56)\n"
+    "    56.0\n"
+    "    >>> fast_float('invalid', default=50)\n"
+    "    50\n"
+    "    >>> fast_float('invalid', 50)  # 'default' is first optional positional arg\n"
+    "    50\n"
+    "    >>> fast_float('nan')\n"
+    "    nan\n"
+    "    >>> fast_float('nan', nan=0.0)\n"
+    "    0.0\n"
+    "    >>> fast_float('56.07', nan=0.0)\n"
+    "    56.07\n"
+    "    >>> fast_float('56.07 lb', raise_on_invalid=True) #doctest: "
+    "+IGNORE_EXCEPTION_DETAIL\n"
+    "    Traceback (most recent call last):\n"
+    "      ...\n"
+    "    ValueError: could not convert string to float: '56.07 lb'\n"
+    "    >>> fast_float('invalid', on_fail=len)\n"
+    "    7\n"
+    "\n"
+    "Notes\n"
+    "-----\n"
+    "It is roughly equivalent to (but much faster than)\n"
+    "\n"
+    "    >>> def py_fast_float(input, default=None, raise_on_invalid=False,\n"
+    "    ...                   on_fail=None, nan=None, inf=None):\n"
+    "    ...     try:\n"
+    "    ...         x = float(input)\n"
+    "    ...     except ValueError:\n"
+    "    ...         if raise_on_invalid:\n"
+    "    ...             raise\n"
+    "    ...         elif on_fail is not None:\n"
+    "    ...             return on_fail(input)\n"
+    "    ...         elif default is not None:\n"
+    "    ...             return default\n"
+    "    ...         else:\n"
+    "    ...             return input\n"
+    "    ...     else:\n"
+    "    ...         if nan is not None and math.isnan(x):\n"
+    "    ...             return nan\n"
+    "    ...         elif inf is not None and math.isinf(x):\n"
+    "    ...             return inf\n"
+    "    ...         else:\n"
+    "    ...             return x\n"
+    "    ... \n"
+    "\n"
+);
+
+PyDoc_STRVAR(
+    fast_int__doc__,
+    "fast_int(x, default=None, raise_on_invalid=False, on_fail=None, base=10, "
+    "allow_underscores=True)\n"
+    "Quickly convert input to an `int`.\n"
+    "\n"
+    "Any input that is valid for the built-in `int`\n"
+    "function will be converted to a `int`. An input\n"
+    "of a single digit unicode character is also valid. The return value\n"
+    "is guaranteed to be of type `str` or `int`.\n"
+    "\n"
+    "If the given input is a string and cannot be converted to an `int`\n"
+    "it will be returned as-is unless `default` or `raise_on_invalid`\n"
+    "is given.\n"
+    "\n"
+    "Parameters\n"
+    "----------\n"
+    "input : {str, float, int, long}\n"
+    "    The input you wish to convert to an `int`.\n"
+    "default : optional\n"
+    "    This value will be returned instead of the input\n"
+    "    when the input cannot be converted. Has no effect if\n"
+    "    *raise_on_invalid* is *True*.\n"
+    "raise_on_invalid : bool, optional\n"
+    "    If *True*, a `ValueError` will be raised if string input cannot be\n"
+    "    converted to an `int`. If *False*, the string will be\n"
+    "    returned as-is. The default is *False*.\n"
+    "on_fail : callable, optional\n"
+    "    If given and the *input* cannot be converted, the input will be\n"
+    "    passed to the callable object and its return value will be returned.\n"
+    "    The function expect only one positional argument.\n"
+    "    For backwards-compatability, you may call this option `key` instead\n"
+    "    of `on_fail`, but this is deprecated behavior.\n"
+    "base : int, optional\n"
+    "    Follows the rules of Python's built-in :func:`int`; see it's\n"
+    "    documentation for your Python version. If given, the input\n"
+    "    **must** be of type `str`.\n"
+    "allow_underscores : bool, optional\n"
+    "    Starting with Python 3.6, underscores are allowed in numeric literals\n"
+    "    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
+    "    what is and is not allowed). You can disable that behavior by setting\n"
+    "    this option to *False* - the default is *True*.\n"
+    "\n"
+    "Returns\n"
+    "-------\n"
+    "out : {str, int}\n"
+    "    If the input could be converted to an `int`, the return type will be\n"
+    "    `int`.\n"
+    "    Otherwise, the input `str` will be returned as-is\n"
+    "    (if `raise_on_invalid` is *False*) or whatever value\n"
+    "    is assigned to *default* if *default* is not *None*.\n"
+    "\n"
+    "Raises\n"
+    "------\n"
+    "TypeError\n"
+    "    If the input is not one of `str`, `float`, or `int`.\n"
+    "ValueError\n"
+    "    If `raise_on_invalid` is *True*, this will be raised if the input\n"
+    "    string cannot be converted to an `int`.\n"
+    "\n"
+    "See Also\n"
+    "--------\n"
+    "fast_forceint\n"
+    "isint\n"
+    "int\n"
+    "\n"
+    "Examples\n"
+    "--------\n"
+    "\n"
+    "    >>> from fastnumbers import fast_int\n"
+    "    >>> fast_int('56')\n"
+    "    56\n"
+    "    >>> fast_int('56.0')\n"
+    "    '56.0'\n"
+    "    >>> fast_int('56.07 lb')\n"
+    "    '56.07 lb'\n"
+    "    >>> fast_int(56.07)\n"
+    "    56\n"
+    "    >>> fast_int(56)\n"
+    "    56\n"
+    "    >>> fast_int('invalid', default=50)\n"
+    "    50\n"
+    "    >>> fast_int('invalid', 50)  # 'default' is first optional positional arg\n"
+    "    50\n"
+    "    >>> fast_int('56.07 lb', raise_on_invalid=True) #doctest: "
+    "+IGNORE_EXCEPTION_DETAIL\n"
+    "    Traceback (most recent call last):\n"
+    "      ...\n"
+    "    ValueError: could not convert string to int: '56.07 lb'\n"
+    "    >>> fast_int('invalid', on_fail=len)\n"
+    "    7\n"
+    "\n"
+    "Notes\n"
+    "-----\n"
+    "It is roughly equivalent to (but much faster than)\n"
+    "\n"
+    "    >>> def py_fast_int(input, default=None, raise_on_invalid=False, "
+    "on_fail=None):\n"
+    "    ...     try:\n"
+    "    ...         return int(input)\n"
+    "    ...     except ValueError:\n"
+    "    ...         if raise_on_invalid:\n"
+    "    ...             raise\n"
+    "    ...         elif on_fail is not None:\n"
+    "    ...             return on_fail(input)\n"
+    "    ...         elif default is not None:\n"
+    "    ...             return default\n"
+    "    ...         else:\n"
+    "    ...             return input\n"
+    "    ... \n"
+    "\n"
+);
+
+PyDoc_STRVAR(
+    fast_forceint__doc__,
+    "fast_forceint(x, default=None, raise_on_invalid=False, on_fail=None, "
+    "allow_underscores=True)\n"
+    "Quickly convert input to an `int`, truncating if is a `float`.\n"
+    "\n"
+    "Any input that is valid for the built-in `int`\n"
+    "function will be converted to a `int`. An input\n"
+    "of a single numeric unicode character is also valid. The return value\n"
+    "is guaranteed to be of type `str` or `int`.\n"
+    "\n"
+    "In addition to the above, any input valid for the built-in `float` will\n"
+    "be parsed and the truncated to the nearest integer; for example, '56.07'\n"
+    "will be converted to `56`.\n"
+    "\n"
+    "If the given input is a string and cannot be converted to an `int`\n"
+    "it will be returned as-is unless `default` or `raise_on_invalid`\n"
+    "is given.\n"
+    "\n"
+    "Parameters\n"
+    "----------\n"
+    "input : {str, float, int, long}\n"
+    "    The input you wish to convert to an `int`.\n"
+    "default : optional\n"
+    "    This value will be returned instead of the input\n"
+    "    when the input cannot be converted. Has no effect if\n"
+    "    *raise_on_invalid* is *True*\n"
+    "raise_on_invalid : bool, optional\n"
+    "    If *True*, a `ValueError` will be raised if string input cannot be\n"
+    "    converted to an `int`.  If *False*, the string will be\n"
+    "    returned as-is.  The default is *False*.\n"
+    "on_fail : callable, optional\n"
+    "    If given and the *input* cannot be converted, the input will be\n"
+    "    passed to the callable object and its return value will be returned.\n"
+    "    The function expect only one positional argument.\n"
+    "    For backwards-compatability, you may call this option `key` instead\n"
+    "    of `on_fail`, but this is deprecated behavior.\n"
+    "allow_underscores : bool, optional\n"
+    "    Starting with Python 3.6, underscores are allowed in numeric literals\n"
+    "    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
+    "    what is and is not allowed). You can disable that behavior by setting\n"
+    "    this option to *False* - the default is *True*.\n"
+    "\n"
+    "Returns\n"
+    "-------\n"
+    "out : {str, int}\n"
+    "    If the input could be converted to an `int`, the return type will be\n"
+    "    `int`.\n"
+    "    Otherwise, the input `str` will be returned as-is\n"
+    "    (if `raise_on_invalid` is *False*) or whatever value\n"
+    "    is assigned to *default* if *default* is not *None*.\n"
+    "\n"
+    "Raises\n"
+    "------\n"
+    "TypeError\n"
+    "    If the input is not one of `str`, `float`, or `int`.\n"
+    "ValueError\n"
+    "    If `raise_on_invalid` is *True*, this will be raised if the input\n"
+    "    string cannot be converted to an `int`.\n"
+    "\n"
+    "See Also\n"
+    "--------\n"
+    "fast_int\n"
+    "isintlike\n"
+    "\n"
+    "Examples\n"
+    "--------\n"
+    "\n"
+    "    >>> from fastnumbers import fast_forceint\n"
+    "    >>> fast_forceint('56')\n"
+    "    56\n"
+    "    >>> fast_forceint('56.0')\n"
+    "    56\n"
+    "    >>> fast_forceint('56.07')\n"
+    "    56\n"
+    "    >>> fast_forceint('56.07 lb')\n"
+    "    '56.07 lb'\n"
+    "    >>> fast_forceint(56.07)\n"
+    "    56\n"
+    "    >>> fast_forceint(56)\n"
+    "    56\n"
+    "    >>> fast_forceint('invalid', default=50)\n"
+    "    50\n"
+    "    >>> fast_forceint('invalid', 50)  # 'default' is first optional positional "
+    "arg\n"
+    "    50\n"
+    "    >>> fast_forceint('56.07 lb', raise_on_invalid=True) #doctest: "
+    "+IGNORE_EXCEPTION_DETAIL\n"
+    "    Traceback (most recent call last):\n"
+    "      ...\n"
+    "    ValueError: could not convert string to float: '56.07 lb'\n"
+    "    >>> fast_forceint('invalid', on_fail=len)\n"
+    "    7\n"
+    "\n"
+    "Notes\n"
+    "-----\n"
+    "It is roughly equivalent to (but much faster than)\n"
+    "\n"
+    "    >>> def py_fast_forceint(input, default=None, raise_on_invalid=False, "
+    "on_fail=None):\n"
+    "    ...     try:\n"
+    "    ...         return int(input)\n"
+    "    ...     except ValueError:\n"
+    "    ...         try:\n"
+    "    ...             return int(float(input))\n"
+    "    ...         except ValueError:\n"
+    "    ...             if raise_on_invalid:\n"
+    "    ...                 raise\n"
+    "    ...             elif on_fail is not None:\n"
+    "    ...                 return on_fail(input)\n"
+    "    ...             elif default is not None:\n"
+    "    ...                 return default\n"
+    "    ...             else:\n"
+    "    ...                 return input\n"
+    "    ... \n"
+    "\n"
+);
+
+PyDoc_STRVAR(
+    isreal__doc__,
+    "isreal(x, str_only=False, num_only=False, allow_inf=False, allow_nan=False, "
+    "allow_underscores=True)\n"
+    "Quickly determine if a string is a real number.\n"
+    "\n"
+    "Returns *True* if the input is valid input for the built-in `float` or\n"
+    "`int` functions, or is a single valid numeric unicode character.\n"
+    "\n"
+    "The input may be whitespace-padded.\n"
+    "\n"
+    "Parameters\n"
+    "----------\n"
+    "input :\n"
+    "    The input you wish to test if it is a real number.\n"
+    "str_only : bool, optional\n"
+    "    If *True*, then any non-`str` input will cause this function to return\n"
+    "    *False*. The default is *False*.\n"
+    "num_only : bool, optional\n"
+    "    If *True*, then any `str` input will cause this function to return\n"
+    "    *False*. The default is *False*.\n"
+    "allow_inf : bool, optional\n"
+    "    If *True*, then the strings 'inf' and 'infinity' will also return *True*.\n"
+    "    This check is case-insensitive, and the string may be signed (i.e. '+/-').\n"
+    "    The default is *False*.\n"
+    "allow_nan : bool, optional\n"
+    "    If *True*, then the string 'nan' will also return *True*.\n"
+    "    This check is case-insensitive, and the string may be signed (i.e. '+/-').\n"
+    "    The default is *False*.\n"
+    "allow_underscores : bool, optional\n"
+    "    Starting with Python 3.6, underscores are allowed in numeric literals\n"
+    "    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
+    "    what is and is not allowed). You can disable that behavior by setting\n"
+    "    this option to *False* - the default is *True*.\n"
+    "\n"
+    "Returns\n"
+    "-------\n"
+    "result : bool\n"
+    "    Whether or not the input is a real number.\n"
+    "\n"
+    "See Also\n"
+    "--------\n"
+    "fast_real\n"
+    "\n"
+    "Examples\n"
+    "--------\n"
+    "\n"
+    "    >>> from fastnumbers import isreal\n"
+    "    >>> isreal('56')\n"
+    "    True\n"
+    "    >>> isreal('56.07')\n"
+    "    True\n"
+    "    >>> isreal('56.07', num_only=True)\n"
+    "    False\n"
+    "    >>> isreal('56.07 lb')\n"
+    "    False\n"
+    "    >>> isreal(56.07)\n"
+    "    True\n"
+    "    >>> isreal(56.07, str_only=True)\n"
+    "    False\n"
+    "    >>> isreal(56)\n"
+    "    True\n"
+    "    >>> isreal('nan')\n"
+    "    False\n"
+    "    >>> isreal('nan', allow_nan=True)\n"
+    "    True\n"
+    "\n"
+    "Notes\n"
+    "-----\n"
+    "It is roughly equivalent to (but much faster than)\n"
+    "\n"
+    "    >>> import re\n"
+    "    >>> def py_isreal(input, str_only=False, num_only=False,\n"
+    "    ...               allow_nan=False, allow_inf=False):\n"
+    "    ...     if str_only and type(input) != str:\n"
+    "    ...         return False\n"
+    "    ...     if num_only and type(input) not in (float, int):\n"
+    "    ...         return False\n"
+    "    ...     try:\n"
+    "    ...         x = bool(re.match(r'[-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?$', "
+    "input))\n"
+    "    ...     except TypeError:\n"
+    "    ...         return type(input) in (float, int)\n"
+    "    ...     else:\n"
+    "    ...         if x:\n"
+    "    ...             return True\n"
+    "    ...         elif allow_inf and input.lower().strip().lstrip('-+') in ('inf', "
+    "'infinity'):\n"
+    "    ...             return True\n"
+    "    ...         elif allow_nan and input.lower().strip().lstrip('-+') == 'nan':\n"
+    "    ...             return True\n"
+    "    ...         else:\n"
+    "    ...             return False\n"
+    "    ... \n"
+    "\n"
+);
+
+PyDoc_STRVAR(
+    isfloat__doc__,
+    "isfloat(x, str_only=False, num_only=False, allow_inf=False, allow_nan=False, "
+    "allow_underscores=True)\n"
+    "Quickly determine if a string is a `float`.\n"
+    "\n"
+    "Returns *True* if the input is valid input for the built-in `float`\n"
+    "function, is already a valid `float`, or is a single valid numeric unicode\n"
+    "character. It differs from `isreal` in that an `int` input will return\n"
+    "*False*.\n"
+    "\n"
+    "The input may be whitespace-padded.\n"
+    "\n"
+    "Parameters\n"
+    "----------\n"
+    "input :\n"
+    "    The input you wish to test if it is a `float`.\n"
+    "str_only : bool, optional\n"
+    "    If *True*, then any non-`str` input will cause this function to return\n"
+    "    *False*. The default is *False*.\n"
+    "num_only : bool, optional\n"
+    "    If *True*, then any `str` input will cause this function to return\n"
+    "    *False*. The default is *False*.\n"
+    "allow_inf : bool, optional\n"
+    "    If *True*, then the strings 'inf' and 'infinity' will also return *True*.\n"
+    "    This check is case-insensitive, and the string may be signed (i.e. '+/-').\n"
+    "    The default is *False*.\n"
+    "allow_nan : bool, optional\n"
+    "    If *True*, then the string 'nan' will also return *True*.\n"
+    "    This check is case-insensitive, and the string may be signed (i.e. '+/-').\n"
+    "    The default is *False*.\n"
+    "allow_underscores : bool, optional\n"
+    "    Starting with Python 3.6, underscores are allowed in numeric literals\n"
+    "    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
+    "    what is and is not allowed). You can disable that behavior by setting\n"
+    "    this option to *False* - the default is *True*.\n"
+    "\n"
+    "Returns\n"
+    "-------\n"
+    "result : bool\n"
+    "    Whether or not the input is a `float`.\n"
+    "\n"
+    "See Also\n"
+    "--------\n"
+    "fast_float, isreal\n"
+    "\n"
+    "Examples\n"
+    "--------\n"
+    "\n"
+    "    >>> from fastnumbers import isfloat\n"
+    "    >>> isfloat('56')\n"
+    "    True\n"
+    "    >>> isfloat('56.07')\n"
+    "    True\n"
+    "    >>> isfloat('56.07', num_only=True)\n"
+    "    False\n"
+    "    >>> isfloat('56.07 lb')\n"
+    "    False\n"
+    "    >>> isfloat(56.07)\n"
+    "    True\n"
+    "    >>> isfloat(56.07, str_only=True)\n"
+    "    False\n"
+    "    >>> isfloat(56)\n"
+    "    False\n"
+    "    >>> isfloat('nan')\n"
+    "    False\n"
+    "    >>> isfloat('nan', allow_nan=True)\n"
+    "    True\n"
+    "\n"
+    "Notes\n"
+    "-----\n"
+    "It is roughly equivalent to (but much faster than)\n"
+    "\n"
+    "    >>> import re\n"
+    "    >>> def py_isfloat(input, str_only=False, num_only=False,\n"
+    "    ...                allow_nan=False, allow_inf=False):\n"
+    "    ...     if str_only and type(input) != str:\n"
+    "    ...         return False\n"
+    "    ...     if num_only and type(input) != float:\n"
+    "    ...         return False\n"
+    "    ...     try:\n"
+    "    ...         x = bool(re.match(r'[-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?$', "
+    "input))\n"
+    "    ...     except TypeError:\n"
+    "    ...         return type(input) == float\n"
+    "    ...     else:\n"
+    "    ...         if x:\n"
+    "    ...             return True\n"
+    "    ...         elif allow_inf and input.lower().strip().lstrip('-+') in ('inf', "
+    "'infinity'):\n"
+    "    ...             return True\n"
+    "    ...         elif allow_nan and input.lower().strip().lstrip('-+') == 'nan':\n"
+    "    ...             return True\n"
+    "    ...         else:\n"
+    "    ...             return False\n"
+    "\n"
+);
+
+PyDoc_STRVAR(
+    isint__doc__,
+    "isint(x, str_only=False, num_only=False, allow_underscores=True)\n"
+    "Quickly determine if a string is an `int`.\n"
+    "\n"
+    "Returns *True* if the input is valid input for the built-in `int`\n"
+    "function, is already a valid `int`, or is a single valid digit unicode\n"
+    "character. It differs from `isintlike` in that a `float` input will\n"
+    "return *False* and that `int`-like strings (i.e. '45.0') will return\n"
+    "*False*.\n"
+    "\n"
+    "The input may be whitespace-padded.\n"
+    "\n"
+    "Parameters\n"
+    "----------\n"
+    "input :\n"
+    "    The input you wish to test if it is an `int`.\n"
+    "str_only : bool, optional\n"
+    "    If *True*, then any non-`str` input will cause this function to return\n"
+    "    *False*. The default is *False*.\n"
+    "num_only : bool, optional\n"
+    "    If *True*, then any `str` input will cause this function to return\n"
+    "    *False*. The default is *False*.\n"
+    "allow_underscores : bool, optional\n"
+    "    Starting with Python 3.6, underscores are allowed in numeric literals\n"
+    "    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
+    "    what is and is not allowed). You can disable that behavior by setting\n"
+    "    this option to *False* - the default is *True*.\n"
+    "\n"
+    "Returns\n"
+    "-------\n"
+    "result : bool\n"
+    "    Whether or not the input is an `int`.\n"
+    "\n"
+    "See Also\n"
+    "--------\n"
+    "fast_int, isintlike\n"
+    "\n"
+    "Examples\n"
+    "--------\n"
+    "\n"
+    "    >>> from fastnumbers import isint\n"
+    "    >>> isint('56')\n"
+    "    True\n"
+    "    >>> isint('56', num_only=True)\n"
+    "    False\n"
+    "    >>> isint('56.07')\n"
+    "    False\n"
+    "    >>> isint('56.07 lb')\n"
+    "    False\n"
+    "    >>> isint(56.07)\n"
+    "    False\n"
+    "    >>> isint(56)\n"
+    "    True\n"
+    "    >>> isint(56, str_only=True)\n"
+    "    False\n"
+    "\n"
+    "Notes\n"
+    "-----\n"
+    "It is roughly equivalent to (but much faster than)\n"
+    "\n"
+    "    >>> import re\n"
+    "    >>> def py_isint(input, str_only=False, num_only=False):\n"
+    "    ...     if str_only and type(input) != str:\n"
+    "    ...         return False\n"
+    "    ...     if num_only and type(input) != int:\n"
+    "    ...         return False\n"
+    "    ...     try:\n"
+    "    ...         return bool(re.match(r'[-+]?\\d+$', input))\n"
+    "    ...     except TypeError:\n"
+    "    ...         return False\n"
+    "    ... \n"
+    "\n"
+);
+
+PyDoc_STRVAR(
+    isintlike__doc__,
+    "isintlike(x, str_only=False, num_only=False, allow_underscores=True)\n"
+    "Quickly determine if a string (or object) is an `int` or `int`-like.\n"
+    "\n"
+    "Returns *True* if the input is valid input for the built-in `int`\n"
+    "function, is already a valid `int` or `float`, or is a single valid\n"
+    "numeric unicode character. It differs from `isintlike` in that `int`-like\n"
+    "floats or strings (i.e. '45.0') will return *True*.\n"
+    "\n"
+    "The input may be whitespace-padded.\n"
+    "\n"
+    "Parameters\n"
+    "----------\n"
+    "input :\n"
+    "    The input you wish to test if it is a `int`-like.\n"
+    "\n"
+    "Returns\n"
+    "-------\n"
+    "result : bool\n"
+    "    Whether or not the input is an `int`.\n"
+    "str_only : bool, optional\n"
+    "    If *True*, then any non-`str` input will cause this function to return\n"
+    "    *False*. The default is *False*.\n"
+    "num_only : bool, optional\n"
+    "    If *True*, then any `str` input will cause this function to return\n"
+    "    *False*. The default is *False*.\n"
+    "allow_underscores : bool, optional\n"
+    "    Starting with Python 3.6, underscores are allowed in numeric literals\n"
+    "    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
+    "    what is and is not allowed). You can disable that behavior by setting\n"
+    "    this option to *False* - the default is *True*.\n"
+    "\n"
+    "See Also\n"
+    "--------\n"
+    "fast_forceint\n"
+    "\n"
+    "Examples\n"
+    "--------\n"
+    "\n"
+    "    >>> from fastnumbers import isintlike\n"
+    "    >>> isintlike('56')\n"
+    "    True\n"
+    "    >>> isintlike('56', num_only=True)\n"
+    "    False\n"
+    "    >>> isintlike('56.07')\n"
+    "    False\n"
+    "    >>> isintlike('56.0')\n"
+    "    True\n"
+    "    >>> isintlike('56.07 lb')\n"
+    "    False\n"
+    "    >>> isintlike(56.07)\n"
+    "    False\n"
+    "    >>> isintlike(56.0)\n"
+    "    True\n"
+    "    >>> isintlike(56.0, str_only=True)\n"
+    "    False\n"
+    "    >>> isintlike(56)\n"
+    "    True\n"
+    "\n"
+    "Notes\n"
+    "-----\n"
+    "It is roughly equivalent to (but much faster than)\n"
+    "\n"
+    "    >>> import re\n"
+    "    >>> def py_isintlike(input, str_only=False, num_only=False):\n"
+    "    ...     if str_only and type(input) != str:\n"
+    "    ...         return False\n"
+    "    ...     if num_only and type(input) not in (int, float):\n"
+    "    ...         return False\n"
+    "    ...     try:\n"
+    "    ...         if re.match(r'[-+]?\\d+$', input):\n"
+    "    ...             return True\n"
+    "    ...         elif re.match(r'[-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?$', input):\n"
+    "    ...             return float(input).is_integer()\n"
+    "    ...         else:\n"
+    "    ...             return False\n"
+    "    ...     except TypeError:\n"
+    "    ...         if type(input) == float:\n"
+    "    ...             return input.is_integer()\n"
+    "    ...         elif type(input) == int:\n"
+    "    ...             return True\n"
+    "    ...         else:\n"
+    "    ...             return False\n"
+    "    ... \n"
+    "\n"
+);
+
+PyDoc_STRVAR(
+    query_type__doc__,
+    "query_type(x, allow_inf=False, allow_nan=False, coerce=False, allowed_types=*, "
+    "allow_underscores=True)\n"
+    "Quickly determine the type that fastnumbers would return for a given input.\n"
+    "\n"
+    "For string or bytes-like input, the contents of the string will be examined and\n"
+    "the type *int* or *float* will be returned if the object contains a "
+    "representation\n"
+    "of an *int* or *float*, respectively. For all other cases, the type of the input\n"
+    "object is returned, just like the built-in function *type*.\n"
+    "\n"
+    "The input may be whitespace-padded.\n"
+    "\n"
+    "Parameters\n"
+    "----------\n"
+    "input :\n"
+    "    The input of which you wish to query the type fastnumbers might return.\n"
+    "allow_inf : bool, optional\n"
+    "    If *True*, then the strings 'inf' and 'infinity' will also return *float*.\n"
+    "    This check is case-insensitive, and the string may be signed (i.e. '+/-').\n"
+    "    The default is *False*.\n"
+    "allow_nan : bool, optional\n"
+    "    If *True*, then the string 'nan' will also return *float*.\n"
+    "    This check is case-insensitive, and the string may be signed (i.e. '+/-').\n"
+    "    The default is *False*.\n"
+    "coerce : bool, optional\n"
+    "    If *True*, then numbers that are given as *float* but could be converted to\n"
+    "    an *int* without loss of precision will return type *int* instead of *float*.\n"
+    "allowed_types : sequence of types, optional\n"
+    "    If given, then only the given types may be returned, and anything else will\n"
+    "    return *None*.\n"
+    "allow_underscores : bool, optional\n"
+    "    Starting with Python 3.6, underscores are allowed in numeric literals\n"
+    "    and in strings passed to `int` or `float` (see PEP 515 for details on\n"
+    "    what is and is not allowed). You can disable that behavior by setting\n"
+    "    this option to *False* - the default is *True*.\n"
+    "\n"
+    "Returns\n"
+    "-------\n"
+    "result : type\n"
+    "    The type that fastnumbers might return for the given input.\n"
+    "\n"
+    "See Also\n"
+    "--------\n"
+    "isreal\n"
+    "isfloat\n"
+    "isint\n"
+    "isintlike\n"
+    "\n"
+    "Examples\n"
+    "--------\n"
+    "\n"
+    "    >>> from fastnumbers import query_type\n"
+    "    >>> query_type('56')\n"
+    "    <class 'int'>\n"
+    "    >>> query_type('56.07')\n"
+    "    <class 'float'>\n"
+    "    >>> query_type('56.07 lb')\n"
+    "    <class 'str'>\n"
+    "    >>> query_type('56.07 lb', allowed_types=(float, int))  # returns None\n"
+    "    >>> query_type('56.0')\n"
+    "    <class 'float'>\n"
+    "    >>> query_type('56.0', coerce=True)\n"
+    "    <class 'int'>\n"
+    "    >>> query_type(56.07)\n"
+    "    <class 'float'>\n"
+    "    >>> query_type(56)\n"
+    "    <class 'int'>\n"
+    "    >>> query_type('nan')\n"
+    "    <class 'str'>\n"
+    "    >>> query_type('nan', allow_nan=True)\n"
+    "    <class 'float'>\n"
+    "\n"
+);
+
+PyDoc_STRVAR(
+    fastnumbers_int__doc__,
+    "int(x=0, base=10)\n"
+    "Drop-in but faster replacement for the built-in *int*.\n"
+    "\n"
+    "Behaves identically to the built-in *int* except for the following:\n"
+    "\n"
+    "    - Cannot convert from the ``__trunc__`` special method of an object.\n"
+    "    - Is implemented as a function, not a class, which means it cannot be\n"
+    "      sub-classed, and has no *from_bytes* classmethod.\n"
+    "    - You cannot use this function in ``isinstance``.\n"
+    "\n"
+    "If you need any of the above functionality you can still access the original\n"
+    "*int* class through ``builtins``.\n"
+    "\n"
+    "    >>> from fastnumbers import int\n"
+    "    >>> isinstance(9, int) # doctest: +ELLIPSIS\n"
+    "    Traceback (most recent call last):\n"
+    "        ...\n"
+    "    TypeError: ...\n"
+    "    >>> import builtins\n"
+    "    >>> isinstance(9, builtins.int)\n"
+    "    True\n"
+    "\n"
+    "\n"
+);
+
+PyDoc_STRVAR(
+    fastnumbers_float__doc__,
+    "float(x=0)\n"
+    "Drop-in but faster replacement for the built-in *float*.\n"
+    "\n"
+    "Behaves identically to the built-in *float* except for the following:\n"
+    "\n"
+    "    - Is implemented as a function, not a class, which means it cannot be\n"
+    "      sub-classed, and has no *fromhex* classmethod.\n"
+    "    - A *ValueError* will be raised instead of a *UnicodeEncodeError*\n"
+    "      if a partial surrogate is given as input.\n"
+    "    - You cannot use this function in ``isinstance``.\n"
+    "\n"
+    "If you need any of the above functionality you can still access the original\n"
+    "*float* class through ``builtins``.\n"
+    "\n"
+    "    >>> from fastnumbers import float\n"
+    "    >>> isinstance(9.4, float)  # doctest: +ELLIPSIS\n"
+    "    Traceback (most recent call last):\n"
+    "        ...\n"
+    "    TypeError: ...\n"
+    "    >>> import builtins\n"
+    "    >>> isinstance(9.4, builtins.float)\n"
+    "    True\n"
+    "\n"
+    "\n"
+);
+
+PyDoc_STRVAR(
+    fastnumbers_real__doc__,
+    "real(x=0.0, coerce=True)\n"
+    "Convert to *float* or *int*, whichever is most appropriate.\n"
+    "\n"
+    "If an *int* literal or string containing an *int* is provided,\n"
+    "then an *int* will be returned. If a *float* literal or a string\n"
+    "containing a non-*int* and non-*complex* number is provided,\n"
+    "a *float* will be returned.\n"
+    "\n"
+    "If *coerce* is *True* (the default), then if a *float* is given\n"
+    "that has no decimal places after conversion or only zeros after\n"
+    "the decimal point, it will be returned as an *int* instead of a\n"
+    "*float*.\n"
+    "\n"
+);
diff --git a/include/fastnumbers/evaluator.hpp b/include/fastnumbers/evaluator.hpp
new file mode 100644
index 00000000..1e07ef57
--- /dev/null
+++ b/include/fastnumbers/evaluator.hpp
@@ -0,0 +1,356 @@
+#pragma once
+
+#include <climits>
+
+#include <Python.h>
+
+#include "fastnumbers/parser.hpp"
+#include "fastnumbers/payload.hpp"
+#include "fastnumbers/user_options.hpp"
+
+/// The conversion the user has requested
+enum class UserType {
+    REAL, ///< Convert to/check a real
+    FLOAT, ///< Convert to/check a float
+    INT, ///< Convert to/check an int
+    INTLIKE, ///< Check int-like
+    FORCEINT, ///< Force conversion to int
+};
+
+/**
+ * \class Evaluator
+ * \brief Evaluate the contents of a Python object
+ */
+template <typename ParserT>
+class Evaluator {
+public:
+    /// Constructor from a Python object
+    Evaluator(PyObject* obj, const UserOptions& options, ParserT& parser)
+        : m_obj(obj)
+        , m_parser(parser)
+        , m_options(options)
+    {
+        Py_XINCREF(m_obj);
+        if (m_parser.parser_type() == ParserType::NUMERIC) {
+            m_options.set_nan_allowed(true);
+            m_options.set_inf_allowed(true);
+        }
+    }
+
+    // Other constructors, destructors, and assignment
+    Evaluator(const Evaluator&) = delete;
+    Evaluator(Evaluator&&) = delete;
+    Evaluator& operator=(const Evaluator&) = delete;
+    ~Evaluator() { Py_XDECREF(m_obj); }
+
+    /// Assign a new object to analyze
+    void set_object(PyObject* obj)
+    {
+        Py_XDECREF(m_obj);
+        m_obj = obj;
+        Py_XINCREF(m_obj);
+    }
+
+    /// Access the user-given options for evaluating
+    const UserOptions& options() const { return m_options; }
+
+    /// Return the parser type currenly associated with the Evaluator
+    ParserType parser_type() const { return m_parser.parser_type(); }
+
+    /// Was the passed Python object of the correct type?
+    bool is_type(const UserType ntype) const
+    {
+        // Dispatch to the appropriate parser function based on requested type
+        switch (ntype) {
+        case UserType::REAL:
+            return (options().allow_nan() && m_parser.is_nan())
+                || (options().allow_inf() && m_parser.is_infinity())
+                || m_parser.is_real();
+
+        case UserType::FLOAT:
+            return (options().allow_nan() && m_parser.is_nan())
+                || (options().allow_inf() && m_parser.is_infinity())
+                || m_parser.is_float();
+
+        case UserType::INT:
+            return m_parser.is_int();
+
+        case UserType::INTLIKE:
+        case UserType::FORCEINT:
+            return m_parser.is_intlike();
+
+        default:
+            Py_UNREACHABLE();
+        }
+    }
+
+    /// Is the stored type a float? Account for nan_action and inf_action.
+    bool type_is_float() const
+    {
+        return (options().allow_nan() && m_parser.is_nan())
+            || (options().allow_inf() && m_parser.is_infinity()) || m_parser.is_float();
+    }
+
+    /// Is the stored type an integer? If coerce is true, is the type intlike?
+    bool type_is_int() const
+    {
+        return options().allow_coerce() ? m_parser.is_intlike() : m_parser.is_int();
+    }
+
+    /**
+     * \brief Convert the stored object to the desired number type
+     *
+     * Use the appropriate error handling on error.
+     *
+     * \param ntype PyNumberType indicating the desired type to check
+     * \return Payload
+     */
+    Payload as_type(const UserType ntype)
+    {
+        // Send to the appropriate convenience function based on the found type
+        switch (parser_type()) {
+        case ParserType::NUMERIC:
+            return from_numeric_as_type(ntype);
+
+        case ParserType::UNICODE:
+            if (!options().allow_unicode()) {
+                return typed_error(ntype, false);
+            }
+            /* DELIBERATE FALL-THROUGH */
+        case ParserType::CHARACTER:
+            return from_text_as_type(ntype);
+
+        case ParserType::UNKNOWN:
+        default:
+            break;
+        }
+
+        // If here, the input type is not valid
+        return typed_error(ntype);
+    }
+
+private:
+    /// The Python object under evaluation
+    PyObject* m_obj;
+
+    /// A Parser object used for evaluating the Python object
+    ParserT& m_parser;
+
+    /// Hold the evaluator options
+    UserOptions m_options;
+
+private:
+    /// Logic for evaluating a numeric python object
+    Payload from_numeric_as_type(const UserType ntype)
+    {
+        // If not a numeric type it is a type error
+        if (m_parser.not_float_or_int() && !m_parser.is_user_numeric()) {
+            return typed_error(ntype);
+        }
+
+        // Otherwise, tell the downstream parser what action to take based
+        // on the user requested type
+        switch (ntype) {
+        case UserType::REAL:
+            if (options().allow_coerce() && m_parser.is_intlike()) {
+                return Payload(m_parser.as_pyint());
+            } else if (m_parser.is_nan() || m_parser.is_infinity()) {
+                return Payload(handle_nan_and_inf());
+            } else if (m_parser.is_user_numeric()) {
+                if (m_parser.is_user_numeric_float()) {
+                    return Payload(m_parser.as_pyfloat());
+                } else {
+                    return Payload(m_parser.as_pyint());
+                }
+            } else {
+                Py_IncRef(m_obj);
+                return Payload(m_obj);
+            }
+
+        case UserType::FLOAT:
+            if (m_parser.is_nan() || m_parser.is_infinity()) {
+                return Payload(handle_nan_and_inf());
+            } else {
+                return Payload(m_parser.as_pyfloat());
+            }
+
+        case UserType::INT:
+        case UserType::INTLIKE:
+        case UserType::FORCEINT:
+            if (!options().is_default_base()) {
+                return Payload(ActionType::ERROR_INVALID_BASE);
+            }
+            return Payload(m_parser.as_pyint());
+
+        default:
+            Py_UNREACHABLE();
+        }
+    }
+
+    /// Logic for evaluating a text python object
+    Payload from_text_as_type(const UserType ntype)
+    {
+        switch (ntype) {
+        case UserType::REAL:
+        case UserType::INTLIKE:
+        case UserType::FORCEINT:
+            // REAL will only try to coerce to integer... the others will force
+            return from_text_as_int_or_float(ntype != UserType::REAL);
+
+        case UserType::FLOAT:
+            return from_text_as_float();
+
+        case UserType::INT:
+            return from_text_as_int();
+
+        default:
+            Py_UNREACHABLE();
+        }
+    }
+
+    /// Logic for evaluating a text python object as a float or integer
+    Payload from_text_as_int_or_float(const bool force_int)
+    {
+        // Integers are returned as-is
+        // NaN and infinity are illegal with force_int
+        // Otherwise, grab as a float and convert to int if required
+        if (m_parser.is_int()) {
+            return from_text_as_int();
+
+        } else if (force_int && (m_parser.is_infinity() || m_parser.is_nan())) {
+            return Payload(ActionType::ERROR_INVALID_INT);
+
+        } else {
+            Payload payload = from_text_as_float();
+
+            // If the returned value is a double or a python float, annotate
+            // whether it should be an integer and then return.
+            const bool is_python = payload.get_action() == ActionType::PY_OBJECT
+                && payload.to_pyobject() != nullptr;
+            if (is_python) {
+                const double value = PyFloat_AS_DOUBLE(payload.to_pyobject());
+                Py_DECREF(payload.to_pyobject());
+                return Payload(
+                    value,
+                    force_int
+                        || (options().allow_coerce() && Parser::float_is_intlike(value))
+                );
+            }
+
+            // Otherwise return the payload as-is.
+            return payload;
+        }
+    }
+
+    /// Logic for evaluating a text python object as a float
+    Payload from_text_as_float()
+    {
+        // Special-case handling of infinity and NaN
+        if (m_parser.is_infinity()) {
+            return Payload(inf_action(m_parser.is_negative()));
+        } else if (m_parser.is_nan()) {
+            return Payload(nan_action(m_parser.is_negative()));
+        }
+
+        // Otherwise, attempt to naively parse if possible, otherwise
+        // use the python conversion function
+        const double result = m_parser.as_float();
+        if (m_parser.errored()) {
+            return m_parser.potential_overflow()
+                ? to_pyfloat()
+                : Payload(ActionType::ERROR_INVALID_FLOAT);
+        }
+
+        // Successful naive conversion
+        return Payload(result);
+    }
+
+    /// Logic for evaluating a text python object as an int
+    Payload from_text_as_int()
+    {
+        // We use python to convert non-base-10 integer strings.
+        // Some strings are not allowed to use an explict base,
+        // so check that first.
+        if (m_parser.options().get_base() != 10) {
+            if (m_parser.illegal_explicit_base()) {
+                return Payload(ActionType::ERROR_ILLEGAL_EXPLICIT_BASE);
+            }
+            return to_pyint();
+        }
+
+        // Otherwise, attempt to naively parse if possible, otherwise
+        // use the python conversion function
+        const long result = m_parser.as_int();
+        if (m_parser.errored()) {
+            return m_parser.potential_overflow()
+                ? to_pyint()
+                : Payload(ActionType::ERROR_INVALID_INT);
+        }
+
+        // Successful naive conversion
+        return Payload(result);
+    }
+
+    /// Return an error due to a bad type
+    static Payload typed_error(const UserType ntype, const bool type = true)
+    {
+        if (ntype == UserType::REAL || ntype == UserType::FLOAT) {
+            if (type) {
+                return Payload(ActionType::ERROR_BAD_TYPE_FLOAT);
+            } else {
+                return Payload(ActionType::ERROR_INVALID_FLOAT);
+            }
+        } else {
+            if (type) {
+                return Payload(ActionType::ERROR_BAD_TYPE_INT);
+            } else {
+                return Payload(ActionType::ERROR_INVALID_INT);
+            }
+        }
+    }
+
+    /// Return the correct action if an INF was found
+    static ActionType inf_action(const bool is_negative)
+    {
+        return is_negative ? ActionType::NEG_INF_ACTION : ActionType::INF_ACTION;
+    }
+
+    /// Return the correct action if an NAN was found
+    static ActionType nan_action(const bool is_negative)
+    {
+        return is_negative ? ActionType::NEG_NAN_ACTION : ActionType::NAN_ACTION;
+    }
+
+    /// Helper to properly respond to NaN and infinity
+    ActionType handle_nan_and_inf()
+    {
+        // Exctract the double from the parser, accounting for errors
+        const double value = m_parser.as_float();
+        if (m_parser.errored()) {
+            return ActionType::ERROR_BAD_TYPE_FLOAT;
+        }
+
+        // Assume infinity or NaN.
+        return m_parser.is_nan() ? nan_action(value < 0.0) : inf_action(value < 0.0);
+    }
+
+    /// Helper function to convert to Python float objects
+    Payload to_pyfloat()
+    {
+        PyObject* result = m_parser.as_pyfloat();
+        if (m_parser.errored()) {
+            return Payload(ActionType::ERROR_INVALID_FLOAT);
+        }
+        return Payload(result);
+    }
+
+    /// Helper function to convert to Python int objects
+    Payload to_pyint()
+    {
+        PyObject* result = m_parser.as_pyint();
+        if (m_parser.errored()) {
+            return Payload(ActionType::ERROR_INVALID_INT);
+        }
+        return Payload(result);
+    }
+};
diff --git a/include/fastnumbers/extractor.hpp b/include/fastnumbers/extractor.hpp
new file mode 100644
index 00000000..ca0bdb5f
--- /dev/null
+++ b/include/fastnumbers/extractor.hpp
@@ -0,0 +1,124 @@
+#pragma once
+
+#include <climits>
+#include <cstddef>
+
+#include <Python.h>
+
+#include "fastnumbers/parser.hpp"
+#include "fastnumbers/payload.hpp"
+
+/**
+ * \class TextExtractor
+ * \brief Extract text data from an object
+ */
+class TextExtractor {
+public:
+    /// Constructor from a Python object
+    TextExtractor(PyObject* obj, Buffer& buffer)
+        : m_obj(obj)
+        , m_char_buffer(buffer)
+        , m_str(nullptr)
+        , m_str_len(0)
+        , m_uchar(0)
+        , m_negative(false)
+        , m_explicit_base_allowed(true)
+    {
+        Py_XINCREF(m_obj);
+        PyNumberMethods* nmeth = Py_TYPE(m_obj)->tp_as_number;
+        const bool user_numeric
+            = nmeth && (nmeth->nb_index || nmeth->nb_int || nmeth->nb_float);
+        if (!user_numeric) {
+            extract_string_data();
+        }
+    }
+
+    // Other constructors, destructors, and assignment
+    TextExtractor() = delete;
+    TextExtractor(const TextExtractor&) = delete;
+    TextExtractor(TextExtractor&&) = delete;
+    TextExtractor& operator=(const TextExtractor&) = delete;
+    ~TextExtractor() { Py_XDECREF(m_obj); }
+
+    /// Is text stored in this extractor?
+    bool is_text() const { return m_str != nullptr; }
+
+    /// Is a unicode character in this extractor?
+    bool is_unicode_character() const { return m_uchar != 0; }
+
+    /// Is there no text?
+    bool is_non_text() const { return !(is_text() || is_unicode_character()); }
+
+    /**
+     * \brief Return a CharacterParser for the conained data
+     *
+     * Call is_text first to ensue it is valid
+     * to create this parser.
+     *
+     * \param options A UserOptions instance containing the options
+     *                specified by the user.
+     *
+     * \return CharacterParser
+     */
+    CharacterParser text_parser(const UserOptions& options) const
+    {
+        return CharacterParser(m_str, m_str_len, options, m_explicit_base_allowed);
+    }
+
+    /**
+     * \brief Return a UnicodeParser for the conained data
+     *
+     * Call is_unicode_character first to ensue it is valid
+     * to create this parser.
+     *
+     * \param options A UserOptions instance containing the options
+     *                specified by the user.
+     *
+     * \return UnicodeParser
+     */
+    UnicodeParser unicode_char_parser(const UserOptions& options) const
+    {
+        return UnicodeParser(m_uchar, m_negative, options);
+    }
+
+private:
+    /// The Python object under evaluation
+    PyObject* m_obj;
+
+    /// Buffer object into which to store character data
+    Buffer& m_char_buffer;
+
+    /// Pointer to string data
+    const char* m_str;
+
+    /// The length of the string
+    std::size_t m_str_len;
+
+    /// A single unicode characgter that is read
+    Py_UCS4 m_uchar;
+
+    /// Whether or not the unicode character was negative
+    bool m_negative;
+
+    /// Whether or not an explict base is allowed for text processing
+    bool m_explicit_base_allowed;
+
+private:
+    /// Generate a text parsing Parser object
+    void extract_string_data();
+
+    /// Obtain character data from a Python unicode object
+    bool extract_from_unicode();
+
+    /// Obtain character data from a Python bytes object
+    bool extract_from_bytes();
+
+    /// Obtain character data from a Python bytearray object
+    bool extract_from_bytearray();
+
+    /// Obtain character data from a Python buffer object
+    bool extract_from_buffer();
+
+    /// Parse unicode data and convert to character data
+    bool parse_unicode_to_char();
+};
diff --git a/include/fastnumbers/fn_bool.h b/include/fastnumbers/fn_bool.h
deleted file mode 100644
index e0e0b6da..00000000
--- a/include/fastnumbers/fn_bool.h
+++ /dev/null
@@ -1,50 +0,0 @@
-#ifndef __FN_BOOL
-#define __FN_BOOL
-
-#ifndef __cplusplus
-
-    /* If using library in C, use stdbool.h if available */
-    #if __STDC_VERSION__ >= 199901L
-        #include <stdbool.h>
-    #else
-
-        #ifndef __GNUC__
-            /* _Bool builtin type is included in GCC */
-            /* ISO C Standard: 5.2.5 An object declared as
-            type _Bool is large enough to store
-            the values 0 and 1. */
-            /* We choose 8 bit to match C++ */
-            /* It must also promote to integer */
-            #ifdef _MSC_VER
-                #if _MSC_VER < 1800
-                    #if _MSC_VER >= 1600
-                        #include <stdint.h>
-                    #else
-                        typedef __int8 int8_t;
-                    #endif
-                    typedef int8_t _Bool;
-                #endif
-            #else
-                typedef int8_t _Bool;
-            #endif
-        #endif /* __GNUC__ */
-
-        /* ISO C Standard: 7.16 Boolean type */
-        #ifndef bool
-            #define bool _Bool
-        #endif
-        #ifndef true
-            #define true 1
-        #endif
-        #ifndef false
-            #define false 0
-        #endif
-        #ifndef __bool_true_false_are_defined
-            #define __bool_true_false_are_defined 1
-        #endif
-
-    #endif /* __STDC_VERSION__ >= 199901L */
-
-#endif /* __cplusplus */
-
-#endif /* __FN_BOOL */
diff --git a/include/fastnumbers/numbers.h b/include/fastnumbers/numbers.h
deleted file mode 100644
index 536bfc3c..00000000
--- a/include/fastnumbers/numbers.h
+++ /dev/null
@@ -1,74 +0,0 @@
-#ifndef __FN_NUMBER_HANDLING
-#define __FN_NUMBER_HANDLING
-
-/*
- * Master header for all number handling source files.
- */
-
-#include <Python.h>
-#include <limits.h>
-#include "fastnumbers/fn_bool.h"
-#include "fastnumbers/options.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/*
- * Copy of _Py_dg_stdnan from the Python code base.
- * Needed to generate NaN on Windows.
- */
-double
-compat_generate_nan(int sign);
-
-/* All the awesome MACROS */
-
-#define PyNumber_IsNAN(pynum) (PyFloat_Check(pynum) && Py_IS_NAN(PyFloat_AS_DOUBLE(pynum)))
-#define PyNumber_IsINF(pynum) (PyFloat_Check(pynum) && Py_IS_INFINITY(PyFloat_AS_DOUBLE(pynum)))
-
-/* Return NaN correctly on this system. */
-#if !defined(PY_NO_SHORT_FLOAT_REPR)
-#define PyFloat_from_NaN(negative) PyFloat_FromDouble(compat_generate_nan(negative));
-#else
-#define PyFloat_from_NaN(negative) \
-    (negative) ? PyFloat_FromDouble(-Py_NAN) : PyFloat_FromDouble(Py_NAN);
-#endif
-
-/* Quickies for raising errors. Try to mimic what Python would say. */
-#define FN_FLOAT_MSG "could not convert string to float: %R"
-#define FN_INT_MSG "invalid literal for int() with base %d: %R"
-
-#define SET_ERR_INVALID_INT(o)                              \
-    if (Options_Should_Raise(o))                            \
-        PyErr_Format(PyExc_ValueError,                      \
-                     FN_INT_MSG,                            \
-                     (o)->base == INT_MIN ? 10 : (o)->base, \
-                     (o)->input)
-#define SET_ERR_INVALID_FLOAT(o) \
-    if (Options_Should_Raise(o)) \
-        PyErr_Format(PyExc_ValueError, FN_FLOAT_MSG, (o)->input)
-#define SET_ILLEGAL_BASE_ERROR(o)        \
-    if (Options_Should_Raise(o))         \
-        PyErr_SetString(PyExc_TypeError, \
-                        "int() can't convert non-string with explicit base");
-
-/* Declarations */
-
-PyObject *
-PyFloat_to_PyInt(PyObject *fobj, const Options *options);
-
-bool
-PyFloat_is_Intlike(PyObject *obj);
-
-PyObject *
-PyNumber_to_PyNumber(PyObject *obj, const PyNumberType type,
-                     const Options *options);
-
-bool
-PyNumber_is_type(PyObject *obj, const PyNumberType type);
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* __FN_NUMBER_HANDLING */
diff --git a/include/fastnumbers/objects.h b/include/fastnumbers/objects.h
deleted file mode 100644
index dfe56892..00000000
--- a/include/fastnumbers/objects.h
+++ /dev/null
@@ -1,32 +0,0 @@
-#ifndef __FN_OBJECT_HANDLING
-#define __FN_OBJECT_HANDLING
-
-/*
- * Master header for all general object handling source files.
- */
-
-#include <Python.h>
-#include "fastnumbers/options.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* Declarations */
-
-PyObject *
-PyObject_to_PyNumber(PyObject *obj, const PyNumberType type,
-                     const Options *options);
-
-PyObject *
-PyObject_is_number(PyObject *obj, const PyNumberType type,
-                   const Options *options);
-
-PyObject *
-PyObject_contains_type(PyObject *obj, const Options *options);
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* __FN_OBJECT_HANDLING */
diff --git a/include/fastnumbers/options.h b/include/fastnumbers/options.h
deleted file mode 100644
index 3739a782..00000000
--- a/include/fastnumbers/options.h
+++ /dev/null
@@ -1,107 +0,0 @@
-#ifndef FN_OPTIONS
-#define FN_OPTIONS
-
-#include <Python.h>
-#include <limits.h>
-#include "fastnumbers/fn_bool.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* Selector for the type of number to check/convert. */
-typedef enum PyNumberType { REAL, FLOAT, INT, INTLIKE, FORCEINT } PyNumberType;
-
-/* This struct holds all the user options.
- * Makes adding new future options easier to manage.
- */
-typedef struct Options {
-    PyObject *retval;
-    PyObject *input;
-    PyObject *on_fail;
-    PyObject *handle_inf;
-    PyObject *handle_nan;
-    int coerce;             /* use int instead of bool because   */
-    int num_only;           /* PyArg_ParseTupleAndKeywords       */
-    int str_only;           /* does not support 'bool' type with */
-    int allow_underscores;  /* the "p" converter.                */
-    bool allow_uni;
-    int base;
-} Options;
-
-/* Convenience for initializing.
- */
-#define init_Options_convert {     \
-        .retval = NULL,            \
-        .input = NULL,             \
-        .on_fail = NULL,           \
-        .handle_inf = NULL,        \
-        .handle_nan = NULL,        \
-        .coerce = true,            \
-        .num_only = false,         \
-        .str_only = false,         \
-        .allow_underscores = true, \
-        .allow_uni = true,         \
-        .base = INT_MIN,           \
-    }
-#define init_Options_check {       \
-        .retval = Py_None,         \
-        .input = NULL,             \
-        .on_fail = NULL,           \
-        .handle_inf = Py_False,    \
-        .handle_nan = Py_False,    \
-        .coerce = true,            \
-        .num_only = false,         \
-        .str_only = false,         \
-        .allow_underscores = true, \
-        .allow_uni = true,         \
-        .base = INT_MIN,           \
-    }
-
-/* Some query MACROs. Each expects a pointer. */
-#define Options_Coerce_True(o) ((o)->coerce)
-#define Options_Has_NaN_Sub(o) ((o)->handle_nan != NULL)
-#define Options_Has_INF_Sub(o) ((o)->handle_inf != NULL)
-#define Options_Return_NaN_Sub(o) (Py_INCREF((o)->handle_nan), (o)->handle_nan)
-#define Options_Return_INF_Sub(o) (Py_INCREF((o)->handle_inf), (o)->handle_inf)
-#define Options_Should_Raise(o) ((o)->retval == NULL)
-#define Options_Default_Base(o) ((o)->base == INT_MIN)
-#define Options_Allow_UnicodeCharacter(o) (o)->allow_uni
-#define Options_Allow_Infinity(o) PyObject_IsTrue((o)->handle_inf)
-#define Options_Allow_NAN(o) PyObject_IsTrue((o)->handle_nan)
-#define Options_Allow_Underscores(o) ((o)->allow_underscores)
-#define Options_String_Only(o) ((o)->str_only)
-#define Options_Number_Only(o) ((o)->num_only)
-
-/* Set allow unicode. */
-#define Options_Set_Disallow_UnicodeCharacter(o) \
-    do {                        \
-        (o)->retval = NULL;     \
-        (o)->allow_uni = false; \
-    } while (0)
-
-/* MACRO to return the correct result based on user-input.
- * Expects the Options struct as a pointer.
- */
-#define Options_Return_Correct_Result_On_Error(o)                     \
-    (Options_Should_Raise(o) ?                                        \
-     NULL :                                                           \
-     ((o)->on_fail != NULL ?                                          \
-      PyObject_CallFunctionObjArgs((o)->on_fail, (o)->retval, NULL) : \
-      (Py_INCREF((o)->retval), (o)->retval)                           \
-     ))
-
-/* MACRO to set the correct return value based on given input.
- * Expects the Options struct NOT as a pointer.
- */
-#define Options_Set_Return_Value(o, input, default_value, raise) \
-    (o).input = (input); \
-    (o).retval = (raise) \
-                 ? NULL \
-                 : (((o).on_fail != NULL || default_value == NULL) ? input : default_value)
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* FN_OPTIONS */
\ No newline at end of file
diff --git a/include/fastnumbers/parser.hpp b/include/fastnumbers/parser.hpp
new file mode 100644
index 00000000..a350bd76
--- /dev/null
+++ b/include/fastnumbers/parser.hpp
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "fastnumbers/parser/base.hpp"
+#include "fastnumbers/parser/buffer.hpp"
+#include "fastnumbers/parser/character.hpp"
+#include "fastnumbers/parser/numeric.hpp"
+#include "fastnumbers/parser/unicode.hpp"
diff --git a/include/fastnumbers/parser/base.hpp b/include/fastnumbers/parser/base.hpp
new file mode 100644
index 00000000..ae4234b3
--- /dev/null
+++ b/include/fastnumbers/parser/base.hpp
@@ -0,0 +1,215 @@
+#pragma once
+
+#include <cmath>
+#include <cstring>
+#include <stdexcept>
+
+#include <Python.h>
+
+#include "fastnumbers/user_options.hpp"
+
+/// Possible types of Parser objects
+enum class ParserType {
+    NUMERIC, ///< The parser is handling numeric Python objects
+    UNICODE, ///< The parser is handling single unicode characters
+    CHARACTER, ///< The parser is handling C-style character arrays
+    UNKNOWN, ///< The incoming object unknown to the parser
+};
+
+/**
+ * \class Parser
+ * \brief Flexible parser of various possible Python input types
+ *
+ * Parses the input as a number. Intended to be used as a base class
+ * for more specialized parsers.
+ */
+class Parser {
+public:
+    // Constructors, destructors, and assignment
+    // There is no constructor with arguments
+    Parser(const UserOptions& options)
+        : Parser(ParserType::UNKNOWN, options)
+    { }
+    Parser(const Parser&) = default;
+    Parser(Parser&&) = default;
+    Parser& operator=(const Parser&) = default;
+    virtual ~Parser() = default;
+
+    /// What type of parser is this?
+    ParserType parser_type() const { return m_ptype; };
+
+    /// Whether the last conversion encountered an error
+    bool errored() const { return m_error_type != ErrorType::NONE; }
+
+    /// Whether the last conversion potentially had an overflow
+    bool potential_overflow() const
+    {
+        return m_error_type == ErrorType::POTENTIAL_OVERFLOW;
+    }
+
+    /// Was an explict base given illegally?
+    bool illegal_explicit_base() const
+    {
+        return !is_explict_base_allowed() && !options().is_default_base();
+    }
+
+    /// Is the stored number negative?
+    bool is_negative() const { return m_negative; }
+
+    /// Access the user-given options for parsing
+    const UserOptions& options() const { return m_options; }
+
+    /// Convert the stored object to a long (check error state)
+    /// Base implementation does nothing but throw a runtime error
+    virtual long as_int() = 0;
+
+    /// Convert the stored object to a double (check error state)
+    /// Base implementation does nothing but throw a runtime error
+    virtual double as_float() = 0;
+
+    /// Convert the stored object to a python int (check error state)
+    /// Base implementation does nothing but throw a runtime error
+    virtual PyObject* as_pyint() = 0;
+
+    /// Convert the stored object to a python float (check error state)
+    /// Base implementation does nothing but throw a runtime error
+    virtual PyObject* as_pyfloat() = 0;
+
+    /// Was the passed Python object not float or int?
+    virtual bool not_float_or_int() const
+    {
+        return m_number_type == NumberType::NOT_FLOAT_OR_INT;
+    }
+
+    /// Was the passed Python object finite?
+    virtual bool is_finite() const { return is_real() && !(is_infinity() || is_nan()); }
+
+    /// Was the passed Python object infinity?
+    virtual bool is_infinity() const { return false; }
+
+    /// Was the passed Python object NaN?
+    virtual bool is_nan() const { return false; }
+
+    /// Was the passed Python object real (e.g. float or int)?
+    virtual bool is_real() const { return is_float() || is_int(); }
+
+    /// Was the passed Python object a float?
+    virtual bool is_float() const { return m_number_type == NumberType::FLOAT; }
+
+    /// Was the passed Python object an int?
+    virtual bool is_int() const { return m_number_type == NumberType::INT; }
+
+    /**
+     * \brief Was the passed Python object intlike?
+     *
+     * "intlike" is defined as either an int, or a float that can be
+     * converted to an int with no loss of information.
+     */
+    virtual bool is_intlike() const { return is_int(); }
+
+    /// Is the object is a a user-defined numeric class?
+    virtual bool is_user_numeric() const { return false; }
+
+    /// Is the object is a a user-defined numeric float?
+    virtual bool is_user_numeric_float() const { return false; }
+
+    /// Is the object is a a user-defined numeric int?
+    virtual bool is_user_numeric_int() const { return false; }
+
+    /**
+     * \brief Determine if a float is "intlike"
+     *
+     * This is defined as a float that can be converted to an int with
+     * no information loss.
+     */
+    static bool float_is_intlike(const double x)
+    {
+        errno = 0;
+        return std::isfinite(x) && std::floor(x) == x && errno == 0;
+    }
+
+protected:
+    /// Constructor for use only by base-classes to define the parser type
+    /// and base requirements
+    Parser(
+        const ParserType ptype,
+        const UserOptions& options,
+        const bool explicit_base_allowed = false
+    )
+        : m_ptype(ptype)
+        , m_number_type(NumberType::NOT_FLOAT_OR_INT)
+        , m_error_type(ErrorType::NONE)
+        , m_negative(false)
+        , m_explicit_base_allowed(explicit_base_allowed)
+        , m_options(options)
+    { }
+
+    /// Define this parser as "unknown"
+    void set_as_unknown_parser() { m_ptype = ParserType::UNKNOWN; }
+
+    /// The stored number type is "float"
+    void set_as_float_type() { m_number_type = NumberType::FLOAT; }
+
+    /// The stored number type is "int"
+    void set_as_int_type() { m_number_type = NumberType::INT; }
+
+    /// Record that the conversion encountered an error
+    void encountered_conversion_error() { m_error_type = ErrorType::CANNOT_PARSE; }
+
+    /// Record that the conversion encountered a potential overflow
+    void encountered_potential_overflow_error()
+    {
+        m_error_type = ErrorType::POTENTIAL_OVERFLOW;
+    }
+
+    /// Reset the error state to "no error"
+    void reset_error() { m_error_type = ErrorType::NONE; }
+
+    /// Define whether an explicitly give base is allowed for integers
+    void set_explict_base_allowed(bool explicit_base_allowed)
+    {
+        m_explicit_base_allowed = explicit_base_allowed;
+    }
+
+    /// Is an explicitly give base allowed for integers?
+    bool is_explict_base_allowed() const { return m_explicit_base_allowed; }
+
+    /// Toggle whether the store value is negative or not
+    void set_negative(const bool negative = true) { m_negative = negative; }
+
+    /// Integer that can be used to apply the sign of the number in the text
+    int sign() const { return is_negative() ? -1 : 1; }
+
+private:
+    /// The type of the parser
+    ParserType m_ptype;
+
+    /// The types of data this class can store
+    enum NumberType {
+        NOT_FLOAT_OR_INT,
+        FLOAT,
+        INT,
+    };
+
+    /// Tracker of what type is being stored
+    NumberType m_number_type;
+
+    /// The types of errors this class can encounter
+    enum ErrorType {
+        NONE,
+        CANNOT_PARSE,
+        POTENTIAL_OVERFLOW,
+    };
+
+    /// Tracker of what error is being stored
+    ErrorType m_error_type;
+
+    /// Whether or not text is a negative number
+    bool m_negative;
+
+    /// Whether or not explicit base are allowed when parsing
+    bool m_explicit_base_allowed;
+
+    /// Hold the parser options
+    UserOptions m_options;
+};
diff --git a/include/fastnumbers/parser/buffer.hpp b/include/fastnumbers/parser/buffer.hpp
new file mode 100644
index 00000000..9b9c8bf7
--- /dev/null
+++ b/include/fastnumbers/parser/buffer.hpp
@@ -0,0 +1,105 @@
+#pragma once
+
+#include <cstring>
+#include <vector>
+
+/**
+ * \class Buffer
+ * \brief A buffer of character data
+ */
+class Buffer {
+public:
+    /// Create a zero-sized buffer
+    Buffer()
+        : Buffer(0)
+    { }
+
+    /// Allocate buffer space
+    explicit Buffer(const std::size_t needed_length)
+        : m_fixed_buffer()
+        , m_variable_buffer()
+        , m_buffer(nullptr)
+        , m_len(needed_length)
+    {
+        reserve();
+    }
+
+    /// Allocate buffer space and copy data into it
+    Buffer(const char* data, const std::size_t needed_length)
+        : Buffer(needed_length)
+    {
+        copy(data);
+    }
+
+    Buffer(const Buffer&) = default;
+    Buffer(Buffer&&) = default;
+    Buffer& operator=(const Buffer&) = default;
+    ~Buffer() = default;
+
+    /// Return the start of the buffer
+    char* start() { return m_buffer; }
+
+    /// Return the end of the buffer
+    char* end() { return m_buffer + m_len; }
+
+    /// Return the length of the buffer
+    std::size_t length() { return m_len; }
+
+    /// Set aside a fixed lenth of data
+    void reserve(const std::size_t needed_length)
+    {
+        m_len = needed_length;
+        reserve();
+    }
+
+    /// Copy a fixed length of data into the buffer
+    void copy(const char* data, const std::size_t needed_length)
+    {
+        reserve(needed_length);
+        copy(data);
+    }
+
+    /// Remove underscores that are syntactically valid in a number
+    void remove_valid_underscores() { remove_valid_underscores(false); }
+
+    /// Remove underscores that are syntactically valid in a number,
+    /// possibly accounting for non-base-10 integers
+    void remove_valid_underscores(const bool based);
+
+    /// The largest amount of data the buffer can contain
+    std::vector<char>::size_type max_size() const
+    {
+        return m_variable_buffer.max_size();
+    }
+
+private:
+    /// Size of the fix-with buffer
+    static const std::size_t FIXED_BUFFER_SIZE = 32;
+
+    /// A string buffer of fixed size, in case data must be copied
+    char m_fixed_buffer[FIXED_BUFFER_SIZE];
+
+    /// A string buffer of variable size, in case large data must be copied
+    std::vector<char> m_variable_buffer;
+
+    /// Pointer to the character buffer being used
+    char* m_buffer;
+
+    /// The length of the character array
+    std::size_t m_len;
+
+private:
+    /// Set aside the amount of data stored in m_len
+    void reserve()
+    {
+        if (m_len + 1 < FIXED_BUFFER_SIZE) {
+            m_buffer = m_fixed_buffer;
+        } else {
+            m_variable_buffer.reserve(m_len + 1);
+            m_buffer = m_variable_buffer.data();
+        }
+    }
+
+    /// Copy data into the buffer of the currently stored length
+    void copy(const char* data) { std::memcpy(m_buffer, data, m_len); }
+};
diff --git a/include/fastnumbers/parser/character.hpp b/include/fastnumbers/parser/character.hpp
new file mode 100644
index 00000000..d691ae4f
--- /dev/null
+++ b/include/fastnumbers/parser/character.hpp
@@ -0,0 +1,124 @@
+#pragma once
+
+#include <cmath>
+#include <cstring>
+#include <vector>
+
+#include <Python.h>
+
+#include "fastnumbers/parser/base.hpp"
+#include "fastnumbers/parser/buffer.hpp"
+#include "fastnumbers/user_options.hpp"
+
+/**
+ * \class CharacterParser
+ * \brief Parses an ASCII character array for numeric properties
+ */
+class CharacterParser final : public Parser {
+
+public:
+    /// Construct with a single unicode character and sign
+    CharacterParser(
+        const char* str,
+        const std::size_t len,
+        const UserOptions& options,
+        const bool explict_base_allowed = true
+    );
+
+    // No default constructor
+    CharacterParser() = delete;
+
+    // Default copy/assignment and desctructor
+    CharacterParser(const CharacterParser&) = default;
+    CharacterParser(CharacterParser&&) = default;
+    CharacterParser& operator=(const CharacterParser&) = default;
+    ~CharacterParser() = default;
+
+    /// Convert the stored object to a long (check error state)
+    long as_int() override;
+
+    /// Convert the stored object to a double (check error state)
+    double as_float() override;
+
+    /// Convert the stored object to a python int (check error state)
+    PyObject* as_pyint() override;
+
+    /// Convert the stored object to a python float (check error state)
+    PyObject* as_pyfloat() override;
+
+    /// Was the passed Python object infinity?
+    bool is_infinity() const override;
+
+    /// Was the passed Python object NaN?
+    bool is_nan() const override;
+
+    /// Was the passed Python object real (e.g. float or int)?
+    bool is_real() const override { return is_float(); }
+
+    /// Was the passed Python object a float?
+    bool is_float() const override;
+
+    /// Was the passed Python object an int?
+    bool is_int() const override;
+
+    /**
+     * \brief Was the passed Python object intlike?
+     *
+     * "intlike" is defined as either an int, or a float that can be
+     * converted to an int with no loss of information.
+     */
+    bool is_intlike() const override;
+
+private:
+    /// The potential start of the character array
+    const char* m_start;
+
+    /// The original end of the character array
+    const char* m_end_orig;
+
+    /// The potential length of the character array
+    std::size_t m_str_len;
+
+private:
+    /// Check if the character array contains valid underscores
+    bool has_valid_underscores() const
+    {
+        return m_start != nullptr && options().allow_underscores() && m_str_len > 0
+            && std::memchr(m_start, '_', m_str_len);
+    }
+
+    /// Check if the character array contains invalid underscores
+    bool has_invalid_underscores() const
+    {
+        return m_start != nullptr && !options().allow_underscores() && m_str_len > 0
+            && std::memchr(m_start, '_', m_str_len);
+    }
+
+    /// The end of the stored character array
+    const char* end() const
+    {
+        return m_start == nullptr ? nullptr : (m_start + m_str_len);
+    }
+
+    /// Template for conversion to base types
+    template <
+        typename T,
+        typename CheckFunction,
+        typename OverflowCheckFunction,
+        typename ConvertFunction>
+    T as_type(
+        CheckFunction check_function,
+        OverflowCheckFunction overflow_check_function,
+        ConvertFunction convert_function
+    );
+
+    /// Template for checking that in input contains a type, accounting for underscores
+    template <typename Function>
+    bool check_string_for_number(
+        Buffer& buffer,
+        const char*& start,
+        const char*& new_end,
+        const std::size_t offset,
+        Function string_contains_number
+    );
+};
diff --git a/include/fastnumbers/parser/numeric.hpp b/include/fastnumbers/parser/numeric.hpp
new file mode 100644
index 00000000..e5a0bc99
--- /dev/null
+++ b/include/fastnumbers/parser/numeric.hpp
@@ -0,0 +1,152 @@
+#pragma once
+
+#include <cmath>
+
+#include <Python.h>
+
+#include "fastnumbers/parser/base.hpp"
+#include "fastnumbers/user_options.hpp"
+
+/**
+ * \class NumericParser
+ * \brief Parses numeric python objects
+ */
+class NumericParser final : public Parser {
+public:
+    /// Construct with a Python object
+    explicit NumericParser(PyObject* obj, const UserOptions& options)
+        : Parser(ParserType::NUMERIC, options)
+        , m_obj(obj)
+        , m_user_numeric(false)
+    {
+        if (m_obj != nullptr) {
+            // Store whether the number is a float or integer
+            if (PyFloat_Check(m_obj)) {
+                set_as_float_type();
+            } else if (PyLong_Check(m_obj)) {
+                set_as_int_type();
+            } else if (is_user_numeric_int() || is_user_numeric_float()) {
+                m_user_numeric = true;
+            }
+
+            // Increment the reference count for this object
+            Py_IncRef(m_obj);
+        } else {
+            // If given a null object, make the parser unknown
+            set_as_unknown_parser();
+        }
+    }
+
+    // No default constructor
+    NumericParser() = delete;
+
+    // Default copy/assignment
+    NumericParser(const NumericParser&) = default;
+    NumericParser(NumericParser&&) = default;
+    NumericParser& operator=(const NumericParser&) = default;
+
+    /// Descructor decreases reference count of the stored object
+    ~NumericParser() { Py_XDECREF(m_obj); };
+
+    /// Convert the stored object to a long (check error state)
+    long as_int() override
+    {
+        reset_error();
+
+        if (Parser::is_int() || is_user_numeric_int()) {
+            int overflow = 0;
+            const long value = PyLong_AsLongAndOverflow(m_obj, &overflow);
+            if (overflow) {
+                encountered_potential_overflow_error();
+                return -1L;
+            } else if (value == -1 && PyErr_Occurred()) {
+                PyErr_Clear();
+            }
+            return value;
+        }
+        encountered_conversion_error();
+        return -1L;
+    }
+
+    /// Convert the stored object to a double (check error state)
+    double as_float() override
+    {
+        reset_error();
+
+        if (Parser::is_float()) {
+            return get_double();
+        } else if (is_user_numeric_float()) {
+            const double value = PyFloat_AsDouble(m_obj);
+            if (value == -1.0 && PyErr_Occurred()) {
+                PyErr_Clear();
+            } else {
+                return value;
+            }
+        }
+        encountered_conversion_error();
+        return -1.0;
+    }
+
+    /// Convert the stored object to a python int (check error state)
+    PyObject* as_pyint() override { return PyNumber_Long(m_obj); }
+
+    /// Convert the stored object to a python float (check error state)
+    PyObject* as_pyfloat() override { return PyNumber_Float(m_obj); }
+
+    /// Was the passed Python object finite?
+    bool is_finite() const override
+    {
+        return Parser::is_int() || (Parser::is_float() && std::isfinite(get_double()));
+    }
+
+    /// Was the passed Python object infinity?
+    bool is_infinity() const override
+    {
+        return Parser::is_float() && std::isinf(get_double());
+    }
+
+    /// Was the passed Python object NaN?
+    bool is_nan() const override
+    {
+        return Parser::is_float() && std::isnan(get_double());
+    }
+
+    /**
+     * \brief Was the passed Python object intlike?
+     *
+     * "intlike" is defined as either an int, or a float that can be
+     * converted to an int with no loss of information.
+     */
+    bool is_intlike() const override
+    {
+        return Parser::is_int()
+            || (Parser::is_float() && Parser::float_is_intlike(get_double()));
+    }
+
+    /// Is the object is a a user-defined numeric class?
+    bool is_user_numeric() const override { return m_user_numeric; }
+
+    /// Is the object is a a user-defined numeric float?
+    bool is_user_numeric_float() const override
+    {
+        return Py_TYPE(m_obj)->tp_as_number && Py_TYPE(m_obj)->tp_as_number->nb_float;
+    }
+
+    /// Is the object is a a user-defined numeric int?
+    bool is_user_numeric_int() const override
+    {
+        PyNumberMethods* m_nmeth = Py_TYPE(m_obj)->tp_as_number;
+        return m_nmeth && (m_nmeth->nb_index || m_nmeth->nb_int);
+    }
+
+private:
+    /// The Python object potentially under analysis
+    PyObject* m_obj;
+
+    /// Track if the object is a a user-defined numeric class
+    bool m_user_numeric;
+
+private:
+    /// Return the object as a double. No error checking is performed.
+    double get_double() const { return PyFloat_AS_DOUBLE(m_obj); }
+};
diff --git a/include/fastnumbers/parser/unicode.hpp b/include/fastnumbers/parser/unicode.hpp
new file mode 100644
index 00000000..021144c8
--- /dev/null
+++ b/include/fastnumbers/parser/unicode.hpp
@@ -0,0 +1,113 @@
+#pragma once
+
+#include <Python.h>
+
+#include "fastnumbers/parser/base.hpp"
+#include "fastnumbers/user_options.hpp"
+
+/**
+ * \class UnicodeParser
+ * \brief Parses single unicode characters for numeric properties
+ */
+class UnicodeParser final : public Parser {
+public:
+    /// Construct with a single unicode character and sign
+    UnicodeParser(const Py_UCS4 uchar, const bool negative, const UserOptions& options)
+        : Parser(ParserType::UNICODE, options)
+        , m_numeric(Py_UNICODE_TONUMERIC(uchar))
+        , m_digit(Py_UNICODE_TODIGIT(uchar))
+    {
+        // Evaluate if the stored number is a float or int
+        if (m_digit > -1) {
+            set_as_int_type();
+        } else if (m_numeric > -1.0) {
+            set_as_float_type();
+        }
+
+        // Store the sign
+        set_negative(negative);
+    }
+
+    // No default constructor
+    UnicodeParser() = delete;
+
+    // Default copy/assignment and desctructor
+    UnicodeParser(const UnicodeParser&) = default;
+    UnicodeParser(UnicodeParser&&) = default;
+    UnicodeParser& operator=(const UnicodeParser&) = default;
+    ~UnicodeParser() = default;
+
+    /// Convert the stored object to a long (check error state)
+    long as_int() override
+    {
+        reset_error();
+
+        if (Parser::is_int()) {
+            return sign() * m_digit;
+        }
+        encountered_conversion_error();
+        return -1L;
+    }
+
+    /// Convert the stored object to a double (check error state)
+    double as_float() override
+    {
+        reset_error();
+
+        if (Parser::is_int()) {
+            return static_cast<double>(sign() * m_digit);
+        } else if (Parser::is_float()) {
+            return sign() * m_numeric;
+        }
+        encountered_conversion_error();
+        return -1.0;
+    }
+
+    /// Convert the stored object to a python int (check error state)
+    PyObject* as_pyint() override
+    {
+        reset_error();
+
+        const long value = as_int();
+        if (!errored()) {
+            return PyLong_FromLong(value);
+        }
+        encountered_conversion_error();
+        return nullptr;
+    }
+
+    /// Convert the stored object to a python float (check error state)
+    PyObject* as_pyfloat() override
+    {
+        reset_error();
+
+        const double value = as_float();
+        if (!errored()) {
+            return PyFloat_FromDouble(value);
+        }
+        encountered_conversion_error();
+        return nullptr;
+    }
+
+    /// Was the passed Python object a float?
+    bool is_float() const override { return Parser::is_float() || Parser::is_int(); }
+
+    /**
+     * \brief Was the passed Python object intlike?
+     *
+     * "intlike" is defined as either an int, or a float that can be
+     * converted to an int with no loss of information.
+     */
+    bool is_intlike() const override
+    {
+        return Parser::is_int()
+            || (Parser::is_float() && Parser::float_is_intlike(m_numeric));
+    }
+
+private:
+    /// The potential numeric value of a unicode character
+    double m_numeric;
+
+    /// The potential digit value of a unicode character
+    long m_digit;
+};
diff --git a/include/fastnumbers/parsing.h b/include/fastnumbers/parsing.h
deleted file mode 100644
index 42692e7c..00000000
--- a/include/fastnumbers/parsing.h
+++ /dev/null
@@ -1,170 +0,0 @@
-#ifndef __FN_PARSING
-#define __FN_PARSING
-
-#include <Python.h>
-#include <float.h>
-#include <limits.h>
-#include "fastnumbers/pstdint.h"
-#include "fastnumbers/fn_bool.h"
-
-/* Ensure 64 bits are handled. */
-#ifndef INT64_MAX
-#error "fastnumbers requires that your compiler support 64 bit integers, but it appears that this compiler does not"
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* Parsing helpers. */
-
-/* Convert char to int/long etc. */
-#define ascii2int(c) ((int) (*(c) - '0'))
-#define ascii2uint(c) ((unsigned) (*(c) - '0'))
-#define ascii2long(c) ((long) (*(c) - '0'))
-#define ascii2ulong(c) ((unsigned long) (*(c) - '0'))
-
-/* ID characters. */
-#define is_white_space(c) (*(c) == ' ' || (*(c) >= '\t' && *(c) <= '\r'))
-#define is_valid_digit(c) (*(c) >= '0' && *(c) <= '9')
-#define is_valid_digit_char(c) ((c) >= '0' && (c) <= '9')
-#define is_sign(c) (*(c) == '-' || *(c) == '+')
-
-/* Consume characters based on ID. */
-#define consume_white_space(str) while (is_white_space(str)) ++(str)
-#define consume_sign(str) if (is_sign(str)) str += 1
-#define consume_and_return_sign(str) \
-    (*(str) == '-' ? ((str)++, -1) : ((*(str) == '+') ? ((str)++, 1) : 1))
-
-/* A rather bold macro to strip whitespace from both ends. */
-#define strip_whitespace(start, end, length) \
-    do { \
-        (end) = (start) + (size_t) (length) - 1; /* Length includes NUL char. */ \
-        consume_white_space(start); \
-        while (is_white_space(end) && (start) != (end)) --(end); \
-        end += 1;  /* End on the space after the non-whitespace. */ \
-    } while(0)
-
-
-/* Overflow detection */
-
-/* Define the number of digits in an int that fastnumbers is willing
- * to attempt to convert itself. This is entirely based on the size
- * of a this compilers long, since the long is what Python uses to
- * represent an int under the hood - use one less than the maximum
- * length of a long.
- */
-#if ULONG_MAX == UINT64_MAX
-/* 64-bit int max == 9223372036854775807; len('9223372036854775807') - 1 == 18 */
-#define FN_MAX_INT_LEN 18
-#else
-/* 32-bit int max == 2147483647; len('2147483647') - 1 == 9 */
-#define FN_MAX_INT_LEN 9
-#endif
-
-/* Define the number of digits in a float that fastnumbers is willing
- * to attempt to convert itself. For Clang and GNUC, it is known
- * that DBL_DIG - 4 works. It is known that DBL_DIG - 6 works for MSVC.
- * To be safe, any compiler not known during fastnumbers testing will
- * use DBL_DIG - 6.
- * Also define the largest and smallest exponent that
- * fastnumbers is willing attempt to convert itself. For Clang and GNUC,
- * it is known that larger numbers work than MSVC.
- * To be safe, any compiler not known during fastnumbers testing will
- * use the smaller numbers.
- */
-#if defined(__clang__) || defined(__GNUC__)
-
-#define FN_DBL_DIG DBL_DIG - 4
-#define FN_MAX_EXP 99
-#define FN_MIN_EXP -98
-#define neg_exp_ok(len, str) \
-    ((len) == 1) || \
-    ((len) == 2 && (*(str) <= '8' || \
-                    (*(str) == '9' && *((str) + 1) <= '8')))
-#define pos_exp_ok(len, str) (len) > 0 && (len) <= 2
-
-#else
-
-#define FN_DBL_DIG DBL_DIG - 6
-#define FN_MAX_EXP 22
-#define pos_exp_ok(len, str) \
-    ((len) == 1) || \
-    ((len) == 2 && (*(str) <= '1' || \
-                    (*(str) == '2' && *((str) + 1) <= '2')))
-#define FN_MIN_EXP -22
-#define neg_exp_ok(len, str) \
-    ((len) == 1) || \
-    ((len) == 2 && (*(str) <= '1' || \
-                    (*(str) == '2' && *((str) + 1) <= '2')))
-
-#endif
-
-/* Quickly detect INFINITY and NAN. */
-#define _quick_detect_nf(start, len) \
-    ((len) == 3 && \
-     ((start)[1] == 'n' || (start)[1] == 'N') && \
-     ((start)[2] == 'f' || (start)[2] == 'F'))
-#define _quick_detect_nfinity(start, len) \
-    ((len) == 8 && \
-     ((start)[1] == 'n' || (start)[1] == 'N') && \
-     ((start)[2] == 'f' || (start)[2] == 'F') && \
-     ((start)[3] == 'i' || (start)[3] == 'I') && \
-     ((start)[4] == 'n' || (start)[4] == 'N') && \
-     ((start)[5] == 'i' || (start)[5] == 'I') && \
-     ((start)[6] == 't' || (start)[6] == 'T') && \
-     ((start)[7] == 'y' || (start)[7] == 'Y'))
-#define quick_detect_infinity(start, len) \
-    (((start)[0] == 'i' || (start)[0] == 'I') && \
-     (_quick_detect_nf(start, len) || _quick_detect_nfinity(start, len)))
-#define quick_detect_nan(start, len) \
-    (((start)[0] == 'n' || (start)[0] == 'N') && \
-     (len) == 3 && \
-     ((start)[1] == 'a' || (start)[1] == 'A') && \
-     ((start)[2] == 'n' || (start)[2] == 'N'))
-
-/* Quickly detect if a string is an integer or float. */
-#define is_likely_int(start, len) ((len) > 0 && is_valid_digit(start))
-#define is_likely_float(start, len) \
-    ((len) > 0 && \
-     (is_valid_digit(start) || \
-      (*(start) == '.' && is_valid_digit((start) + 1))) \
-    )
-
-/* Guess if an int or float will overflow. */
-#define int_might_overflow(start, end) ((end) - (start)) > FN_MAX_INT_LEN
-
-bool
-float_might_overflow(register const char *start,
-                     register const Py_ssize_t len);
-
-
-/* Declarations. */
-
-bool
-is_valid_digit_arbitrary_base(const char c, const int base);
-
-long
-parse_int(register const char *str, register const char *end, bool *error);
-
-double
-parse_float(register const char *str, register const char *end, bool *error,
-            const int8_t sign);
-
-bool
-string_contains_float(register const char *str, register const char *end,
-                      const bool allow_inf, const bool allow_nan);
-
-bool
-string_contains_intlike_float(register const char *str,
-                              register const char *end);
-
-bool
-string_contains_int(register const char *str, register const char *end,
-                    int base);
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* __FN_PARSING */
diff --git a/include/fastnumbers/payload.hpp b/include/fastnumbers/payload.hpp
new file mode 100644
index 00000000..31d4111b
--- /dev/null
+++ b/include/fastnumbers/payload.hpp
@@ -0,0 +1,272 @@
+#pragma once
+
+#include <Python.h>
+
+#include "fastnumbers/user_options.hpp"
+
+extern PyObject** FN_POS_INFINITY_PTR;
+extern PyObject** FN_NEG_INFINITY_PTR;
+extern PyObject** FN_POS_NAN_PTR;
+extern PyObject** FN_NEG_NAN_PTR;
+
+/// Possible actions that can be performed on input objects
+enum class ActionType {
+    PY_OBJECT, ///< Return a PyObject*
+    NAN_ACTION, ///< Return NaN
+    INF_ACTION, ///< Return infinity
+    NEG_NAN_ACTION, ///< Return negative NaN
+    NEG_INF_ACTION, ///< Return negative infinity
+    ERROR_INVALID_INT, ///< Raise invalid int exception
+    ERROR_INVALID_FLOAT, ///< Raise invalid float exception
+    ERROR_INVALID_BASE, ///< Raise invalid base exception
+    ERROR_INFINITY_TO_INT, ///< Raise oveflow for infintity to int
+    ERROR_NAN_TO_INT, ///< Raise NaN to int exception
+    ERROR_BAD_TYPE_INT, ///< Raise invalid type for int
+    ERROR_BAD_TYPE_FLOAT, ///< Raise invalid type for float
+    ERROR_ILLEGAL_EXPLICIT_BASE, ///< Raise illegal explict base exception
+};
+
+/**
+ * \brief Transfer data intended to be converted to Python objects
+ *
+ * Various functions can return different values (or errors) depending
+ * on user parameters. This class uniformly stores all types for smoothest
+ * transfer into "Python-land".
+ */
+class Payload {
+public:
+    /// Default construct - needed for use with Cython.
+    Payload()
+        : m_actval(ActionType::PY_OBJECT)
+        , m_pyval(nullptr)
+    { }
+
+    /// Construct the payload with an action.
+    explicit Payload(const ActionType atype)
+        : m_actval(atype)
+        , m_pyval(nullptr)
+    { }
+
+    /// Construct the payload with a double.
+    explicit Payload(const double val)
+        : m_actval(ActionType::PY_OBJECT)
+        , m_pyval(PyFloat_FromDouble(val))
+    { }
+
+    /// Construct the payload with a double that needs to be be converted to an int.
+    explicit Payload(const double val, const bool needs_int_conversion)
+        : m_actval(ActionType::PY_OBJECT)
+        , m_pyval(
+              needs_int_conversion ? PyLong_FromDouble(val) : PyFloat_FromDouble(val)
+          )
+    { }
+
+    /// Construct the payload with a long.
+    explicit Payload(const long val)
+        : m_actval(ActionType::PY_OBJECT)
+        , m_pyval(PyLong_FromLong(val))
+    { }
+
+    /// Construct the payload with a PyObject*.
+    explicit Payload(PyObject* val)
+        : m_actval(ActionType::PY_OBJECT)
+        , m_pyval(val)
+    { }
+
+    // Copy, assignment, and destruct are defaults
+    Payload(const Payload&) = default;
+    Payload(Payload&&) = default;
+    Payload& operator=(const Payload&) = default;
+    ~Payload() = default;
+
+    /// Return the Payload as an ActionType.
+    ActionType get_action() const { return m_actval; }
+
+    /// Return the Payload as a PyObject*.
+    PyObject* to_pyobject() const { return m_pyval; }
+
+    /// Resolve the paylowd into a Python object
+    PyObject* resolve(
+        PyObject* input,
+        const UserOptions& options,
+        PyObject* infinity,
+        PyObject* nan,
+        PyObject* default_,
+        PyObject* on_fail,
+        bool raise_on_invalid
+    ) const
+    {
+        PyObject* return_object
+            = failure_return_value(input, raise_on_invalid, default_);
+
+        const ActionType atype = get_action();
+        switch (atype) {
+        // Return a PyObject*
+        case ActionType::PY_OBJECT: {
+            PyObject* retval = to_pyobject();
+            if (retval == nullptr) {
+                if (return_object == nullptr) {
+                    return nullptr;
+                }
+
+                PyErr_Clear();
+
+                if (on_fail != nullptr) {
+                    return PyObject_CallFunctionObjArgs(on_fail, return_object, nullptr);
+                } else {
+                    return increment_reference(return_object);
+                }
+            }
+            return increment_reference(retval);
+        }
+
+        // Return the appropriate value for when infinity is found
+        case ActionType::INF_ACTION:
+            return increment_reference(
+                infinity == nullptr ? *FN_POS_INFINITY_PTR : infinity
+            );
+
+        // Return the appropriate value for when negative infinity is found
+        case ActionType::NEG_INF_ACTION:
+            return increment_reference(
+                infinity == nullptr ? *FN_NEG_INFINITY_PTR : infinity
+            );
+
+        // Return the appropriate value for when NaN is found
+        case ActionType::NAN_ACTION:
+            return increment_reference(nan == nullptr ? *FN_POS_NAN_PTR : nan);
+
+        // Return the appropriate value for when negative NaN is found
+        case ActionType::NEG_NAN_ACTION:
+            return increment_reference(nan == nullptr ? *FN_NEG_NAN_PTR : nan);
+
+        // Raise an exception due passing an invalid type to convert to
+        // an integer or float, or if using an explicit integer base
+        // where it shouldn't be used
+        case ActionType::ERROR_BAD_TYPE_INT:
+        case ActionType::ERROR_BAD_TYPE_FLOAT:
+        case ActionType::ERROR_ILLEGAL_EXPLICIT_BASE:
+            return raise_appropriate_exception(input, atype, options);
+
+        default:
+            // Raise an exception if that is what the user has asked for, otherwise
+            // transform the input via a function, otherwise return the input as-is
+            if (return_object == nullptr) {
+                return raise_appropriate_exception(input, atype, options);
+            }
+
+            PyErr_Clear();
+
+            if (on_fail != nullptr) {
+                return PyObject_CallFunctionObjArgs(on_fail, return_object, nullptr);
+            } else {
+                return increment_reference(return_object);
+            }
+        }
+
+        Py_UNREACHABLE();
+    }
+
+private:
+    /// Tracker of what action is being requested
+    ActionType m_actval;
+
+    /// The Payload as a PyObject*
+    PyObject* m_pyval;
+
+private:
+    /// Increment the refcount of a non-null object, then return the object
+    static PyObject* increment_reference(PyObject* obj)
+    {
+        Py_IncRef(obj);
+        return obj;
+    }
+
+    /// Return the best return value for when an error occurs
+    static PyObject*
+    failure_return_value(PyObject* obj, bool raise_on_invalid, PyObject* default_)
+    {
+        if (raise_on_invalid) {
+            return nullptr;
+        } else if (default_ != nullptr) {
+            return default_;
+        } else {
+            return obj;
+        }
+    }
+
+    /// Prepare and raise the appropriate exception given an action type
+    PyObject* raise_appropriate_exception(
+        PyObject* input, const ActionType atype, const UserOptions& options
+    ) const
+    {
+        switch (atype) {
+        case ActionType::ERROR_BAD_TYPE_INT:
+            // Raise an exception due passing an invalid type to convert to an integer
+            PyErr_Format(
+                PyExc_TypeError,
+                "int() argument must be a string, a bytes-like object or a number, not "
+                "'%s'",
+                Py_TYPE(input)->tp_name
+            );
+            break;
+
+        case ActionType::ERROR_BAD_TYPE_FLOAT:
+            // Raise an exception due passing an invalid type to convert to a float
+            PyErr_Format(
+                PyExc_TypeError,
+                "float() argument must be a string or a number, not '%s'",
+                Py_TYPE(input)->tp_name
+            );
+            break;
+
+        case ActionType::ERROR_ILLEGAL_EXPLICIT_BASE: // TODO - duplciate
+            // Raise an exception due to useing an explict integer base where it
+            // shouldn't
+            PyErr_SetString(
+                PyExc_TypeError, "int() can't convert non-string with explicit base"
+            );
+            break;
+
+        case ActionType::ERROR_INVALID_INT:
+            // Raise an exception due to an invalid integer
+            PyErr_Format(
+                PyExc_ValueError,
+                "invalid literal for int() with base %d: %.200R",
+                options.get_base(),
+                input
+            );
+            break;
+
+        case ActionType::ERROR_INVALID_FLOAT:
+            // Raise an exception due to an invalid float
+            PyErr_Format(
+                PyExc_ValueError, "could not convert string to float: %.200R", input
+            );
+            break;
+
+        case ActionType::ERROR_INVALID_BASE: // TODO - duplciate
+            // Raise an exception due to an invalid base for integer conversion
+            PyErr_SetString(
+                PyExc_TypeError, "int() can't convert non-string with explicit base"
+            );
+            break;
+
+        case ActionType::ERROR_INFINITY_TO_INT:
+            // Raise an exception due to attempting to convert infininty to an integer
+            PyErr_SetString(
+                PyExc_OverflowError, "cannot convert float infinity to integer"
+            );
+            break;
+
+        case ActionType::ERROR_NAN_TO_INT:
+            // Raise an exception due to attempting to convert NaN to an integer
+            PyErr_SetString(PyExc_ValueError, "cannot convert float NaN to integer");
+
+        default:
+            Py_UNREACHABLE();
+        }
+
+        return nullptr;
+    }
+};
diff --git a/include/fastnumbers/pstdint.h b/include/fastnumbers/pstdint.h
deleted file mode 100644
index f4a30815..00000000
--- a/include/fastnumbers/pstdint.h
+++ /dev/null
@@ -1,919 +0,0 @@
-/*  A portable stdint.h
- ****************************************************************************
- *  BSD License:
- ****************************************************************************
- *
- *  Copyright (c) 2005-2016 Paul Hsieh
- *  All rights reserved.
- *
- *  Redistribution and use in source and binary forms, with or without
- *  modification, are permitted provided that the following conditions
- *  are met:
- *
- *  1. Redistributions of source code must retain the above copyright
- *     notice, this list of conditions and the following disclaimer.
- *  2. Redistributions in binary form must reproduce the above copyright
- *     notice, this list of conditions and the following disclaimer in the
- *     documentation and/or other materials provided with the distribution.
- *  3. The name of the author may not be used to endorse or promote products
- *     derived from this software without specific prior written permission.
- *
- *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- ****************************************************************************
- *
- *  Version 0.1.16.0
- *
- *  The ANSI C standard committee, for the C99 standard, specified the
- *  inclusion of a new standard include file called stdint.h.  This is
- *  a very useful and long desired include file which contains several
- *  very precise definitions for integer scalar types that is critically
- *  important for making several classes of applications portable
- *  including cryptography, hashing, variable length integer libraries
- *  and so on.  But for most developers its likely useful just for
- *  programming sanity.
- *
- *  The problem is that some compiler vendors chose to ignore the C99
- *  standard and some older compilers have no opportunity to be updated.
- *  Because of this situation, simply including stdint.h in your code
- *  makes it unportable.
- *
- *  So that's what this file is all about.  It's an attempt to build a
- *  single universal include file that works on as many platforms as
- *  possible to deliver what stdint.h is supposed to.  Even compilers
- *  that already come with stdint.h can use this file instead without
- *  any loss of functionality.  A few things that should be noted about
- *  this file:
- *
- *    1) It is not guaranteed to be portable and/or present an identical
- *       interface on all platforms.  The extreme variability of the
- *       ANSI C standard makes this an impossibility right from the
- *       very get go. Its really only meant to be useful for the vast
- *       majority of platforms that possess the capability of
- *       implementing usefully and precisely defined, standard sized
- *       integer scalars.  Systems which are not intrinsically 2s
- *       complement may produce invalid constants.
- *
- *    2) There is an unavoidable use of non-reserved symbols.
- *
- *    3) Other standard include files are invoked.
- *
- *    4) This file may come in conflict with future platforms that do
- *       include stdint.h.  The hope is that one or the other can be
- *       used with no real difference.
- *
- *    5) In the current version, if your platform can't represent
- *       int32_t, int16_t and int8_t, it just dumps out with a compiler
- *       error.
- *
- *    6) 64 bit integers may or may not be defined.  Test for their
- *       presence with the test: #ifdef INT64_MAX or #ifdef UINT64_MAX.
- *       Note that this is different from the C99 specification which
- *       requires the existence of 64 bit support in the compiler.  If
- *       this is not defined for your platform, yet it is capable of
- *       dealing with 64 bits then it is because this file has not yet
- *       been extended to cover all of your system's capabilities.
- *
- *    7) (u)intptr_t may or may not be defined.  Test for its presence
- *       with the test: #ifdef PTRDIFF_MAX.  If this is not defined
- *       for your platform, then it is because this file has not yet
- *       been extended to cover all of your system's capabilities, not
- *       because its optional.
- *
- *    8) The following might not been defined even if your platform is
- *       capable of defining it:
- *
- *       WCHAR_MIN
- *       WCHAR_MAX
- *       (u)int64_t
- *       PTRDIFF_MIN
- *       PTRDIFF_MAX
- *       (u)intptr_t
- *
- *    9) The following have not been defined:
- *
- *       WINT_MIN
- *       WINT_MAX
- *
- *   10) The criteria for defining (u)int_least(*)_t isn't clear,
- *       except for systems which don't have a type that precisely
- *       defined 8, 16, or 32 bit types (which this include file does
- *       not support anyways). Default definitions have been given.
- *
- *   11) The criteria for defining (u)int_fast(*)_t isn't something I
- *       would trust to any particular compiler vendor or the ANSI C
- *       committee.  It is well known that "compatible systems" are
- *       commonly created that have very different performance
- *       characteristics from the systems they are compatible with,
- *       especially those whose vendors make both the compiler and the
- *       system.  Default definitions have been given, but its strongly
- *       recommended that users never use these definitions for any
- *       reason (they do *NOT* deliver any serious guarantee of
- *       improved performance -- not in this file, nor any vendor's
- *       stdint.h).
- *
- *   12) The following macros:
- *
- *       PRINTF_INTMAX_MODIFIER
- *       PRINTF_INT64_MODIFIER
- *       PRINTF_INT32_MODIFIER
- *       PRINTF_INT16_MODIFIER
- *       PRINTF_LEAST64_MODIFIER
- *       PRINTF_LEAST32_MODIFIER
- *       PRINTF_LEAST16_MODIFIER
- *       PRINTF_INTPTR_MODIFIER
- *
- *       are strings which have been defined as the modifiers required
- *       for the "d", "u" and "x" printf formats to correctly output
- *       (u)intmax_t, (u)int64_t, (u)int32_t, (u)int16_t, (u)least64_t,
- *       (u)least32_t, (u)least16_t and (u)intptr_t types respectively.
- *       PRINTF_INTPTR_MODIFIER is not defined for some systems which
- *       provide their own stdint.h.  PRINTF_INT64_MODIFIER is not
- *       defined if INT64_MAX is not defined.  These are an extension
- *       beyond what C99 specifies must be in stdint.h.
- *
- *       In addition, the following macros are defined:
- *
- *       PRINTF_INTMAX_HEX_WIDTH
- *       PRINTF_INT64_HEX_WIDTH
- *       PRINTF_INT32_HEX_WIDTH
- *       PRINTF_INT16_HEX_WIDTH
- *       PRINTF_INT8_HEX_WIDTH
- *       PRINTF_INTMAX_DEC_WIDTH
- *       PRINTF_INT64_DEC_WIDTH
- *       PRINTF_INT32_DEC_WIDTH
- *       PRINTF_INT16_DEC_WIDTH
- *       PRINTF_UINT8_DEC_WIDTH
- *       PRINTF_UINTMAX_DEC_WIDTH
- *       PRINTF_UINT64_DEC_WIDTH
- *       PRINTF_UINT32_DEC_WIDTH
- *       PRINTF_UINT16_DEC_WIDTH
- *       PRINTF_UINT8_DEC_WIDTH
- *
- *       Which specifies the maximum number of characters required to
- *       print the number of that type in either hexadecimal or decimal.
- *       These are an extension beyond what C99 specifies must be in
- *       stdint.h.
- *
- *  Compilers tested (all with 0 warnings at their highest respective
- *  settings): Borland Turbo C 2.0, WATCOM C/C++ 11.0 (16 bits and 32
- *  bits), Microsoft Visual C++ 6.0 (32 bit), Microsoft Visual Studio
- *  .net (VC7), Intel C++ 4.0, GNU gcc v3.3.3
- *
- *  This file should be considered a work in progress.  Suggestions for
- *  improvements, especially those which increase coverage are strongly
- *  encouraged.
- *
- *  Acknowledgements
- *
- *  The following people have made significant contributions to the
- *  development and testing of this file:
- *
- *  Chris Howie
- *  John Steele Scott
- *  Dave Thorup
- *  John Dill
- *  Florian Wobbe
- *  Christopher Sean Morrison
- *  Mikkel Fahnoe Jorgensen
- *
- */
-
-#include <stddef.h>
-#include <limits.h>
-#include <signal.h>
-
-/*
- *  For gcc with _STDINT_H, fill in the PRINTF_INT*_MODIFIER macros, and
- *  do nothing else.  On the Mac OS X version of gcc this is _STDINT_H_.
- */
-
-#if ((defined(__SUNPRO_C) && __SUNPRO_C >= 0x570) || (defined(_MSC_VER) && _MSC_VER >= 1600) || (defined(__STDC__) && __STDC__ && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || (defined (__WATCOMC__) && (defined (_STDINT_H_INCLUDED) || __WATCOMC__ >= 1250)) || (defined(__GNUC__) && (__GNUC__ > 3 || defined(_STDINT_H) || defined(_STDINT_H_) || defined (__UINT_FAST64_TYPE__)) )) && !defined (_PSTDINT_H_INCLUDED)
-#include <stdint.h>
-#define _PSTDINT_H_INCLUDED
-# if defined(__GNUC__) && (defined(__x86_64__) || defined(__ppc64__)) && !(defined(__APPLE__) && defined(__MACH__))
-#  ifndef PRINTF_INT64_MODIFIER
-#   define PRINTF_INT64_MODIFIER "l"
-#  endif
-#  ifndef PRINTF_INT32_MODIFIER
-#   define PRINTF_INT32_MODIFIER ""
-#  endif
-# else
-#  ifndef PRINTF_INT64_MODIFIER
-#   define PRINTF_INT64_MODIFIER "ll"
-#  endif
-#  ifndef PRINTF_INT32_MODIFIER
-#   if (UINT_MAX == UINT32_MAX)
-#    define PRINTF_INT32_MODIFIER ""
-#   else
-#    define PRINTF_INT32_MODIFIER "l"
-#   endif
-#  endif
-# endif
-# ifndef PRINTF_INT16_MODIFIER
-#  define PRINTF_INT16_MODIFIER "h"
-# endif
-# ifndef PRINTF_INTMAX_MODIFIER
-#  define PRINTF_INTMAX_MODIFIER PRINTF_INT64_MODIFIER
-# endif
-# ifndef PRINTF_INT64_HEX_WIDTH
-#  define PRINTF_INT64_HEX_WIDTH "16"
-# endif
-# ifndef PRINTF_UINT64_HEX_WIDTH
-#  define PRINTF_UINT64_HEX_WIDTH "16"
-# endif
-# ifndef PRINTF_INT32_HEX_WIDTH
-#  define PRINTF_INT32_HEX_WIDTH "8"
-# endif
-# ifndef PRINTF_UINT32_HEX_WIDTH
-#  define PRINTF_UINT32_HEX_WIDTH "8"
-# endif
-# ifndef PRINTF_INT16_HEX_WIDTH
-#  define PRINTF_INT16_HEX_WIDTH "4"
-# endif
-# ifndef PRINTF_UINT16_HEX_WIDTH
-#  define PRINTF_UINT16_HEX_WIDTH "4"
-# endif
-# ifndef PRINTF_INT8_HEX_WIDTH
-#  define PRINTF_INT8_HEX_WIDTH "2"
-# endif
-# ifndef PRINTF_UINT8_HEX_WIDTH
-#  define PRINTF_UINT8_HEX_WIDTH "2"
-# endif
-# ifndef PRINTF_INT64_DEC_WIDTH
-#  define PRINTF_INT64_DEC_WIDTH "19"
-# endif
-# ifndef PRINTF_UINT64_DEC_WIDTH
-#  define PRINTF_UINT64_DEC_WIDTH "20"
-# endif
-# ifndef PRINTF_INT32_DEC_WIDTH
-#  define PRINTF_INT32_DEC_WIDTH "10"
-# endif
-# ifndef PRINTF_UINT32_DEC_WIDTH
-#  define PRINTF_UINT32_DEC_WIDTH "10"
-# endif
-# ifndef PRINTF_INT16_DEC_WIDTH
-#  define PRINTF_INT16_DEC_WIDTH "5"
-# endif
-# ifndef PRINTF_UINT16_DEC_WIDTH
-#  define PRINTF_UINT16_DEC_WIDTH "5"
-# endif
-# ifndef PRINTF_INT8_DEC_WIDTH
-#  define PRINTF_INT8_DEC_WIDTH "3"
-# endif
-# ifndef PRINTF_UINT8_DEC_WIDTH
-#  define PRINTF_UINT8_DEC_WIDTH "3"
-# endif
-# ifndef PRINTF_INTMAX_HEX_WIDTH
-#  define PRINTF_INTMAX_HEX_WIDTH PRINTF_UINT64_HEX_WIDTH
-# endif
-# ifndef PRINTF_UINTMAX_HEX_WIDTH
-#  define PRINTF_UINTMAX_HEX_WIDTH PRINTF_UINT64_HEX_WIDTH
-# endif
-# ifndef PRINTF_INTMAX_DEC_WIDTH
-#  define PRINTF_INTMAX_DEC_WIDTH PRINTF_UINT64_DEC_WIDTH
-# endif
-# ifndef PRINTF_UINTMAX_DEC_WIDTH
-#  define PRINTF_UINTMAX_DEC_WIDTH PRINTF_UINT64_DEC_WIDTH
-# endif
-
-/*
- *  Something really weird is going on with Open Watcom.  Just pull some of
- *  these duplicated definitions from Open Watcom's stdint.h file for now.
- */
-
-# if defined (__WATCOMC__) && __WATCOMC__ >= 1250
-#  if !defined (INT64_C)
-#   define INT64_C(x)   (x + (INT64_MAX - INT64_MAX))
-#  endif
-#  if !defined (UINT64_C)
-#   define UINT64_C(x)  (x + (UINT64_MAX - UINT64_MAX))
-#  endif
-#  if !defined (INT32_C)
-#   define INT32_C(x)   (x + (INT32_MAX - INT32_MAX))
-#  endif
-#  if !defined (UINT32_C)
-#   define UINT32_C(x)  (x + (UINT32_MAX - UINT32_MAX))
-#  endif
-#  if !defined (INT16_C)
-#   define INT16_C(x)   (x)
-#  endif
-#  if !defined (UINT16_C)
-#   define UINT16_C(x)  (x)
-#  endif
-#  if !defined (INT8_C)
-#   define INT8_C(x)   (x)
-#  endif
-#  if !defined (UINT8_C)
-#   define UINT8_C(x)  (x)
-#  endif
-#  if !defined (UINT64_MAX)
-#   define UINT64_MAX  18446744073709551615ULL
-#  endif
-#  if !defined (INT64_MAX)
-#   define INT64_MAX  9223372036854775807LL
-#  endif
-#  if !defined (UINT32_MAX)
-#   define UINT32_MAX  4294967295UL
-#  endif
-#  if !defined (INT32_MAX)
-#   define INT32_MAX  2147483647L
-#  endif
-#  if !defined (INTMAX_MAX)
-#   define INTMAX_MAX INT64_MAX
-#  endif
-#  if !defined (INTMAX_MIN)
-#   define INTMAX_MIN INT64_MIN
-#  endif
-# endif
-#endif
-
-/*
- *  I have no idea what is the truly correct thing to do on older Solaris.
- *  From some online discussions, this seems to be what is being
- *  recommended.  For people who actually are developing on older Solaris,
- *  what I would like to know is, does this define all of the relevant
- *  macros of a complete stdint.h?  Remember, in pstdint.h 64 bit is
- *  considered optional.
- */
-
-#if (defined(__SUNPRO_C) && __SUNPRO_C >= 0x420) && !defined(_PSTDINT_H_INCLUDED)
-#include <sys/inttypes.h>
-#define _PSTDINT_H_INCLUDED
-#endif
-
-#ifndef _PSTDINT_H_INCLUDED
-#define _PSTDINT_H_INCLUDED
-
-#ifndef SIZE_MAX
-# define SIZE_MAX ((size_t)-1)
-#endif
-
-/*
- *  Deduce the type assignments from limits.h under the assumption that
- *  integer sizes in bits are powers of 2, and follow the ANSI
- *  definitions.
- */
-
-#ifndef UINT8_MAX
-# define UINT8_MAX 0xff
-#endif
-#if !defined(uint8_t) && !defined(_UINT8_T) && !defined(vxWorks)
-# if (UCHAR_MAX == UINT8_MAX) || defined (S_SPLINT_S)
-    typedef unsigned char uint8_t;
-#   define UINT8_C(v) ((uint8_t) v)
-# else
-#   error "Platform not supported"
-# endif
-#endif
-
-#ifndef INT8_MAX
-# define INT8_MAX 0x7f
-#endif
-#ifndef INT8_MIN
-# define INT8_MIN INT8_C(0x80)
-#endif
-#if !defined(int8_t) && !defined(_INT8_T) && !defined(vxWorks)
-# if (SCHAR_MAX == INT8_MAX) || defined (S_SPLINT_S)
-    typedef signed char int8_t;
-#   define INT8_C(v) ((int8_t) v)
-# else
-#   error "Platform not supported"
-# endif
-#endif
-
-#ifndef UINT16_MAX
-# define UINT16_MAX 0xffff
-#endif
-#if !defined(uint16_t) && !defined(_UINT16_T) && !defined(vxWorks)
-#if (UINT_MAX == UINT16_MAX) || defined (S_SPLINT_S)
-  typedef unsigned int uint16_t;
-# ifndef PRINTF_INT16_MODIFIER
-#  define PRINTF_INT16_MODIFIER ""
-# endif
-# define UINT16_C(v) ((uint16_t) (v))
-#elif (USHRT_MAX == UINT16_MAX)
-  typedef unsigned short uint16_t;
-# define UINT16_C(v) ((uint16_t) (v))
-# ifndef PRINTF_INT16_MODIFIER
-#  define PRINTF_INT16_MODIFIER "h"
-# endif
-#else
-#error "Platform not supported"
-#endif
-#endif
-
-#ifndef INT16_MAX
-# define INT16_MAX 0x7fff
-#endif
-#ifndef INT16_MIN
-# define INT16_MIN INT16_C(0x8000)
-#endif
-#if !defined(int16_t) && !defined(_INT16_T) && !defined(vxWorks)
-#if (INT_MAX == INT16_MAX) || defined (S_SPLINT_S)
-  typedef signed int int16_t;
-# define INT16_C(v) ((int16_t) (v))
-# ifndef PRINTF_INT16_MODIFIER
-#  define PRINTF_INT16_MODIFIER ""
-# endif
-#elif (SHRT_MAX == INT16_MAX)
-  typedef signed short int16_t;
-# define INT16_C(v) ((int16_t) (v))
-# ifndef PRINTF_INT16_MODIFIER
-#  define PRINTF_INT16_MODIFIER "h"
-# endif
-#else
-#error "Platform not supported"
-#endif
-#endif
-
-#ifndef UINT32_MAX
-# define UINT32_MAX (0xffffffffUL)
-#endif
-#if !defined(uint32_t) && !defined(_UINT32_T) && !defined(vxWorks)
-#if (ULONG_MAX == UINT32_MAX) || defined (S_SPLINT_S)
-  typedef unsigned long uint32_t;
-# define UINT32_C(v) v ## UL
-# ifndef PRINTF_INT32_MODIFIER
-#  define PRINTF_INT32_MODIFIER "l"
-# endif
-#elif (UINT_MAX == UINT32_MAX)
-  typedef unsigned int uint32_t;
-# ifndef PRINTF_INT32_MODIFIER
-#  define PRINTF_INT32_MODIFIER ""
-# endif
-# define UINT32_C(v) v ## U
-#elif (USHRT_MAX == UINT32_MAX)
-  typedef unsigned short uint32_t;
-# define UINT32_C(v) ((unsigned short) (v))
-# ifndef PRINTF_INT32_MODIFIER
-#  define PRINTF_INT32_MODIFIER ""
-# endif
-#else
-#error "Platform not supported"
-#endif
-#endif
-
-#ifndef INT32_MAX
-# define INT32_MAX (0x7fffffffL)
-#endif
-#ifndef INT32_MIN
-# define INT32_MIN INT32_C(0x80000000)
-#endif
-#if !defined(int32_t) && !defined(_INT32_T) && !defined(vxWorks)
-#if (LONG_MAX == INT32_MAX) || defined (S_SPLINT_S)
-  typedef signed long int32_t;
-# define INT32_C(v) v ## L
-# ifndef PRINTF_INT32_MODIFIER
-#  define PRINTF_INT32_MODIFIER "l"
-# endif
-#elif (INT_MAX == INT32_MAX)
-  typedef signed int int32_t;
-# define INT32_C(v) v
-# ifndef PRINTF_INT32_MODIFIER
-#  define PRINTF_INT32_MODIFIER ""
-# endif
-#elif (SHRT_MAX == INT32_MAX)
-  typedef signed short int32_t;
-# define INT32_C(v) ((short) (v))
-# ifndef PRINTF_INT32_MODIFIER
-#  define PRINTF_INT32_MODIFIER ""
-# endif
-#else
-#error "Platform not supported"
-#endif
-#endif
-
-/*
- *  The macro stdint_int64_defined is temporarily used to record
- *  whether or not 64 integer support is available.  It must be
- *  defined for any 64 integer extensions for new platforms that are
- *  added.
- */
-
-#undef stdint_int64_defined
-#if (defined(__STDC__) && defined(__STDC_VERSION__)) || defined (S_SPLINT_S)
-# if (__STDC__ && __STDC_VERSION__ >= 199901L) || defined (S_SPLINT_S)
-#  define stdint_int64_defined
-   typedef long long int64_t;
-   typedef unsigned long long uint64_t;
-#  define UINT64_C(v) v ## ULL
-#  define  INT64_C(v) v ## LL
-#  ifndef PRINTF_INT64_MODIFIER
-#   define PRINTF_INT64_MODIFIER "ll"
-#  endif
-# endif
-#endif
-
-#if !defined (stdint_int64_defined)
-# if defined(__GNUC__) && !defined(vxWorks)
-#  define stdint_int64_defined
-   __extension__ typedef long long int64_t;
-   __extension__ typedef unsigned long long uint64_t;
-#  define UINT64_C(v) v ## ULL
-#  define  INT64_C(v) v ## LL
-#  ifndef PRINTF_INT64_MODIFIER
-#   define PRINTF_INT64_MODIFIER "ll"
-#  endif
-# elif defined(__MWERKS__) || defined (__SUNPRO_C) || defined (__SUNPRO_CC) || defined (__APPLE_CC__) || defined (_LONG_LONG) || defined (_CRAYC) || defined (S_SPLINT_S)
-#  define stdint_int64_defined
-   typedef long long int64_t;
-   typedef unsigned long long uint64_t;
-#  define UINT64_C(v) v ## ULL
-#  define  INT64_C(v) v ## LL
-#  ifndef PRINTF_INT64_MODIFIER
-#   define PRINTF_INT64_MODIFIER "ll"
-#  endif
-# elif (defined(__WATCOMC__) && defined(__WATCOM_INT64__)) || (defined(_MSC_VER) && _INTEGRAL_MAX_BITS >= 64) || (defined (__BORLANDC__) && __BORLANDC__ > 0x460) || defined (__alpha) || defined (__DECC)
-#  define stdint_int64_defined
-   typedef __int64 int64_t;
-   typedef unsigned __int64 uint64_t;
-#  define UINT64_C(v) v ## UI64
-#  define  INT64_C(v) v ## I64
-#  ifndef PRINTF_INT64_MODIFIER
-#   define PRINTF_INT64_MODIFIER "I64"
-#  endif
-# endif
-#endif
-
-#if !defined (LONG_LONG_MAX) && defined (INT64_C)
-# define LONG_LONG_MAX INT64_C (9223372036854775807)
-#endif
-#ifndef ULONG_LONG_MAX
-# define ULONG_LONG_MAX UINT64_C (18446744073709551615)
-#endif
-
-#if !defined (INT64_MAX) && defined (INT64_C)
-# define INT64_MAX INT64_C (9223372036854775807)
-#endif
-#if !defined (INT64_MIN) && defined (INT64_C)
-# define INT64_MIN INT64_C (-9223372036854775808)
-#endif
-#if !defined (UINT64_MAX) && defined (INT64_C)
-# define UINT64_MAX UINT64_C (18446744073709551615)
-#endif
-
-/*
- *  Width of hexadecimal for number field.
- */
-
-#ifndef PRINTF_INT64_HEX_WIDTH
-# define PRINTF_INT64_HEX_WIDTH "16"
-#endif
-#ifndef PRINTF_INT32_HEX_WIDTH
-# define PRINTF_INT32_HEX_WIDTH "8"
-#endif
-#ifndef PRINTF_INT16_HEX_WIDTH
-# define PRINTF_INT16_HEX_WIDTH "4"
-#endif
-#ifndef PRINTF_INT8_HEX_WIDTH
-# define PRINTF_INT8_HEX_WIDTH "2"
-#endif
-#ifndef PRINTF_INT64_DEC_WIDTH
-# define PRINTF_INT64_DEC_WIDTH "19"
-#endif
-#ifndef PRINTF_INT32_DEC_WIDTH
-# define PRINTF_INT32_DEC_WIDTH "10"
-#endif
-#ifndef PRINTF_INT16_DEC_WIDTH
-# define PRINTF_INT16_DEC_WIDTH "5"
-#endif
-#ifndef PRINTF_INT8_DEC_WIDTH
-# define PRINTF_INT8_DEC_WIDTH "3"
-#endif
-#ifndef PRINTF_UINT64_DEC_WIDTH
-# define PRINTF_UINT64_DEC_WIDTH "20"
-#endif
-#ifndef PRINTF_UINT32_DEC_WIDTH
-# define PRINTF_UINT32_DEC_WIDTH "10"
-#endif
-#ifndef PRINTF_UINT16_DEC_WIDTH
-# define PRINTF_UINT16_DEC_WIDTH "5"
-#endif
-#ifndef PRINTF_UINT8_DEC_WIDTH
-# define PRINTF_UINT8_DEC_WIDTH "3"
-#endif
-
-/*
- *  Ok, lets not worry about 128 bit integers for now.  Moore's law says
- *  we don't need to worry about that until about 2040 at which point
- *  we'll have bigger things to worry about.
- */
-
-#ifdef stdint_int64_defined
-  typedef int64_t intmax_t;
-  typedef uint64_t uintmax_t;
-# define  INTMAX_MAX   INT64_MAX
-# define  INTMAX_MIN   INT64_MIN
-# define UINTMAX_MAX  UINT64_MAX
-# define UINTMAX_C(v) UINT64_C(v)
-# define  INTMAX_C(v)  INT64_C(v)
-# ifndef PRINTF_INTMAX_MODIFIER
-#   define PRINTF_INTMAX_MODIFIER PRINTF_INT64_MODIFIER
-# endif
-# ifndef PRINTF_INTMAX_HEX_WIDTH
-#  define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT64_HEX_WIDTH
-# endif
-# ifndef PRINTF_INTMAX_DEC_WIDTH
-#  define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT64_DEC_WIDTH
-# endif
-#else
-  typedef int32_t intmax_t;
-  typedef uint32_t uintmax_t;
-# define  INTMAX_MAX   INT32_MAX
-# define UINTMAX_MAX  UINT32_MAX
-# define UINTMAX_C(v) UINT32_C(v)
-# define  INTMAX_C(v)  INT32_C(v)
-# ifndef PRINTF_INTMAX_MODIFIER
-#   define PRINTF_INTMAX_MODIFIER PRINTF_INT32_MODIFIER
-# endif
-# ifndef PRINTF_INTMAX_HEX_WIDTH
-#  define PRINTF_INTMAX_HEX_WIDTH PRINTF_INT32_HEX_WIDTH
-# endif
-# ifndef PRINTF_INTMAX_DEC_WIDTH
-#  define PRINTF_INTMAX_DEC_WIDTH PRINTF_INT32_DEC_WIDTH
-# endif
-#endif
-
-/*
- *  Because this file currently only supports platforms which have
- *  precise powers of 2 as bit sizes for the default integers, the
- *  least definitions are all trivial.  Its possible that a future
- *  version of this file could have different definitions.
- */
-
-#ifndef stdint_least_defined
-  typedef   int8_t   int_least8_t;
-  typedef  uint8_t  uint_least8_t;
-  typedef  int16_t  int_least16_t;
-  typedef uint16_t uint_least16_t;
-  typedef  int32_t  int_least32_t;
-  typedef uint32_t uint_least32_t;
-# define PRINTF_LEAST32_MODIFIER PRINTF_INT32_MODIFIER
-# define PRINTF_LEAST16_MODIFIER PRINTF_INT16_MODIFIER
-# define  UINT_LEAST8_MAX  UINT8_MAX
-# define   INT_LEAST8_MAX   INT8_MAX
-# define UINT_LEAST16_MAX UINT16_MAX
-# define  INT_LEAST16_MAX  INT16_MAX
-# define UINT_LEAST32_MAX UINT32_MAX
-# define  INT_LEAST32_MAX  INT32_MAX
-# define   INT_LEAST8_MIN   INT8_MIN
-# define  INT_LEAST16_MIN  INT16_MIN
-# define  INT_LEAST32_MIN  INT32_MIN
-# ifdef stdint_int64_defined
-    typedef  int64_t  int_least64_t;
-    typedef uint64_t uint_least64_t;
-#   define PRINTF_LEAST64_MODIFIER PRINTF_INT64_MODIFIER
-#   define UINT_LEAST64_MAX UINT64_MAX
-#   define  INT_LEAST64_MAX  INT64_MAX
-#   define  INT_LEAST64_MIN  INT64_MIN
-# endif
-#endif
-#undef stdint_least_defined
-
-/*
- *  The ANSI C committee has defined *int*_fast*_t types as well.  This,
- *  of course, defies rationality -- you can't know what will be fast
- *  just from the type itself.  Even for a given architecture, compatible
- *  implementations might have different performance characteristics.
- *  Developers are warned to stay away from these types when using this
- *  or any other stdint.h.
- */
-
-typedef   int_least8_t   int_fast8_t;
-typedef  uint_least8_t  uint_fast8_t;
-typedef  int_least16_t  int_fast16_t;
-typedef uint_least16_t uint_fast16_t;
-typedef  int_least32_t  int_fast32_t;
-typedef uint_least32_t uint_fast32_t;
-#define  UINT_FAST8_MAX  UINT_LEAST8_MAX
-#define   INT_FAST8_MAX   INT_LEAST8_MAX
-#define UINT_FAST16_MAX UINT_LEAST16_MAX
-#define  INT_FAST16_MAX  INT_LEAST16_MAX
-#define UINT_FAST32_MAX UINT_LEAST32_MAX
-#define  INT_FAST32_MAX  INT_LEAST32_MAX
-#define   INT_FAST8_MIN   INT_LEAST8_MIN
-#define  INT_FAST16_MIN  INT_LEAST16_MIN
-#define  INT_FAST32_MIN  INT_LEAST32_MIN
-#ifdef stdint_int64_defined
-  typedef  int_least64_t  int_fast64_t;
-  typedef uint_least64_t uint_fast64_t;
-# define UINT_FAST64_MAX UINT_LEAST64_MAX
-# define  INT_FAST64_MAX  INT_LEAST64_MAX
-# define  INT_FAST64_MIN  INT_LEAST64_MIN
-#endif
-
-#undef stdint_int64_defined
-
-/*
- *  Whatever piecemeal, per compiler thing we can do about the wchar_t
- *  type limits.
- */
-
-#if defined(__WATCOMC__) || defined(_MSC_VER) || defined (__GNUC__) && !defined(vxWorks)
-# include <wchar.h>
-# ifndef WCHAR_MIN
-#  define WCHAR_MIN 0
-# endif
-# ifndef WCHAR_MAX
-#  define WCHAR_MAX ((wchar_t)-1)
-# endif
-#endif
-
-/*
- *  Whatever piecemeal, per compiler/platform thing we can do about the
- *  (u)intptr_t types and limits.
- */
-
-#if (defined (_MSC_VER) && defined (_UINTPTR_T_DEFINED)) || defined (_UINTPTR_T)
-# define STDINT_H_UINTPTR_T_DEFINED
-#endif
-
-#ifndef STDINT_H_UINTPTR_T_DEFINED
-# if defined (__alpha__) || defined (__ia64__) || defined (__x86_64__) || defined (_WIN64) || defined (__ppc64__)
-#  define stdint_intptr_bits 64
-# elif defined (__WATCOMC__) || defined (__TURBOC__)
-#  if defined(__TINY__) || defined(__SMALL__) || defined(__MEDIUM__)
-#    define stdint_intptr_bits 16
-#  else
-#    define stdint_intptr_bits 32
-#  endif
-# elif defined (__i386__) || defined (_WIN32) || defined (WIN32) || defined (__ppc64__)
-#  define stdint_intptr_bits 32
-# elif defined (__INTEL_COMPILER)
-/* TODO -- what did Intel do about x86-64? */
-# else
-/* #error "This platform might not be supported yet" */
-# endif
-
-# ifdef stdint_intptr_bits
-#  define stdint_intptr_glue3_i(a,b,c)  a##b##c
-#  define stdint_intptr_glue3(a,b,c)    stdint_intptr_glue3_i(a,b,c)
-#  ifndef PRINTF_INTPTR_MODIFIER
-#    define PRINTF_INTPTR_MODIFIER      stdint_intptr_glue3(PRINTF_INT,stdint_intptr_bits,_MODIFIER)
-#  endif
-#  ifndef PTRDIFF_MAX
-#    define PTRDIFF_MAX                 stdint_intptr_glue3(INT,stdint_intptr_bits,_MAX)
-#  endif
-#  ifndef PTRDIFF_MIN
-#    define PTRDIFF_MIN                 stdint_intptr_glue3(INT,stdint_intptr_bits,_MIN)
-#  endif
-#  ifndef UINTPTR_MAX
-#    define UINTPTR_MAX                 stdint_intptr_glue3(UINT,stdint_intptr_bits,_MAX)
-#  endif
-#  ifndef INTPTR_MAX
-#    define INTPTR_MAX                  stdint_intptr_glue3(INT,stdint_intptr_bits,_MAX)
-#  endif
-#  ifndef INTPTR_MIN
-#    define INTPTR_MIN                  stdint_intptr_glue3(INT,stdint_intptr_bits,_MIN)
-#  endif
-#  ifndef INTPTR_C
-#    define INTPTR_C(x)                 stdint_intptr_glue3(INT,stdint_intptr_bits,_C)(x)
-#  endif
-#  ifndef UINTPTR_C
-#    define UINTPTR_C(x)                stdint_intptr_glue3(UINT,stdint_intptr_bits,_C)(x)
-#  endif
-  typedef stdint_intptr_glue3(uint,stdint_intptr_bits,_t) uintptr_t;
-  typedef stdint_intptr_glue3( int,stdint_intptr_bits,_t)  intptr_t;
-# else
-/* TODO -- This following is likely wrong for some platforms, and does
-   nothing for the definition of uintptr_t. */
-  typedef ptrdiff_t intptr_t;
-# endif
-# define STDINT_H_UINTPTR_T_DEFINED
-#endif
-
-/*
- *  Assumes sig_atomic_t is signed and we have a 2s complement machine.
- */
-
-#ifndef SIG_ATOMIC_MAX
-# define SIG_ATOMIC_MAX ((((sig_atomic_t) 1) << (sizeof (sig_atomic_t)*CHAR_BIT-1)) - 1)
-#endif
-
-#endif
-
-#if defined (__TEST_PSTDINT_FOR_CORRECTNESS)
-
-/*
- *  Please compile with the maximum warning settings to make sure macros are
- *  not defined more than once.
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-
-#define glue3_aux(x,y,z) x ## y ## z
-#define glue3(x,y,z) glue3_aux(x,y,z)
-
-#define DECLU(bits) glue3(uint,bits,_t) glue3(u,bits,) = glue3(UINT,bits,_C) (0);
-#define DECLI(bits) glue3(int,bits,_t) glue3(i,bits,) = glue3(INT,bits,_C) (0);
-
-#define DECL(us,bits) glue3(DECL,us,) (bits)
-
-#define TESTUMAX(bits) glue3(u,bits,) = ~glue3(u,bits,); if (glue3(UINT,bits,_MAX) != glue3(u,bits,)) printf ("Something wrong with UINT%d_MAX\n", bits)
-
-#define REPORTERROR(msg) { err_n++; if (err_first <= 0) err_first = __LINE__; printf msg; }
-
-#define X_SIZE_MAX ((size_t)-1)
-
-int main () {
-	int err_n = 0;
-	int err_first = 0;
-	DECL(I,8)
-	DECL(U,8)
-	DECL(I,16)
-	DECL(U,16)
-	DECL(I,32)
-	DECL(U,32)
-#ifdef INT64_MAX
-	DECL(I,64)
-	DECL(U,64)
-#endif
-	intmax_t imax = INTMAX_C(0);
-	uintmax_t umax = UINTMAX_C(0);
-	char str0[256], str1[256];
-
-	sprintf (str0, "%" PRINTF_INT32_MODIFIER "d", INT32_C(2147483647));
-	if (0 != strcmp (str0, "2147483647")) REPORTERROR (("Something wrong with PRINTF_INT32_MODIFIER : %s\n", str0));
-	if (atoi(PRINTF_INT32_DEC_WIDTH) != (int) strlen(str0)) REPORTERROR (("Something wrong with PRINTF_INT32_DEC_WIDTH : %s\n", PRINTF_INT32_DEC_WIDTH));
-	sprintf (str0, "%" PRINTF_INT32_MODIFIER "u", UINT32_C(4294967295));
-	if (0 != strcmp (str0, "4294967295")) REPORTERROR (("Something wrong with PRINTF_INT32_MODIFIER : %s\n", str0));
-	if (atoi(PRINTF_UINT32_DEC_WIDTH) != (int) strlen(str0)) REPORTERROR (("Something wrong with PRINTF_UINT32_DEC_WIDTH : %s\n", PRINTF_UINT32_DEC_WIDTH));
-#ifdef INT64_MAX
-	sprintf (str1, "%" PRINTF_INT64_MODIFIER "d", INT64_C(9223372036854775807));
-	if (0 != strcmp (str1, "9223372036854775807")) REPORTERROR (("Something wrong with PRINTF_INT32_MODIFIER : %s\n", str1));
-	if (atoi(PRINTF_INT64_DEC_WIDTH) != (int) strlen(str1)) REPORTERROR (("Something wrong with PRINTF_INT64_DEC_WIDTH : %s, %d\n", PRINTF_INT64_DEC_WIDTH, (int) strlen(str1)));
-	sprintf (str1, "%" PRINTF_INT64_MODIFIER "u", UINT64_C(18446744073709550591));
-	if (0 != strcmp (str1, "18446744073709550591")) REPORTERROR (("Something wrong with PRINTF_INT32_MODIFIER : %s\n", str1));
-	if (atoi(PRINTF_UINT64_DEC_WIDTH) != (int) strlen(str1)) REPORTERROR (("Something wrong with PRINTF_UINT64_DEC_WIDTH : %s, %d\n", PRINTF_UINT64_DEC_WIDTH, (int) strlen(str1)));
-#endif
-
-	sprintf (str0, "%d %x\n", 0, ~0);
-
-	sprintf (str1, "%d %x\n",  i8, ~0);
-	if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with i8 : %s\n", str1));
-	sprintf (str1, "%u %x\n",  u8, ~0);
-	if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with u8 : %s\n", str1));
-	sprintf (str1, "%d %x\n",  i16, ~0);
-	if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with i16 : %s\n", str1));
-	sprintf (str1, "%u %x\n",  u16, ~0);
-	if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with u16 : %s\n", str1));
-	sprintf (str1, "%" PRINTF_INT32_MODIFIER "d %x\n",  i32, ~0);
-	if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with i32 : %s\n", str1));
-	sprintf (str1, "%" PRINTF_INT32_MODIFIER "u %x\n",  u32, ~0);
-	if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with u32 : %s\n", str1));
-#ifdef INT64_MAX
-	sprintf (str1, "%" PRINTF_INT64_MODIFIER "d %x\n",  i64, ~0);
-	if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with i64 : %s\n", str1));
-#endif
-	sprintf (str1, "%" PRINTF_INTMAX_MODIFIER "d %x\n",  imax, ~0);
-	if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with imax : %s\n", str1));
-	sprintf (str1, "%" PRINTF_INTMAX_MODIFIER "u %x\n",  umax, ~0);
-	if (0 != strcmp (str0, str1)) REPORTERROR (("Something wrong with umax : %s\n", str1));
-
-	TESTUMAX(8);
-	TESTUMAX(16);
-	TESTUMAX(32);
-#ifdef INT64_MAX
-	TESTUMAX(64);
-#endif
-
-#define STR(v) #v
-#define Q(v) printf ("sizeof " STR(v) " = %u\n", (unsigned) sizeof (v));
-	if (err_n) {
-		printf ("pstdint.h is not correct.  Please use sizes below to correct it:\n");
-	}
-
-	Q(int)
-	Q(unsigned)
-	Q(long int)
-	Q(short int)
-	Q(int8_t)
-	Q(int16_t)
-	Q(int32_t)
-#ifdef INT64_MAX
-	Q(int64_t)
-#endif
-
-#if UINT_MAX < X_SIZE_MAX
-	printf ("UINT_MAX < X_SIZE_MAX\n");
-#else
-	printf ("UINT_MAX >= X_SIZE_MAX\n");
-#endif
-	printf ("%" PRINTF_INT64_MODIFIER "u vs %" PRINTF_INT64_MODIFIER "u\n", UINT_MAX, X_SIZE_MAX);
-
-	return EXIT_SUCCESS;
-}
-
-#endif
diff --git a/include/fastnumbers/strings.h b/include/fastnumbers/strings.h
deleted file mode 100644
index aa2d347b..00000000
--- a/include/fastnumbers/strings.h
+++ /dev/null
@@ -1,32 +0,0 @@
-#ifndef __FN_STRING_HANDLING
-#define __FN_STRING_HANDLING
-
-/*
- * Master header for all string (character array) handling source files.
- */
-
-#include <Python.h>
-#include "fastnumbers/options.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* Declarations */
-
-PyObject *
-PyString_to_PyNumber(PyObject *obj, const PyNumberType type,
-                     const Options *options);
-
-PyObject *
-PyString_is_number(PyObject *obj, const PyNumberType type,
-                   const Options *options);
-
-PyObject *
-PyString_contains_type(PyObject *obj, const Options *options);
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* __FN_STRING_HANDLING */
diff --git a/include/fastnumbers/unicode_character.h b/include/fastnumbers/unicode_character.h
deleted file mode 100644
index d8ef645c..00000000
--- a/include/fastnumbers/unicode_character.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef __FN_UNICODE_HANDLING
-#define __FN_UNICODE_HANDLING
-
-/*
- * Master header for all unicode handling source files.
- */
-
-#include <Python.h>
-#include "fastnumbers/options.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/* Declarations */
-
-PyObject *
-convert_PyUnicode_to_PyNumber(PyObject *input);
-
-PyObject *
-PyUnicodeCharacter_to_PyNumber(PyObject *obj, const PyNumberType type,
-                               const Options *options);
-
-PyObject *
-PyUnicodeCharacter_is_number(PyObject *obj, const PyNumberType type);
-
-PyObject *
-PyUnicodeCharacter_contains_type(PyObject *obj);
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-#endif /* __FN_UNICODE_HANDLING */
diff --git a/include/fastnumbers/user_options.hpp b/include/fastnumbers/user_options.hpp
new file mode 100644
index 00000000..b3775ec2
--- /dev/null
+++ b/include/fastnumbers/user_options.hpp
@@ -0,0 +1,88 @@
+#pragma once
+
+#include <limits>
+
+class UserOptions final {
+public:
+    UserOptions()
+        : m_base(10)
+        , m_default_base(true)
+        , m_underscore_allowed(false)
+        , m_coerce(false)
+        , m_nan_allowed(false)
+        , m_inf_allowed(false)
+        , m_unicode_allowed(true)
+    { }
+    UserOptions(const UserOptions&) = default;
+    UserOptions(UserOptions&&) = default;
+    UserOptions& operator=(const UserOptions&) = default;
+    ~UserOptions() = default;
+
+    /// Tell the analyzer the base to use when parsing ints
+    void set_base(const int base)
+    {
+        m_default_base = base == std::numeric_limits<int>::min();
+        m_base = m_default_base ? 10 : base;
+    }
+
+    /// Get the stored base
+    int get_base() const { return m_base; }
+
+    /// Was the default base given?
+    bool is_default_base() const { return m_default_base; }
+
+    /// Define whether or not underscores are allowed
+    void set_underscores_allowed(const bool val) { m_underscore_allowed = val; }
+
+    /// Are underscores allowed?
+    bool allow_underscores() const { return m_underscore_allowed; }
+
+    /// Tell the analyzer whether or not to coerce to int for REAL
+    void set_coerce(const bool coerce) { m_coerce = coerce; }
+
+    /// Indicate if we are allowing coersion of floats to ints
+    bool allow_coerce() const { return m_coerce; }
+
+    /// Tell the analyzer if NaN is allowed when type checking
+    void set_nan_allowed(const bool nan_allowed) { m_nan_allowed = nan_allowed; }
+
+    /// Indicate if "NaN" is a valid input string
+    bool allow_nan() const { return m_nan_allowed; }
+
+    /// Tell the analyzer if infinity is allowed when type checking
+    void set_inf_allowed(const bool inf_allowed) { m_inf_allowed = inf_allowed; }
+
+    /// Indicate if "inf" is a valid input string
+    bool allow_inf() const { return m_inf_allowed; }
+
+    /// Tell the analyzer if unicode characters are allowed as input
+    void set_unicode_allowed(const bool unicode_allowed)
+    {
+        m_unicode_allowed = unicode_allowed;
+    }
+
+    /// Indicate if we allow non-ASCII unicode characters as input
+    bool allow_unicode() const { return m_unicode_allowed; }
+
+private:
+    /// The desired base of integers when parsing
+    int m_base;
+
+    /// If the user-given base is the default base
+    bool m_default_base;
+
+    /// Whether or not underscores are allowed when parsing
+    bool m_underscore_allowed;
+
+    /// Whether or not floats should be coerced to integers if user wants REAL
+    bool m_coerce;
+
+    /// Whether or not an NaN is allowed
+    bool m_nan_allowed;
+
+    /// Whether or not an infinity is allowed
+    bool m_inf_allowed;
+
+    /// Whether or not a unicode character is allowed
+    bool m_unicode_allowed;
+};
\ No newline at end of file
diff --git a/include/fastnumbers/version.h b/include/fastnumbers/version.h
deleted file mode 100644
index aa7d47d8..00000000
--- a/include/fastnumbers/version.h
+++ /dev/null
@@ -1,1 +0,0 @@
-#define FASTNUMBERS_VERSION "3.2.1"
diff --git a/include/fastnumbers/version.hpp b/include/fastnumbers/version.hpp
new file mode 100644
index 00000000..829851f5
--- /dev/null
+++ b/include/fastnumbers/version.hpp
@@ -0,0 +1,4 @@
+#pragma once
+
+/// The version of this fastnumbers release
+constexpr const char* FASTNUMBERS_VERSION = "3.2.1";
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 00000000..d1e6ae6e
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,2 @@
+[build-system]
+requires = ["setuptools", "wheel"]
diff --git a/setup.cfg b/setup.cfg
index f9af4cf3..5053124b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -24,7 +24,6 @@ classifiers =
 	License :: OSI Approved :: MIT License
 	Natural Language :: English
 	Programming Language :: Python :: 3
-	Programming Language :: Python :: 3.6
 	Programming Language :: Python :: 3.7
 	Programming Language :: Python :: 3.8
 	Programming Language :: Python :: 3.9
diff --git a/setup.py b/setup.py
index 40f5edae..5e53cf58 100644
--- a/setup.py
+++ b/setup.py
@@ -1,31 +1,54 @@
 #! /usr/bin/env python
 # -*- coding: utf-8 -*-
 
-# Std lib imports
 import glob
 import os
+import sys
 
-# Non-std lib imports
 from setuptools import Extension, find_packages, setup
 
 
+# Compilation arguments are platform-dependent
+if sys.platform == "win32":
+    compile_args = [
+        "/W4",  # Equivalent to -Wall -Wpedantic on GCC/Clang
+        "/wd4100",  # don't warn about unreferenced formal parameters
+        "/wd4127",  # don't warn about constant conditional expressions
+        "/O2",
+    ]
+else:
+    compile_args = [
+        "-std=c++11",
+        "-Wall",
+        "-Weffc++",
+        "-Wpedantic",
+        "-O2",
+        # "-O0",
+        # "-g",
+    ]
+    if sys.platform == "darwin":
+        compile_args.append("-Wno-c++17-extensions")
+
+
+ext = [
+    Extension(
+        "fastnumbers.fastnumbers",
+        sorted(glob.glob("src/cpp/*.cpp")),
+        include_dirs=[os.path.abspath(os.path.join("include"))],
+        extra_compile_args=compile_args,
+        extra_link_args=["-lm"],
+    )
+]
+
 # Define how to build the extension module.
 # All other data is in the setup.cfg file.
 setup(
     name="fastnumbers",
     version="3.2.1",
-    python_requires=">=3.6",
+    python_requires=">=3.7",
     packages=find_packages(where="src"),
     package_dir={"": "src"},
     package_data={"fastnumbers": ["py.typed", "*.pyi"]},
     zip_safe=False,
-    ext_modules=[
-        Extension(
-            "fastnumbers.fastnumbers",
-            sorted(glob.glob("src/*.c")),
-            include_dirs=[os.path.abspath(os.path.join("include"))],
-            extra_compile_args=[],
-            extra_link_args=["-lm"],
-        )
-    ],
+    ext_modules=ext,
 )
diff --git a/src/cpp/c_str_parsing.cpp b/src/cpp/c_str_parsing.cpp
new file mode 100644
index 00000000..aca5863e
--- /dev/null
+++ b/src/cpp/c_str_parsing.cpp
@@ -0,0 +1,553 @@
+/******************/
+/****** NOTE ******/
+/******************/
+// All functions in this file assume whitespace has been trimmed
+// from both sides of the string, and that the sign has been removed.
+
+#include "fastnumbers/c_str_parsing.hpp"
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+#include <cstring>
+
+/// Convert a character to an integer in the desired type
+template <typename T>
+static inline T ascii2int(const char c)
+{
+    return static_cast<T>(c - '0');
+}
+
+/// A function that accepts an argument and does nothing
+static inline void do_nothing(const char) { }
+
+/// Map to the approprate scaling factor for an exponent value
+constexpr long double POWER_OF_TEN_SCALING_FACTOR[] = {
+    1E0L,   1E1L,   1E2L,   1E3L,   1E4L,   1E5L,   1E6L,   1E7L,   1E8L,   1E9L,
+    1E10L,  1E11L,  1E12L,  1E13L,  1E14L,  1E15L,  1E16L,  1E17L,  1E18L,  1E19L,
+    1E20L,  1E21L,  1E22L,  1E23L,  1E24L,  1E25L,  1E26L,  1E27L,  1E28L,  1E29L,
+    1E30L,  1E31L,  1E32L,  1E33L,  1E34L,  1E35L,  1E36L,  1E37L,  1E38L,  1E39L,
+    1E40L,  1E41L,  1E42L,  1E43L,  1E44L,  1E45L,  1E46L,  1E47L,  1E48L,  1E49L,
+    1E50L,  1E51L,  1E52L,  1E53L,  1E54L,  1E55L,  1E56L,  1E57L,  1E58L,  1E59L,
+    1E60L,  1E61L,  1E62L,  1E63L,  1E64L,  1E65L,  1E66L,  1E67L,  1E68L,  1E69L,
+    1E70L,  1E71L,  1E72L,  1E73L,  1E74L,  1E75L,  1E76L,  1E77L,  1E78L,  1E79L,
+    1E80L,  1E81L,  1E82L,  1E83L,  1E84L,  1E85L,  1E86L,  1E87L,  1E88L,  1E89L,
+    1E90L,  1E91L,  1E92L,  1E93L,  1E94L,  1E95L,  1E96L,  1E97L,  1E98L,  1E99L,
+    1E100L, 1E101L, 1E102L, 1E103L, 1E104L, 1E105L, 1E106L, 1E107L, 1E108L, 1E109L,
+    1E110L, 1E111L, 1E112L, 1E113L, 1E114L, 1E115L, 1E116L, 1E117L, 1E118L, 1E119L,
+    1E120L, 1E121L, 1E122L, 1E123L, 1E124L, 1E125L, 1E126L, 1E127L, 1E128L, 1E129L,
+    1E130L, 1E131L, 1E132L, 1E133L, 1E134L, 1E135L, 1E136L, 1E137L, 1E138L, 1E139L,
+    1E140L, 1E141L, 1E142L, 1E143L, 1E144L, 1E145L, 1E146L, 1E147L, 1E148L, 1E149L,
+};
+constexpr int32_t LEN_EXP_ARRAY
+    = sizeof(POWER_OF_TEN_SCALING_FACTOR) / sizeof(POWER_OF_TEN_SCALING_FACTOR[0]);
+constexpr int32_t MAX_EXP_VALUE = LEN_EXP_ARRAY - 1;
+
+// FORWARD DECLARATIONS
+template <typename Function>
+static inline bool parse_integer_components(const char*& str, Function callback);
+static inline bool parse_integer_components(const char*& str);
+template <typename Function>
+static inline bool parse_decimal_components(const char*& str, Function callback);
+static inline bool parse_decimal_components(const char*& str);
+template <typename NFunction, typename Function>
+static inline bool
+parse_exponent_components(const char*& str, NFunction sign_callback, Function callback);
+static inline bool parse_exponent_components(const char*& str);
+static inline bool neg_exp_ok(const char* str, std::size_t len);
+static inline bool pos_exp_ok(const char* str, std::size_t len);
+static inline uint32_t number_trailing_zeros(const char* start, const char* end);
+static inline int detect_base(const char* str, const char* end);
+static inline bool is_valid_digit_arbitrary_base(const char c, const int base);
+
+// END FORWARD DECLARATIONS, BEGIN DEFINITITONS
+
+/*********************/
+/* EXPOSED FUNCTIONS */
+/*********************/
+
+bool string_contains_int(const char* str, const char* end, int base)
+{
+    if (base == 0) {
+        base = detect_base(str, end);
+    }
+
+    /* If base 10, take fast route. */
+    if (base == 10) {
+        return parse_integer_components(str) && str == end;
+    } else if (base == -1) {
+        return false;
+    } else {
+        const std::size_t len = static_cast<std::size_t>(end - str);
+
+        /* Skip leading characters for non-base 10 ints. */
+        if (len > 1 && str[0] == '0' && is_base_prefix(str[1], base)) {
+            str += 2;
+        }
+
+        /* The rest behaves as normal. */
+        bool valid = false;
+        while (is_valid_digit_arbitrary_base(*str, base)) {
+            str += 1;
+            valid = true;
+        }
+        return valid && str == end;
+    }
+}
+
+bool string_contains_float(const char* str, const char* end)
+{
+    bool valid = parse_integer_components(str);
+    valid = parse_decimal_components(str) || valid;
+    if (valid) {
+        valid = parse_exponent_components(str);
+    }
+    return valid && str == end;
+}
+
+bool string_contains_intlike_float(const char* str, const char* end)
+{
+    /* Before decimal. Keep track of number of digits read. */
+    const char* int_start = str;
+    bool valid = parse_integer_components(str);
+
+    /* Decimal part of float. Keep track of number of digits read */
+    /* as well as beginning and end locations. */
+    const char* decimal_start = str;
+    uint32_t dec_length = 0;
+    valid = parse_decimal_components(
+                str,
+                [&dec_length](const char) {
+                    dec_length += 1;
+                }
+            )
+        || valid;
+    const char* decimal_end = str;
+
+    /* Exponential part of float. Parse the magnitude. */
+    uint32_t expon = 0;
+    bool exp_negative = false;
+    if (valid) {
+        valid = parse_exponent_components(
+            str,
+            [&exp_negative](const char) {
+                exp_negative = true;
+            },
+            [&expon](const char c) {
+                expon *= 10;
+                expon += ascii2int<int32_t>(c);
+            }
+        );
+    }
+
+    if (!valid || str != end) {
+        return false;
+    } else {
+        /* If we "move the decimal place" left or right depending on
+         * exponent sign and magnitude, all digits after the decimal
+         * must be zero.
+         */
+        const unsigned int_trailing_zeros
+            = number_trailing_zeros(int_start, decimal_start);
+        const unsigned dec_trailing_zeros = decimal_start == decimal_end
+            ? 0U
+            : number_trailing_zeros(decimal_start + 1, decimal_end);
+
+        if (exp_negative) {
+            return expon <= int_trailing_zeros && dec_length == dec_trailing_zeros;
+        } else {
+            return expon >= (dec_length - dec_trailing_zeros);
+        }
+    }
+}
+
+long parse_int(const char* str, const char* end, bool& error)
+{
+    long value = 0L;
+
+    /* Convert digits, if any. */
+    bool valid = parse_integer_components(str, [&value](const char c) {
+        value *= 10L;
+        value += ascii2int<long>(c);
+    });
+
+    error = !valid || str != end;
+    return value;
+}
+
+double parse_float(const char* str, const char* end, bool& error)
+{
+    /* Parse integer part. */
+    uint64_t intvalue = 0UL;
+    bool valid = parse_integer_components(str, [&intvalue](const char c) {
+        intvalue *= 10UL;
+        intvalue += ascii2int<uint64_t>(c);
+    });
+
+    /* Parse decimal part. */
+    uint32_t decimal_len = 0;
+    valid = parse_decimal_components(
+                str,
+                [&intvalue, &decimal_len](const char c) {
+                    intvalue *= 10UL;
+                    intvalue += ascii2int<uint64_t>(c);
+                    decimal_len += 1U;
+                }
+            )
+        || valid;
+
+    /* Parse exponential part. */
+    int32_t expon = 0;
+    if (valid) {
+        int32_t exp_sign = 1;
+        valid = parse_exponent_components(
+            str,
+            [&exp_sign](const char c) {
+                if (c == '-') {
+                    exp_sign = -1;
+                }
+            },
+            [&expon](const char c) {
+                expon *= 10;
+                expon += ascii2int<int16_t>(c);
+            }
+        );
+        expon *= exp_sign;
+    }
+    expon -= decimal_len; /* Adjust the exponent by the # of decimal places */
+
+    error = !valid || str != end;
+    if (expon < 0) {
+        expon = std::abs(expon);
+        return static_cast<double>(
+            static_cast<long double>(intvalue)
+            / POWER_OF_TEN_SCALING_FACTOR[std::min(expon, MAX_EXP_VALUE)]
+        );
+    } else {
+        return static_cast<double>(
+            static_cast<long double>(intvalue)
+            * POWER_OF_TEN_SCALING_FACTOR[std::min(expon, MAX_EXP_VALUE)]
+        );
+    }
+}
+
+bool float_might_overflow(const char* start, const std::size_t len)
+{
+    // Locate the decimal place (if any).
+    const char* decimal_loc = static_cast<const char*>(std::memchr(start, '.', len));
+    const bool has_decimal = static_cast<bool>(decimal_loc);
+
+    // Find the exponent (if any) in the input.
+    // It is always after the exponent, usually close to the end, so
+    // we will start from the back. No need to include the stop
+    // location since that is either a decimal or the first digit
+    // which cannot be 'e' or 'E'.
+    const char* ptr = nullptr;
+    const char* exp = nullptr;
+    const char* stop = decimal_loc ? decimal_loc : start;
+    for (ptr = start + len - 1; ptr > stop; ptr--) {
+        if (*ptr == 'e' || *ptr == 'E') {
+            exp = ptr;
+            break;
+        }
+    }
+
+    // If the number of pre-exponent digits is greater than the known
+    // value it might overflow.
+    const std::size_t decimal_offset = static_cast<std::size_t>(has_decimal);
+    const std::size_t len_pre_exp = exp ? (exp - start) : len;
+    if (len_pre_exp - decimal_offset > FN_DBL_DIG) {
+        return true;
+    }
+
+    // If an exponent was found, ensure it is within chosen range.
+    if (exp) {
+        const bool neg = *(++exp) == '-'; // First remove 'e' or 'E'.
+        std::size_t exp_len = len - (exp - start);
+        if (is_sign(*exp)) {
+            exp += 1;
+            exp_len -= 1;
+        }
+        const bool exp_ok = neg ? neg_exp_ok(exp, exp_len) : pos_exp_ok(exp, exp_len);
+        if (!exp_ok) {
+            return true;
+        }
+    }
+
+    // Won't overflow
+    return false;
+}
+
+void remove_valid_underscores(char* str, const char*& end, const bool based)
+{
+    const std::size_t len = static_cast<std::size_t>(end - str);
+    std::size_t i, offset;
+
+    // The method that will be used to remove underscores is to
+    // traverse the character array, and when a valid underscore
+    // is found all characters will be shifted one to the left in
+    // order to remove that underscore. Extra characters at the
+    // end of the character array will be overwritten with \0.
+
+    // For non-based strings, parsing is "simple" -
+    // a valid underscore is surrounded by two numbers.
+    if (!based) {
+        for (i = offset = 0; i < len; i++) {
+            if (str[i] == '_' && i > 0 && i < len - 1 && is_valid_digit(str[i - 1])
+                && is_valid_digit(str[i + 1])) {
+                offset += 1;
+                continue;
+            }
+            if (offset) {
+                str[i - offset] = str[i];
+            }
+        }
+    }
+
+    // For based strings we must incorporate some state at the
+    // beginning of the string before the more simple "surrounded
+    // by two numbers" algorithm kicks in.
+    else {
+        i = offset = 0;
+        if (is_sign(*str)) {
+            i += 1;
+        }
+        // Skip leading characters for non-base 10 ints.
+        if ((len - i) > 1 && str[i] == '0' && is_base_prefix(str[i + 1])) {
+            // An underscore after the prefix is allowed, e.g. 0x_d4.
+            if ((len - i > 2) && str[i + 2] == '_') {
+                i += 3;
+                offset += 1;
+            } else {
+                i += 2;
+            }
+        }
+        // No underscore in the base selector, e.g. 0_b0 is invalid.
+        else if ((len - i) > 2 && str[i] == '0' && str[i + 1] == '_' && is_base_prefix(str[i + 2])) {
+            i += 3;
+        }
+
+        // Now search for simpler valid underscores.
+        // use hex as the base because it is the most inclusive.
+        for (; i < len; i++) {
+            if (str[i] == '_' && i > 0 && i < len - 1
+                && is_valid_digit_arbitrary_base(str[i - 1], 16)
+                && is_valid_digit_arbitrary_base(str[i + 1], 16)) {
+                offset += 1;
+                continue;
+            }
+            if (offset) {
+                str[i - offset] = str[i];
+            }
+        }
+    }
+
+    // Update the end position.
+    end = str + (i - offset);
+
+    // Fill the trailing data with nul characters.
+    for (i = len - offset; i < len; i++) {
+        str[i] = '\0';
+    }
+}
+
+/**************************/
+/* IMPLEMENTATION DETAILS */
+/**************************/
+
+/**
+ * \brief Scan a string while integer components are found
+ *
+ * A user-defined callback function can be provided to perform
+ * an operation when a digit is found.
+ *
+ * \param str The string to inspect. Assumed to be non-NULL
+ * \param callback A function to call for each digit character.
+ *                 Must accept a single char and return void.
+ *
+ * \returns true if at least one character was read as a digit
+ */
+template <typename Function>
+bool parse_integer_components(const char*& str, Function callback)
+{
+    const char* start = str;
+    while (is_valid_digit(*str)) {
+        callback(*str);
+        str += 1;
+    }
+    return str != start; // return whether or not a valid digit was found
+}
+
+bool parse_integer_components(const char*& str)
+{
+    return parse_integer_components(str, do_nothing);
+}
+
+/**
+ * \brief Scan a string for decimal parts of a float
+ *
+ * It is basically the same as parse_integer_components except
+ * that it allows a single '.' at the front of the string.
+ *
+ * A user-defined callback function can be provided to perform
+ * an operation when a digit is found.
+ *
+ * \param str The string to inspect. Assumed to be non-NULL
+ * \param callback A function to call for each digit character.
+ *                 Must accept a single char and return void.
+ *
+ * \returns true if at least one character was read as a digit
+ */
+template <typename Function>
+bool parse_decimal_components(const char*& str, Function callback)
+{
+    if (*str == '.') {
+        str += 1;
+        return parse_integer_components(str, callback);
+    }
+    return false;
+}
+
+bool parse_decimal_components(const char*& str)
+{
+    return parse_decimal_components(str, do_nothing);
+}
+
+/**
+ * \brief Scan a string for exponents parts of a float
+ *
+ * Require that the string starts with 'e' or 'E', then
+ * require that an (optionally signed) string of digits follows.
+ *
+ * A user-defined callback function can be provided to perform
+ * an operation when a sign is found.
+ *
+ * A user-defined callback function can be provided to perform
+ * an operation when a digit is found.
+ *
+ * \param str The string to inspect. Assumed to be non-NULL
+ * \param callback A function to call for each digit character.
+ *                 Must accept a single char and return void.
+ *
+ * \returns true if at least one character was read as a digit OR
+ *          if nothing was read
+ */
+template <typename NFunction, typename Function>
+bool parse_exponent_components(
+    const char*& str, NFunction sign_callback, Function callback
+)
+{
+    if (*str == 'e' || *str == 'E') {
+        str += 1;
+        if (is_sign(*str)) {
+            sign_callback(*str);
+            str += 1;
+        }
+        return parse_integer_components(str, callback);
+    }
+    return true;
+}
+
+bool parse_exponent_components(const char*& str)
+{
+    return parse_exponent_components(str, do_nothing, do_nothing);
+}
+
+#ifndef FASTNUMBERS_WIDE_EXP_RANGE
+/// Helper to check if an exponent number is in the allowed range
+static inline bool _exp_ok(const char* str, std::size_t len)
+{
+    return len == 1 || (len == 2 && (*str <= '1' || (*str == '1' && *(str + 1) <= '9')));
+}
+#endif
+
+/**
+ * \brief Check if a negative exponent number is in the allowed range
+ * \param str The string to check, assumed to be non-NULL
+ * \param len The length of the string
+ */
+bool neg_exp_ok(const char* str, std::size_t len)
+{
+#ifdef FASTNUMBERS_WIDE_EXP_RANGE
+    return len == 1 || (len == 2 && (*str <= '8' || (*str == '9' && *(str + 1) <= '8')));
+#else
+    return _exp_ok(str, len);
+#endif
+}
+
+/**
+ * \brief Check if a positive exponent number is in the allowed range
+ * \param str The string to check, assumed to be non-NULL
+ * \param len The length of the string
+ */
+bool pos_exp_ok(const char* str, std::size_t len)
+{
+#ifdef FASTNUMBERS_WIDE_EXP_RANGE
+    return len > 0 && len <= 2;
+#else
+    return _exp_ok(str, len);
+#endif
+}
+
+/**
+ * \brief Check the number of zeros at the end of a number
+ *
+ * \param str The string to check, assumed to be non-NULL
+ * \param end The end of the string being checked
+ * \return The number of zeros
+ */
+uint32_t number_trailing_zeros(const char* start, const char* end)
+{
+    uint32_t n = 0;
+    for (end = end - 1; end >= start; --end) {
+        if (*end == '0') {
+            n += 1;
+        } else {
+            break;
+        }
+    }
+    return n;
+}
+
+/**
+ * \brief Auto-detect the base of the given integer string
+ *
+ * \param str The string to check, assumed to be non-NULL
+ * \param end The end of the string being checked
+ * \return The dectected base, possibly 2, 8, 10, 16, or -1 on error
+ */
+int detect_base(const char* str, const char* end)
+{
+    const std::size_t len = static_cast<std::size_t>(end - str);
+    if (str[0] != '0' || len == 1) {
+        return 10;
+    } else if (str[1] == 'x' || str[1] == 'X') {
+        return 16;
+    } else if (str[1] == 'o' || str[1] == 'O') {
+        return 8;
+    } else if (str[1] == 'b' || str[1] == 'B') {
+        return 2;
+    } else
+        /* "old" (C-style) octal literal illegal in 3.x. */
+        if (number_trailing_zeros(str, end) == len) {
+            return 10;
+        } else {
+            return -1;
+        }
+}
+
+/**
+ * \brief Determine if a character represents a digit in a given base
+ *
+ * \param c The character to analyze
+ * \param base The base to use for digit determination
+ */
+bool is_valid_digit_arbitrary_base(const char c, const int base)
+{
+    if (base < 10) {
+        return c >= '0' && c <= (static_cast<int>('0') + base);
+    } else {
+        const char offset = static_cast<char>(base) - 10;
+        return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'a' + offset)
+            || (c >= 'A' && c <= 'A' + offset);
+    }
+}
diff --git a/src/cpp/extractor.cpp b/src/cpp/extractor.cpp
new file mode 100644
index 00000000..eb7c6617
--- /dev/null
+++ b/src/cpp/extractor.cpp
@@ -0,0 +1,151 @@
+#include <cstring>
+#include <stdexcept>
+
+#include <Python.h>
+
+#include "fastnumbers/c_str_parsing.hpp"
+#include "fastnumbers/extractor.hpp"
+#include "fastnumbers/parser.hpp"
+
+void TextExtractor::extract_string_data()
+{
+    // Use short-circuit logic to extract string data from the python object
+    // from most likely to least likely.
+    extract_from_unicode() || extract_from_bytes() || extract_from_bytearray()
+        || extract_from_buffer();
+}
+
+bool TextExtractor::extract_from_unicode()
+{
+    if (PyUnicode_Check(m_obj)) {
+        // Unicode in ASCII form is stored like bytes!
+        if (PyUnicode_IS_READY(m_obj) && PyUnicode_IS_COMPACT_ASCII(m_obj)) {
+            m_str = (const char*)PyUnicode_1BYTE_DATA(m_obj);
+            m_str_len = static_cast<const std::size_t>(PyUnicode_GET_LENGTH(m_obj));
+            return true;
+        }
+        return parse_unicode_to_char();
+    }
+    return false;
+}
+
+bool TextExtractor::extract_from_bytes()
+{
+    if (PyBytes_Check(m_obj)) {
+        m_str = PyBytes_AS_STRING(m_obj);
+        m_str_len = static_cast<const std::size_t>(PyBytes_GET_SIZE(m_obj));
+        return true;
+    }
+    return false;
+}
+
+bool TextExtractor::extract_from_bytearray()
+{
+    if (PyByteArray_Check(m_obj)) {
+        m_str = PyByteArray_AS_STRING(m_obj);
+        m_str_len = static_cast<const std::size_t>(PyByteArray_GET_SIZE(m_obj));
+        return true;
+    }
+    return false;
+}
+
+bool TextExtractor::extract_from_buffer()
+{
+    Py_buffer view = { NULL, NULL };
+    if (PyObject_CheckBuffer(m_obj)
+        && PyObject_GetBuffer(m_obj, &view, PyBUF_SIMPLE) == 0) {
+        // This buffer could be a memoryview slice. If this is the case, the
+        // nul termination of the string will be past the given length, creating
+        // unexpected parsing results. Rather than complicate the parsing and
+        // adding more operations for a low probability event, a copy of the
+        // slice will be made here and null termination will be added.
+        // If the data amount is small enough, we use a fixed-sized buffer for speed.
+        m_str_len = static_cast<const std::size_t>(view.len);
+        m_char_buffer.copy(static_cast<char*>(view.buf), m_str_len);
+        m_char_buffer.start()[m_str_len] = '\0';
+
+        // All we care about is the underlying buffer data, not the obj
+        // which was allocated when we created the buffer. For this reason
+        // it is safe to release the buffer here.
+        PyBuffer_Release(&view);
+        m_str = m_char_buffer.start();
+        m_explicit_base_allowed = false;
+        return true;
+    }
+    return false;
+}
+
+bool TextExtractor::parse_unicode_to_char()
+{
+    const unsigned kind = PyUnicode_KIND(m_obj); // Unicode storage format.
+    const void* data = PyUnicode_DATA(m_obj); // Raw data
+    Py_ssize_t len = PyUnicode_GET_LENGTH(m_obj);
+    Py_ssize_t index = 0;
+
+    // Ensure input is a valid unicode object.
+    // If true, then not OK for conversion.
+    if (PyUnicode_READY(m_obj)) {
+        m_char_buffer.start()[0] = '\0';
+        m_str = m_char_buffer.start();
+        m_str_len = 0;
+        return true;
+    }
+
+    // Strip whitespace from both ends of the data.
+    while (Py_UNICODE_ISSPACE(PyUnicode_READ(kind, data, index))) {
+        index += 1;
+        len -= 1;
+    }
+    while (Py_UNICODE_ISSPACE(PyUnicode_READ(kind, data, index + len - 1))) {
+        len -= 1;
+    }
+
+    // Remember if it was negative
+    m_negative = PyUnicode_READ(kind, data, index) == '-';
+
+    // Protect against attempting to allocate too much memory
+    if (static_cast<std::size_t>(len) + 1 > m_char_buffer.max_size()) {
+        m_char_buffer.start()[0] = '\0';
+        m_str = m_char_buffer.start();
+        m_str_len = 0;
+        return true;
+    }
+
+    // Allocate space for the character data, but use a small fixed size
+    // buffer if the data is small enough. Ensure a trailing null character.
+    m_char_buffer.reserve(static_cast<std::size_t>(len));
+    char* buffer = m_char_buffer.start();
+    std::size_t buffer_index = 0;
+
+    // Iterate over the unicode data and transform to ASCII-compatible
+    // data. If at any point this fails, exit and just save as a 0-length
+    // string, unless the length was one, in which case we save the one
+    // character.
+    long u_as_decimal = 0;
+    const Py_ssize_t data_len = len + index;
+    for (; index < data_len; index++) {
+        const Py_UCS4 u = (Py_UCS4)PyUnicode_READ(kind, data, index);
+        if (u < ASCII_MAX) {
+            buffer[buffer_index] = static_cast<char>(u);
+        } else if ((u_as_decimal = Py_UNICODE_TODECIMAL(u)) > -1) {
+            buffer[buffer_index] = '0' + static_cast<char>(u_as_decimal);
+        } else if (Py_UNICODE_ISSPACE(u)) {
+            buffer[buffer_index] = ' ';
+        } else {
+            if (len == 1) {
+                m_uchar = u;
+                return true;
+            }
+            buffer[0] = '\0';
+            m_str = buffer;
+            m_str_len = 0;
+            return true;
+        }
+        buffer_index += 1;
+    }
+    buffer[buffer_index] = '\0';
+
+    m_str = buffer;
+    m_str_len = buffer_index;
+    return true;
+}
diff --git a/src/cpp/fastnumbers.cpp b/src/cpp/fastnumbers.cpp
new file mode 100644
index 00000000..335fb21e
--- /dev/null
+++ b/src/cpp/fastnumbers.cpp
@@ -0,0 +1,791 @@
+#include <Python.h>
+
+#include "fastnumbers/c_str_parsing.hpp"
+#include "fastnumbers/docstrings.hpp"
+#include "fastnumbers/evaluator.hpp"
+#include "fastnumbers/extractor.hpp"
+#include "fastnumbers/parser.hpp"
+#include "fastnumbers/payload.hpp"
+#include "fastnumbers/user_options.hpp"
+#include "fastnumbers/version.hpp"
+
+// Infinity and NaN can be cached at startup
+static PyObject* POS_INFINITY;
+static PyObject* NEG_INFINITY;
+static PyObject* POS_NAN;
+static PyObject* NEG_NAN;
+
+// Hacky way to allow access to these module-scoped variables outside this file
+PyObject** FN_POS_INFINITY_PTR = &POS_INFINITY;
+PyObject** FN_NEG_INFINITY_PTR = &NEG_INFINITY;
+PyObject** FN_POS_NAN_PTR = &POS_NAN;
+PyObject** FN_NEG_NAN_PTR = &NEG_NAN;
+
+/**
+ * \brief Function to handle the conversion of base to integers.
+ *
+ * \param pybase The base as a Python object
+ * \param base The base as an integer
+ * \returns 0 is success, 1 is failure.
+ */
+int assess_integer_base_input(PyObject* pybase, int& base)
+{
+    Py_ssize_t longbase = 0;
+
+    // Default to INT_MIN
+    if (pybase == nullptr) {
+        base = INT_MIN;
+        return 0;
+    }
+
+    // Convert to int and check for overflow
+    longbase = PyNumber_AsSsize_t(pybase, nullptr);
+    if (longbase == -1 && PyErr_Occurred()) {
+        return 1;
+    }
+
+    // Ensure valid integer in valid range
+    if ((longbase != 0 && longbase < 2) || longbase > 36) {
+        PyErr_SetString(PyExc_ValueError, "int() base must be >= 2 and <= 36");
+        return 1;
+    }
+    base = static_cast<int>(longbase);
+    return 0;
+}
+
+/**
+ * \brief If key is defined, move the value to on_fail.
+ *
+ * If both were defined, error.
+ *
+ * \param on_fail
+ * \param key
+ * \returns 0 is success, 1 is failure.
+ */
+int handle_key_backwards_compatibility(PyObject*& on_fail, PyObject*& key)
+{
+    if (key != nullptr) {
+        if (on_fail != nullptr) {
+            PyErr_SetString(PyExc_ValueError, "Cannot set both on_fail and key");
+            return 1;
+        }
+        on_fail = key;
+        key = nullptr;
+    }
+    return 0;
+}
+
+/// Extract the return payload from a given python object
+Payload collect_payload(PyObject* obj, const UserOptions& options, const UserType ntype)
+{
+    Buffer buffer;
+    TextExtractor extractor(obj, buffer);
+    if (extractor.is_text()) {
+        CharacterParser cparser = extractor.text_parser(options);
+        return Evaluator<CharacterParser>(obj, options, cparser).as_type(ntype);
+    } else if (extractor.is_unicode_character()) {
+        UnicodeParser uparser = extractor.unicode_char_parser(options);
+        return Evaluator<UnicodeParser>(obj, options, uparser).as_type(ntype);
+    } else {
+        NumericParser nparser(obj, options);
+        return Evaluator<NumericParser>(obj, options, nparser).as_type(ntype);
+    }
+}
+
+/// Check if an arbitrary PyObject is a PyNumber.
+PyObject* object_is_number(
+    PyObject* obj,
+    const UserType type,
+    int base,
+    bool allow_nan,
+    bool allow_inf,
+    bool str_only,
+    bool num_only,
+    bool allow_underscores
+)
+{
+    // Store the user options in a common interface
+    UserOptions options;
+    options.set_base(base);
+    options.set_nan_allowed(allow_nan);
+    options.set_inf_allowed(allow_inf);
+    options.set_underscores_allowed(allow_underscores);
+
+    // Attempt to extract character data from the object
+    Buffer buffer;
+    TextExtractor extractor(obj, buffer);
+    if (num_only && (extractor.is_text() || extractor.is_unicode_character())) {
+        Py_RETURN_FALSE;
+    } else if (str_only && extractor.is_non_text()) {
+        Py_RETURN_FALSE;
+    }
+
+    // Create a parser and use it to evaluate the user request
+    if (extractor.is_text()) {
+        CharacterParser cparser = extractor.text_parser(options);
+        return PyBool_FromLong(
+            Evaluator<CharacterParser>(obj, options, cparser).is_type(type)
+        );
+    } else if (extractor.is_unicode_character()) {
+        UnicodeParser uparser = extractor.unicode_char_parser(options);
+        return PyBool_FromLong(
+            Evaluator<UnicodeParser>(obj, options, uparser).is_type(type)
+        );
+    } else {
+        NumericParser nparser(obj, options);
+        return PyBool_FromLong(
+            Evaluator<NumericParser>(obj, options, nparser).is_type(type)
+        );
+    }
+}
+
+/// Ensure the type is allowed, otherwise return None
+PyObject* validate_query_type(PyObject* result, PyObject* allowed_types)
+{
+    if (allowed_types != nullptr && !PySequence_Contains(allowed_types, result)) {
+        Py_RETURN_NONE;
+    }
+    Py_IncRef(result);
+    return result;
+}
+
+/// Return the correct type to search for in the input
+template <typename T>
+PyObject* query_search_type(const T& evaluator, PyObject* input)
+{
+    return evaluator.type_is_int()
+        ? (PyObject*)&PyLong_Type
+        : (evaluator.type_is_float() ? (PyObject*)&PyFloat_Type
+                                     : (PyObject*)Py_TYPE(input));
+}
+
+// Quickly convert to an int or float, depending on value
+static PyObject* fastnumbers_fast_real(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* input = nullptr;
+    PyObject* default_value = nullptr;
+    PyObject* on_fail = nullptr;
+    PyObject* key = nullptr;
+    PyObject* inf = nullptr;
+    PyObject* nan = nullptr;
+    int raise_on_invalid = false;
+    int coerce = true;
+    int allow_underscores = true;
+    static const char* keywords[]
+        = { "x",   "default", "raise_on_invalid",  "on_fail", "inf",
+            "nan", "coerce",  "allow_underscores", "key",     nullptr };
+    static const char* format = "O|O$pOOOppO:fast_real";
+
+    // Read the function argument
+    if (!PyArg_ParseTupleAndKeywords(
+            args,
+            kwargs,
+            format,
+            const_cast<char**>(keywords),
+            &input,
+            &default_value,
+            &raise_on_invalid,
+            &on_fail,
+            &inf,
+            &nan,
+            &coerce,
+            &allow_underscores,
+            &key
+        )) {
+        return nullptr;
+    }
+    if (handle_key_backwards_compatibility(on_fail, key)) {
+        return nullptr;
+    }
+
+    UserOptions options;
+    options.set_coerce(coerce);
+    options.set_underscores_allowed(allow_underscores);
+
+    const Payload payload = collect_payload(input, options, UserType::REAL);
+    return payload.resolve(
+        input, options, inf, nan, default_value, on_fail, raise_on_invalid
+    );
+}
+
+/* Quickly convert to a float, depending on value. */
+static PyObject* fastnumbers_fast_float(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* input = nullptr;
+    PyObject* default_value = nullptr;
+    PyObject* on_fail = nullptr;
+    PyObject* key = nullptr;
+    PyObject* inf = nullptr;
+    PyObject* nan = nullptr;
+    int raise_on_invalid = false;
+    int allow_underscores = true;
+    static const char* keywords[] = { "x",    "default", "raise_on_invalid",  "on_fail",
+                                      "inf",  "nan",     "allow_underscores", "key",
+                                      nullptr };
+    static const char* format = "O|O$pOOOpO:fast_float";
+
+    /* Read the function argument. */
+    if (!PyArg_ParseTupleAndKeywords(
+            args,
+            kwargs,
+            format,
+            const_cast<char**>(keywords),
+            &input,
+            &default_value,
+            &raise_on_invalid,
+            &on_fail,
+            &inf,
+            &nan,
+            &allow_underscores,
+            &key
+        )) {
+        return nullptr;
+    }
+    if (handle_key_backwards_compatibility(on_fail, key)) {
+        return nullptr;
+    }
+
+    UserOptions options;
+    options.set_underscores_allowed(allow_underscores);
+
+    const Payload payload = collect_payload(input, options, UserType::FLOAT);
+    return payload.resolve(
+        input, options, inf, nan, default_value, on_fail, raise_on_invalid
+    );
+}
+
+/* Quickly convert to an int, depending on value. */
+static PyObject* fastnumbers_fast_int(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* input = nullptr;
+    PyObject* default_value = nullptr;
+    PyObject* on_fail = nullptr;
+    PyObject* key = nullptr;
+    PyObject* pybase = nullptr;
+    int raise_on_invalid = false;
+    int allow_underscores = true;
+    static const char* keywords[]
+        = { "x",   "default", "raise_on_invalid", "on_fail", "base", "allow_underscores",
+            "key", nullptr
+
+          };
+    static const char* format = "O|O$pOOpO:fast_int";
+
+    /* Read the function argument. */
+    if (!PyArg_ParseTupleAndKeywords(
+            args,
+            kwargs,
+            format,
+            const_cast<char**>(keywords),
+            &input,
+            &default_value,
+            &raise_on_invalid,
+            &on_fail,
+            &pybase,
+            &allow_underscores,
+            &key
+        )) {
+        return nullptr;
+    }
+    if (handle_key_backwards_compatibility(on_fail, key)) {
+        return nullptr;
+    }
+
+    int base = INT_MIN;
+    if (assess_integer_base_input(pybase, base)) {
+        return nullptr;
+    }
+
+    UserOptions options;
+    options.set_base(base);
+    options.set_unicode_allowed(options.is_default_base());
+    options.set_underscores_allowed(allow_underscores);
+
+    const Payload payload = collect_payload(input, options, UserType::INT);
+    return payload.resolve(
+        input, options, nullptr, nullptr, default_value, on_fail, raise_on_invalid
+    );
+}
+
+/* Safely convert to an int (even if in a string and as a float). */
+static PyObject*
+fastnumbers_fast_forceint(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* input = nullptr;
+    PyObject* default_value = nullptr;
+    PyObject* on_fail = nullptr;
+    PyObject* key = nullptr;
+    int raise_on_invalid = false;
+    int allow_underscores = true;
+    static const char* keywords[]
+        = { "x",   "default", "raise_on_invalid", "on_fail", "allow_underscores",
+            "key", nullptr
+
+          };
+    static const char* format = "O|O$pOpO:fast_forceint";
+
+    /* Read the function argument. */
+    if (!PyArg_ParseTupleAndKeywords(
+            args,
+            kwargs,
+            format,
+            const_cast<char**>(keywords),
+            &input,
+            &default_value,
+            &raise_on_invalid,
+            &on_fail,
+            &allow_underscores,
+            &key
+        )) {
+        return nullptr;
+    }
+    if (handle_key_backwards_compatibility(on_fail, key)) {
+        return nullptr;
+    }
+
+    UserOptions options;
+    options.set_underscores_allowed(allow_underscores);
+
+    const Payload payload = collect_payload(input, options, UserType::FORCEINT);
+    return payload.resolve(
+        input, options, nullptr, nullptr, default_value, on_fail, raise_on_invalid
+    );
+}
+
+/* Quickly determine if the input is a real. */
+static PyObject* fastnumbers_isreal(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* input = nullptr;
+    int str_only = false;
+    int num_only = false;
+    int allow_inf = false;
+    int allow_nan = false;
+    int allow_underscores = true;
+    static const char* keywords[]
+        = { "x",         "str_only",          "num_only", "allow_inf",
+            "allow_nan", "allow_underscores", nullptr };
+    static const char* format = "O|$ppppp:isreal";
+
+    /* Read the function argument. */
+    if (!PyArg_ParseTupleAndKeywords(
+            args,
+            kwargs,
+            format,
+            const_cast<char**>(keywords),
+            &input,
+            &str_only,
+            &num_only,
+            &allow_inf,
+            &allow_nan,
+            &allow_underscores
+        )) {
+        return nullptr;
+    }
+
+    return object_is_number(
+        input,
+        UserType::REAL,
+        INT_MIN,
+        allow_nan,
+        allow_inf,
+        str_only,
+        num_only,
+        allow_underscores
+    );
+}
+
+/* Quickly determine if the input is a float. */
+static PyObject* fastnumbers_isfloat(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* input = nullptr;
+    int str_only = false;
+    int num_only = false;
+    int allow_inf = false;
+    int allow_nan = false;
+    int allow_underscores = true;
+    static const char* keywords[]
+        = { "x",         "str_only",          "num_only", "allow_inf",
+            "allow_nan", "allow_underscores", nullptr };
+    static const char* format = "O|$ppppp:isfloat";
+
+    /* Read the function argument. */
+    if (!PyArg_ParseTupleAndKeywords(
+            args,
+            kwargs,
+            format,
+            const_cast<char**>(keywords),
+            &input,
+            &str_only,
+            &num_only,
+            &allow_inf,
+            &allow_nan,
+            &allow_underscores
+        )) {
+        return nullptr;
+    }
+
+    return object_is_number(
+        input,
+        UserType::FLOAT,
+        INT_MIN,
+        allow_nan,
+        allow_inf,
+        str_only,
+        num_only,
+        allow_underscores
+    );
+}
+
+/* Quickly determine if the input is an int. */
+static PyObject* fastnumbers_isint(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* input = nullptr;
+    PyObject* pybase = nullptr;
+    int str_only = false;
+    int num_only = false;
+    int allow_underscores = true;
+    static const char* keywords[]
+        = { "x", "str_only", "num_only", "base", "allow_underscores", nullptr };
+    static const char* format = "O|$ppOp:isint";
+
+    /* Read the function argument. */
+    if (!PyArg_ParseTupleAndKeywords(
+            args,
+            kwargs,
+            format,
+            const_cast<char**>(keywords),
+            &input,
+            &str_only,
+            &num_only,
+            &pybase,
+            &allow_underscores
+        )) {
+        return nullptr;
+    }
+
+    int base = INT_MIN;
+    if (assess_integer_base_input(pybase, base)) {
+        return nullptr;
+    }
+
+    return object_is_number(
+        input, UserType::INT, base, false, false, str_only, num_only, allow_underscores
+    );
+}
+
+/* Quickly determine if the input is int-like. */
+static PyObject* fastnumbers_isintlike(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* input = nullptr;
+    int str_only = false;
+    int num_only = false;
+    int allow_underscores = true;
+    static const char* keywords[]
+        = { "x", "str_only", "num_only", "allow_underscores", nullptr };
+    static const char* format = "O|$ppp:isintlike";
+
+    /* Read the function argument. */
+    if (!PyArg_ParseTupleAndKeywords(
+            args,
+            kwargs,
+            format,
+            const_cast<char**>(keywords),
+            &input,
+            &str_only,
+            &num_only,
+            &allow_underscores
+        )) {
+        return nullptr;
+    }
+
+    return object_is_number(
+        input,
+        UserType::INTLIKE,
+        INT_MIN,
+        false,
+        false,
+        str_only,
+        num_only,
+        allow_underscores
+    );
+}
+
+/* Quickly detect the type. */
+static PyObject* fastnumbers_query_type(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* input = nullptr;
+    PyObject* allowed_types = nullptr;
+    int coerce = false;
+    int allow_inf = false;
+    int allow_nan = false;
+    int allow_underscores = true;
+    static const char* keywords[] = { "x",      "allow_inf",     "allow_nan",
+                                      "coerce", "allowed_types", "allow_underscores",
+                                      nullptr };
+    static const char* format = "O|$pppOp:type";
+
+    /* Read the function argument. */
+    if (!PyArg_ParseTupleAndKeywords(
+            args,
+            kwargs,
+            format,
+            const_cast<char**>(keywords),
+            &input,
+            &allow_inf,
+            &allow_nan,
+            &coerce,
+            &allowed_types,
+            &allow_underscores
+        )) {
+        return nullptr;
+    }
+    // Allowed types must be a non-empty sequence.
+    if (allowed_types != nullptr) {
+        if (!PySequence_Check(allowed_types)) {
+            PyErr_Format(
+                PyExc_TypeError, "allowed_type is not a sequence type: %R", allowed_types
+            );
+            return nullptr;
+        }
+        if (PySequence_Length(allowed_types) < 1) {
+            PyErr_SetString(
+                PyExc_ValueError, "allowed_type must not be an empty sequence"
+            );
+            return nullptr;
+        }
+    }
+
+    // Store the user options in a common interface
+    UserOptions options;
+    options.set_coerce(coerce);
+    options.set_nan_allowed(allow_nan);
+    options.set_inf_allowed(allow_inf);
+    options.set_underscores_allowed(allow_underscores);
+
+    // Attempt to extract character data from the object
+    Buffer buffer;
+    TextExtractor extractor(input, buffer);
+
+    // Create a parser and use it to evaluate the user request
+    PyObject* search_type = nullptr;
+    if (extractor.is_text()) {
+        CharacterParser cparser = extractor.text_parser(options);
+        search_type = query_search_type(
+            Evaluator<CharacterParser>(input, options, cparser), input
+        );
+    } else if (extractor.is_unicode_character()) {
+        UnicodeParser uparser = extractor.unicode_char_parser(options);
+        search_type = query_search_type(
+            Evaluator<UnicodeParser>(input, options, uparser), input
+        );
+    } else {
+        NumericParser nparser(input, options);
+        search_type = query_search_type(
+            Evaluator<NumericParser>(input, options, nparser), input
+        );
+    }
+    return validate_query_type(search_type, allowed_types);
+}
+
+/* Drop-in replacement for int, float */
+static PyObject* fastnumbers_int(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* input = nullptr;
+    PyObject* pybase = nullptr;
+    static const char* format = "|OO:int";
+
+    /* Do not accept number as a keyword argument. */
+    static const char* keywords[] = { "", "base", nullptr };
+
+    /* Read the function argument */
+    if (!PyArg_ParseTupleAndKeywords(
+            args, kwargs, format, const_cast<char**>(keywords), &input, &pybase
+        )) {
+        return nullptr;
+    }
+    /* No arguments returns 0. */
+    if (input == nullptr) {
+        if (pybase != nullptr) {
+            PyErr_SetString(PyExc_TypeError, "int() missing string argument");
+            return nullptr;
+        }
+        return PyLong_FromLong(0);
+    }
+
+    int base = INT_MIN;
+    if (assess_integer_base_input(pybase, base)) {
+        return nullptr;
+    }
+
+    UserOptions options;
+    options.set_base(base);
+    options.set_unicode_allowed(false);
+    options.set_underscores_allowed(true);
+
+    const Payload payload = collect_payload(input, options, UserType::INT);
+    return payload.resolve(input, options, nullptr, nullptr, nullptr, nullptr, true);
+}
+
+static PyObject* fastnumbers_float(PyObject* self, PyObject* args)
+{
+    PyObject* input = nullptr;
+    static const char* format = "|O:float";
+
+    /* Read the function argument - do not accept it as a keyword argument. */
+    if (!PyArg_ParseTuple(args, format, &input)) {
+        return nullptr;
+    }
+
+    /* No arguments returns 0.0. */
+    if (input == nullptr) {
+        return PyFloat_FromDouble(0.0);
+    }
+
+    UserOptions options;
+    options.set_unicode_allowed(false);
+    options.set_underscores_allowed(true);
+
+    const Payload payload = collect_payload(input, options, UserType::FLOAT);
+    return payload.resolve(input, options, nullptr, nullptr, nullptr, nullptr, true);
+}
+
+/* Behaves like float or int, but returns correct type. */
+static PyObject* fastnumbers_real(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+    PyObject* input = nullptr;
+    int coerce = true;
+    static const char* format = "|O$p:real";
+
+    /* Do not accept number as a keyword argument. */
+    static const char* keywords[] = { "", "coerce", nullptr };
+
+    /* Read the function argument. */
+    if (!PyArg_ParseTupleAndKeywords(
+            args, kwargs, format, const_cast<char**>(keywords), &input, &coerce
+        )) {
+        return nullptr;
+    }
+    /* No arguments returns 0.0 or 0 depending on the state of coerce. */
+    if (input == nullptr) {
+        return coerce ? PyLong_FromLong(0) : PyFloat_FromDouble(0.0);
+    }
+
+    UserOptions options;
+    options.set_coerce(coerce);
+    options.set_unicode_allowed(false);
+    options.set_underscores_allowed(true);
+
+    const Payload payload = collect_payload(input, options, UserType::REAL);
+    return payload.resolve(input, options, nullptr, nullptr, nullptr, nullptr, true);
+}
+
+/* This defines the methods contained in this module. */
+static PyMethodDef FastnumbersMethods[] = {
+    { "fast_real",
+      (PyCFunction)fastnumbers_fast_real,
+      METH_VARARGS | METH_KEYWORDS,
+      fast_real__doc__ },
+    { "fast_float",
+      (PyCFunction)fastnumbers_fast_float,
+      METH_VARARGS | METH_KEYWORDS,
+      fast_float__doc__ },
+    { "fast_int",
+      (PyCFunction)fastnumbers_fast_int,
+      METH_VARARGS | METH_KEYWORDS,
+      fast_int__doc__ },
+    { "fast_forceint",
+      (PyCFunction)fastnumbers_fast_forceint,
+      METH_VARARGS | METH_KEYWORDS,
+      fast_forceint__doc__ },
+    { "isreal",
+      (PyCFunction)fastnumbers_isreal,
+      METH_VARARGS | METH_KEYWORDS,
+      isreal__doc__ },
+    { "isfloat",
+      (PyCFunction)fastnumbers_isfloat,
+      METH_VARARGS | METH_KEYWORDS,
+      isfloat__doc__ },
+    { "isint",
+      (PyCFunction)fastnumbers_isint,
+      METH_VARARGS | METH_KEYWORDS,
+      isint__doc__ },
+    { "isintlike",
+      (PyCFunction)fastnumbers_isintlike,
+      METH_VARARGS | METH_KEYWORDS,
+      isintlike__doc__ },
+    { "query_type",
+      (PyCFunction)fastnumbers_query_type,
+      METH_VARARGS | METH_KEYWORDS,
+      query_type__doc__ },
+    { "int",
+      (PyCFunction)fastnumbers_int,
+      METH_VARARGS | METH_KEYWORDS,
+      fastnumbers_int__doc__ },
+    { "float", (PyCFunction)fastnumbers_float, METH_VARARGS, fastnumbers_float__doc__ },
+    { "real",
+      (PyCFunction)fastnumbers_real,
+      METH_VARARGS | METH_KEYWORDS,
+      fastnumbers_real__doc__ },
+    { nullptr, nullptr, 0, nullptr } /* Sentinel */
+};
+
+// We want a module-level variable that is the version.
+static PyObject* fastnumbers__version__;
+
+// Some constants that may be useful for debugging.
+static PyObject* fastnumbers_FN_MAX_INT_LEN;
+static PyObject* fastnumbers_FN_DBL_DIG;
+static PyObject* fastnumbers_FN_MAX_EXP;
+static PyObject* fastnumbers_FN_MIN_EXP;
+
+/* Define the module interface. */
+static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT,
+                                        "fastnumbers",
+                                        fastnumbers__doc__,
+                                        -1,
+                                        FastnumbersMethods,
+                                        nullptr,
+                                        nullptr,
+                                        nullptr,
+                                        nullptr };
+
+PyMODINIT_FUNC PyInit_fastnumbers()
+{
+    PyObject* m = PyModule_Create(&moduledef);
+    if (m == nullptr) {
+        return nullptr;
+    }
+
+    // Add module level constants.
+    fastnumbers__version__ = PyUnicode_FromString(FASTNUMBERS_VERSION);
+    fastnumbers_FN_MAX_INT_LEN = PyLong_FromLong(FN_MAX_INT_LEN);
+    fastnumbers_FN_DBL_DIG = PyLong_FromLong(FN_DBL_DIG);
+    fastnumbers_FN_MAX_EXP = PyLong_FromLong(FN_MAX_EXP);
+    fastnumbers_FN_MIN_EXP = PyLong_FromLong(FN_MIN_EXP);
+    Py_INCREF(fastnumbers__version__);
+    Py_INCREF(fastnumbers_FN_MAX_INT_LEN);
+    Py_INCREF(fastnumbers_FN_DBL_DIG);
+    Py_INCREF(fastnumbers_FN_MAX_EXP);
+    Py_INCREF(fastnumbers_FN_MIN_EXP);
+    PyModule_AddObject(m, "__version__", fastnumbers__version__);
+    PyModule_AddObject(m, "max_int_len", fastnumbers_FN_MAX_INT_LEN);
+    PyModule_AddObject(m, "dig", fastnumbers_FN_DBL_DIG);
+    PyModule_AddObject(m, "max_exp", fastnumbers_FN_MAX_EXP);
+    PyModule_AddObject(m, "min_exp", fastnumbers_FN_MIN_EXP);
+
+    // Constants cached for internal use
+    PyObject* pos_inf_str = PyBytes_FromString("+infinity");
+    PyObject* neg_inf_str = PyBytes_FromString("-infinity");
+    PyObject* pos_nan_str = PyBytes_FromString("+nan");
+    PyObject* neg_nan_str = PyBytes_FromString("-nan");
+    POS_INFINITY = PyFloat_FromString(pos_inf_str);
+    NEG_INFINITY = PyFloat_FromString(neg_inf_str);
+    POS_NAN = PyFloat_FromString(pos_nan_str);
+    NEG_NAN = PyFloat_FromString(neg_nan_str);
+    Py_DecRef(pos_inf_str);
+    Py_DecRef(neg_inf_str);
+    Py_DecRef(pos_nan_str);
+    Py_DecRef(neg_nan_str);
+
+    return m;
+}
diff --git a/src/cpp/parser.cpp b/src/cpp/parser.cpp
new file mode 100644
index 00000000..3fdcd278
--- /dev/null
+++ b/src/cpp/parser.cpp
@@ -0,0 +1,293 @@
+#include "fastnumbers/c_str_parsing.hpp"
+#include "fastnumbers/parser/base.hpp"
+#include "fastnumbers/parser/buffer.hpp"
+#include "fastnumbers/parser/character.hpp"
+#include "fastnumbers/user_options.hpp"
+
+/**
+ * \brief Remove whitespace at the end of a string
+ *
+ * To do this successfully, move the end pointer
+ * back one to not be on the '\0' character, move
+ * backwards, then push the end pointer up one
+ * as if there were still a '\0' character.
+ *
+ * \param start The beginning of the string to strip
+ * \param end The end of the string to strip - updated in-place
+ */
+inline void strip_trailing_whitespace(const char* start, const char*& end)
+{
+    end -= 1;
+    while (start < end && is_whitespace(*end)) {
+        end -= 1;
+    }
+    end += 1;
+}
+
+CharacterParser::CharacterParser(
+    const char* str,
+    const std::size_t len,
+    const UserOptions& options,
+    const bool explict_base_allowed
+)
+    : Parser(ParserType::CHARACTER, options, explict_base_allowed)
+    , m_start(nullptr)
+    , m_end_orig(nullptr)
+    , m_str_len(0)
+{
+    // Skip if this character array points to nothing
+    if (str == nullptr) {
+        set_as_unknown_parser();
+        return;
+    }
+
+    // Store the start and end point of the character array
+    m_start = str;
+    const char* end = m_end_orig = str + len;
+
+    // Strip leading whitespace
+    while (is_whitespace(*m_start)) {
+        m_start += 1;
+    }
+
+    // Strip trailing whitespace.
+    strip_trailing_whitespace(m_start, end);
+
+    // Remove the sign if present and remember what it represents
+    if (*m_start == '+') {
+        m_start += 1;
+    } else if (*m_start == '-') {
+        m_start += 1;
+        set_negative();
+    }
+
+    // Calculate the length of the string after accounting for
+    // whitespace and signs
+    m_str_len = static_cast<std::size_t>(end - m_start);
+}
+
+long CharacterParser::as_int()
+{
+    return as_type<long>(is_likely_int, int_might_overflow, parse_int);
+}
+
+double CharacterParser::as_float()
+{
+    return as_type<double>(is_likely_float, float_might_overflow, parse_float);
+}
+
+PyObject* CharacterParser::as_pyint()
+{
+    reset_error();
+
+    // If the number is negative, include the sign as part of the string
+    const std::size_t offset = std::size_t(is_negative());
+    const char* start = m_start - offset;
+
+    // Attempt to pre-detect if the string is a valid integer.
+    // it is cheaper to do this than to let Python do that and construct an exception.
+    // Need to account for underscores if they exist and we are allowing them.
+    Buffer buffer;
+    const char* my_end = m_end_orig;
+    const bool is_integer = check_string_for_number(
+        buffer,
+        start,
+        my_end,
+        offset,
+        [&](const char* start, const char* end) -> bool {
+            return string_contains_int(start, end, options().get_base());
+        }
+    );
+    if (!is_integer) {
+        encountered_conversion_error();
+        return nullptr;
+    }
+
+    // Parse and record the location where parsing ended (including trailing whitespace)
+    char* their_end = nullptr;
+    PyObject* retval = PyLong_FromString(start, &their_end, options().get_base());
+
+    // Check the parsed end against the original end - if they don't match
+    // there was a parsing error.
+    if (their_end != my_end && retval != nullptr) {
+        encountered_conversion_error();
+        return nullptr;
+    }
+
+    // Return the value without checking python's error state - let Cython handle it
+    return retval;
+}
+
+PyObject* CharacterParser::as_pyfloat()
+{
+    reset_error();
+
+    // If the number is negative, include the sign as part of the string
+    const std::size_t offset = std::size_t(is_negative());
+    const char* start = m_start - offset;
+
+    // Attempt to pre-detect if the string is a valid integer.
+    // it is cheaper to do this than to let Python do that and construct an exception.
+    // Need to account for underscores if they exist and we are allowing them.
+    double retval = -1.0;
+    char* their_end = nullptr;
+    const char* my_end = end();
+    Buffer buffer;
+    if (!check_string_for_number(buffer, start, my_end, offset, string_contains_float)) {
+        encountered_conversion_error();
+        return nullptr;
+    }
+    strip_trailing_whitespace(start, my_end);
+
+    // Parse and record the location where parsing ended (excluding trailing whitespace)
+    retval = PyOS_string_to_double(start, &their_end, nullptr);
+
+    // If an exception was raised propagate it. If the full string was not parsed, raise
+    // an error also.
+    if (retval == -1.0 && PyErr_Occurred()) {
+        return nullptr;
+    } else if (their_end != my_end) {
+        encountered_conversion_error();
+        return nullptr;
+    }
+
+    // Return the value as a python object
+    return PyFloat_FromDouble(retval);
+}
+
+bool CharacterParser::is_infinity() const
+{
+    return quick_detect_infinity(m_start, m_str_len);
+}
+
+bool CharacterParser::is_nan() const
+{
+    return quick_detect_nan(m_start, m_str_len);
+}
+
+bool CharacterParser::is_float() const
+{
+    if (string_contains_float(m_start, end())) {
+        return true;
+    } else if (has_valid_underscores()) {
+        Buffer buffer(m_start, m_str_len);
+        buffer.remove_valid_underscores();
+        return string_contains_float(buffer.start(), buffer.end());
+    } else {
+        return false;
+    }
+}
+
+bool CharacterParser::is_int() const
+{
+    if (string_contains_int(m_start, end(), options().get_base())) {
+        return true;
+    } else if (has_valid_underscores()) {
+        Buffer buffer(m_start, m_str_len);
+        buffer.remove_valid_underscores(!options().is_default_base());
+        return string_contains_int(buffer.start(), buffer.end(), options().get_base());
+    } else {
+        return false;
+    }
+}
+
+bool CharacterParser::is_intlike() const
+{
+    if (string_contains_intlike_float(m_start, end())) {
+        return true;
+    } else if (has_valid_underscores()) {
+        Buffer buffer(m_start, m_str_len);
+        buffer.remove_valid_underscores();
+        return string_contains_intlike_float(buffer.start(), buffer.end());
+    } else {
+        return false;
+    }
+}
+
+template <
+    typename T,
+    typename CheckFunction,
+    typename OverflowCheckFunction,
+    typename ConvertFunction>
+T CharacterParser::as_type(
+    CheckFunction check_function,
+    OverflowCheckFunction overflow_check_function,
+    ConvertFunction convert_function
+)
+{
+    reset_error();
+
+    // Do a quick check if the input likely contains the desired type
+    if (check_function(m_start, m_str_len)) {
+
+        // If the number will be too long report an overflow error
+        if (overflow_check_function(m_start, m_str_len)) {
+            if (has_invalid_underscores()) {
+                encountered_conversion_error();
+            } else {
+                encountered_potential_overflow_error();
+            }
+            return static_cast<T>(-1);
+        }
+
+        // Convert to the desired type - handle the special case
+        // of underscores in the string by removing them and trying again
+        bool error = false;
+        T result = convert_function(m_start, end(), error);
+        if (!error) {
+            return sign() * result;
+        } else if (has_valid_underscores()) {
+            Buffer buffer(m_start, m_str_len);
+            buffer.remove_valid_underscores();
+            result = convert_function(buffer.start(), buffer.end(), error);
+            if (!error) {
+                return sign() * result;
+            }
+        }
+    }
+
+    // Any non-success above ends up here, record as failure
+    encountered_conversion_error();
+    return static_cast<T>(-1);
+}
+
+template <typename Function>
+bool CharacterParser::check_string_for_number(
+    Buffer& buffer,
+    const char*& start,
+    const char*& original_end,
+    const std::size_t offset,
+    Function string_contains_number
+)
+{
+    if (!string_contains_number(m_start, end())) {
+        // If it does not contain a number, see if it is because of underscores.
+        // If so, remove them and try again.
+        // Otherwise, it does not contain a number.
+        if (has_valid_underscores()) {
+            const std::size_t len = static_cast<std::size_t>(original_end - start);
+            buffer.copy(start, len);
+            buffer.remove_valid_underscores(options().get_base() != 10);
+            const char* b_start = buffer.start() + offset;
+            const char* b_end = buffer.end();
+            strip_trailing_whitespace(b_start, b_end);
+            if (!string_contains_number(b_start, b_end)) {
+                return false;
+            }
+            start = buffer.start();
+            original_end = buffer.end();
+        } else {
+            return false;
+        }
+    }
+
+    // If here, the string for sure contains the number
+    return true;
+}
+
+void Buffer::remove_valid_underscores(const bool based)
+{
+    const char* new_end = end();
+    ::remove_valid_underscores(start(), new_end, based);
+    m_len = static_cast<std::size_t>(new_end - start());
+}
diff --git a/src/fastnumbers.c b/src/fastnumbers.c
deleted file mode 100644
index 3d20f746..00000000
--- a/src/fastnumbers.c
+++ /dev/null
@@ -1,556 +0,0 @@
-/*
- * C implementation of the python library fastnumbers.
- *
- * Author: Seth M. Morton, July 30, 2014
- */
-
-#include <Python.h>
-#include <limits.h>
-#include "fastnumbers/version.h"
-#include "fastnumbers/docstrings.h"
-#include "fastnumbers/options.h"
-#include "fastnumbers/objects.h"
-#include "fastnumbers/numbers.h"
-#include "fastnumbers/parsing.h"
-
-
-/* Function to handle the conversion of base to integers.
- * 0 is success, 1 is failure.
- */
-int
-assess_integer_base_input(PyObject *pybase, int *base)
-{
-    Py_ssize_t longbase = 0;
-
-    /* Default to INT_MIN.
-     */
-    if (pybase == NULL) {
-        *base = INT_MIN;
-        return 0;
-    }
-
-    /* Convert to int and check for overflow.
-     */
-    longbase = PyNumber_AsSsize_t(pybase, NULL);
-    if (longbase == -1 && PyErr_Occurred()) {
-        return 1;
-    }
-
-    /* Ensure valid integer in valid range.
-     */
-    if ((longbase != 0 && longbase < 2) || longbase > 36) {
-        PyErr_SetString(PyExc_ValueError,
-                        "int() base must be >= 2 and <= 36");
-        return 1;
-    }
-    *base = (int) longbase;
-    return 0;
-}
-
-
-/* If key is defined, move the value to on_fail.
- * If both were defined, error.
- */
-int
-handle_key_backwards_compatibility(PyObject **on_fail, PyObject **key)
-{
-    if (*key != NULL) {
-        if (*on_fail != NULL) {
-            PyErr_SetString(PyExc_ValueError, "Cannot set both on_fail and key");
-            return 1;
-        }
-        *on_fail = *key;
-        *key = NULL;
-    }
-    return 0;
-}
-
-
-/* Quickly convert to an int or float, depending on value. */
-static PyObject *
-fastnumbers_fast_real(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *input = NULL;
-    PyObject *default_value = NULL;
-    PyObject *key = NULL;
-    int raise_on_invalid =
-        false;  /* cannot use bool with PyArg_ParseTupleAndKeywords */
-    Options opts = init_Options_convert;
-    static char *keywords[] = { "x", "default", "raise_on_invalid",
-                                "on_fail", "inf", "nan", "coerce",
-                                "allow_underscores", "key", NULL
-                              };
-    static const char *format = "O|O$pOOOppO:fast_real";
-
-    /* Read the function argument. */
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords,
-                                     &input, &default_value, &raise_on_invalid,
-                                     &opts.on_fail, &opts.handle_inf, &opts.handle_nan,
-                                     &opts.coerce, &opts.allow_underscores, &key)) {
-        return NULL;
-    }
-    if (handle_key_backwards_compatibility(&opts.on_fail, &key)) {
-        return NULL;
-    }
-    Options_Set_Return_Value(opts, input, default_value, raise_on_invalid);
-
-    return PyObject_to_PyNumber(input, REAL, &opts);
-}
-
-
-/* Quickly convert to a float, depending on value. */
-static PyObject *
-fastnumbers_fast_float(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *input = NULL;
-    PyObject *default_value = NULL;
-    PyObject *key = NULL;
-    int raise_on_invalid =
-        false;  /* cannot use bool with PyArg_ParseTupleAndKeywords */
-    Options opts = init_Options_convert;
-    static char *keywords[] = { "x", "default", "raise_on_invalid",
-                                "on_fail", "inf", "nan",
-                                "allow_underscores", "key", NULL
-                              };
-    static const char *format = "O|O$pOOOpO:fast_float";
-
-    /* Read the function argument. */
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords,
-                                     &input, &default_value, &raise_on_invalid,
-                                     &opts.on_fail, &opts.handle_inf, &opts.handle_nan,
-                                     &opts.allow_underscores, &key)) {
-        return NULL;
-    }
-    if (handle_key_backwards_compatibility(&opts.on_fail, &key)) {
-        return NULL;
-    }
-    Options_Set_Return_Value(opts, input, default_value, raise_on_invalid);
-
-    return PyObject_to_PyNumber(input, FLOAT, &opts);
-}
-
-
-/* Quickly convert to an int, depending on value. */
-static PyObject *
-fastnumbers_fast_int(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *input = NULL;
-    PyObject *default_value = NULL;
-    PyObject *base = NULL;
-    PyObject *key = NULL;
-    int raise_on_invalid =
-        false;  /* cannot use bool with PyArg_ParseTupleAndKeywords */
-    Options opts = init_Options_convert;
-    static char *keywords[] = { "x", "default", "raise_on_invalid",
-                                "on_fail", "base",
-                                "allow_underscores", "key", NULL
-
-                              };
-    static const char *format = "O|O$pOOpO:fast_int";
-
-    /* Read the function argument. */
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords,
-                                     &input, &default_value, &raise_on_invalid,
-                                     &opts.on_fail, &base,
-                                     &opts.allow_underscores, &key)) {
-        return NULL;
-    }
-    if (handle_key_backwards_compatibility(&opts.on_fail, &key)) {
-        return NULL;
-    }
-    Options_Set_Return_Value(opts, input, default_value, raise_on_invalid);
-    if (assess_integer_base_input(base, &opts.base)) {
-        return NULL;
-    }
-
-    return PyObject_to_PyNumber(input, INT, &opts);
-}
-
-
-/* Safely convert to an int (even if in a string and as a float). */
-static PyObject *
-fastnumbers_fast_forceint(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *input = NULL;
-    PyObject *default_value = NULL;
-    PyObject *key = NULL;
-    int raise_on_invalid =
-        false;  /* cannot use bool with PyArg_ParseTupleAndKeywords */
-    Options opts = init_Options_convert;
-    static char *keywords[] = { "x", "default", "raise_on_invalid",
-                                "on_fail", "allow_underscores", "key", NULL
-                              };
-    static const char *format = "O|O$pOpO:fast_forceint";
-
-    /* Read the function argument. */
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords,
-                                     &input, &default_value, &raise_on_invalid,
-                                     &opts.on_fail, &opts.allow_underscores, &key)) {
-        return NULL;
-    }
-    if (handle_key_backwards_compatibility(&opts.on_fail, &key)) {
-        return NULL;
-    }
-    Options_Set_Return_Value(opts, input, default_value, raise_on_invalid);
-
-    return PyObject_to_PyNumber(input, FORCEINT, &opts);
-}
-
-
-/* Quickly determine if the input is a real. */
-static PyObject *
-fastnumbers_isreal(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *input = NULL;
-    Options opts = init_Options_check;
-    static char *keywords[] = { "x", "str_only", "num_only",
-                                "allow_inf", "allow_nan",
-                                "allow_underscores", NULL
-                              };
-    static const char *format = "O|$ppOOp:isreal";
-
-    /* Read the function argument. */
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords,
-                                     &input, &opts.str_only, &opts.num_only,
-                                     &opts.handle_inf, &opts.handle_nan,
-                                     &opts.allow_underscores)) {
-        return NULL;
-    }
-
-    return PyObject_is_number(input, REAL, &opts);
-}
-
-
-/* Quickly determine if the input is a float. */
-static PyObject *
-fastnumbers_isfloat(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *input = NULL;
-    Options opts = init_Options_check;
-    static char *keywords[] = { "x", "str_only", "num_only",
-                                "allow_inf", "allow_nan",
-                                "allow_underscores", NULL
-                              };
-    static const char *format = "O|$ppOOp:isfloat";
-
-    /* Read the function argument. */
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords,
-                                     &input, &opts.str_only, &opts.num_only,
-                                     &opts.handle_inf, &opts.handle_nan,
-                                     &opts.allow_underscores)) {
-        return NULL;
-    }
-
-    return PyObject_is_number(input, FLOAT, &opts);
-}
-
-
-/* Quickly determine if the input is an int. */
-static PyObject *
-fastnumbers_isint(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *input = NULL;
-    PyObject *base = NULL;
-    Options opts = init_Options_check;
-    static char *keywords[] = { "x", "str_only", "num_only", "base",
-                                "allow_underscores", NULL
-                              };
-    static const char *format = "O|$ppOp:isint";
-
-    /* Read the function argument. */
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords,
-                                     &input, &opts.str_only, &opts.num_only,
-                                     &base, &opts.allow_underscores)) {
-        return NULL;
-    }
-    if (assess_integer_base_input(base, &opts.base)) {
-        return NULL;
-    }
-
-    return PyObject_is_number(input, INT, &opts);
-}
-
-
-/* Quickly determine if the input is int-like. */
-static PyObject *
-fastnumbers_isintlike(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *input = NULL;
-    Options opts = init_Options_check;
-    static char *keywords[] = { "x", "str_only", "num_only",
-                                "allow_underscores", NULL
-                              };
-    static const char *format = "O|$ppp:isintlike";
-
-    /* Read the function argument. */
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords,
-                                     &input, &opts.str_only, &opts.num_only,
-                                     &opts.allow_underscores)) {
-        return NULL;
-    }
-
-    return PyObject_is_number(input, INTLIKE, &opts);
-}
-
-
-/* Quickly detect the type. */
-static PyObject *
-fastnumbers_query_type(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *input = NULL;
-    PyObject *allowed_types = NULL;
-    PyObject *result = NULL;
-    Options opts = init_Options_check;
-    static char *keywords[] = { "x", "allow_inf", "allow_nan", "coerce",
-                                "allowed_types", "allow_underscores", NULL
-                              };
-    static const char *format = "O|$OOOOp:type";
-
-    /* Coerce is false by default here. */
-    opts.coerce = false;
-
-    /* Read the function argument. */
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords,
-                                     &input, &opts.handle_inf, &opts.handle_nan,
-                                     &opts.coerce, &allowed_types,
-                                     &opts.allow_underscores)) {
-        return NULL;
-    }
-    // Allowed types must be a non-empty sequence.
-    if (allowed_types != NULL) {
-        if (!PySequence_Check(allowed_types)) {
-            PyErr_Format(
-                PyExc_TypeError,
-                "allowed_type is not a sequence type: %R",
-                allowed_types
-            );
-            return NULL;
-        }
-        if (PySequence_Length(allowed_types) < 1) {
-            PyErr_SetString(
-                PyExc_ValueError,
-                "allowed_type must not be an empty sequence"
-            );
-            return NULL;
-        }
-    }
-
-    result = PyObject_contains_type(input, &opts);
-
-    /* If the result is not one of the allowed types, return None instead */
-    if (result != NULL && allowed_types != NULL &&
-            !PySequence_Contains(allowed_types, result))
-    {
-        Py_DECREF(result);
-        Py_RETURN_NONE;
-    }
-    else {
-        return result;
-    }
-}
-
-
-/* Drop-in replacement for int, float */
-static PyObject *
-fastnumbers_int(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *input = NULL;
-    PyObject *base = NULL;
-    Options opts = init_Options_convert;
-    static const char *format = "|OO:int";
-
-#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7)
-    /* Do not accept number as a keyword argument. */
-    static char *keywords[] = { "", "base", NULL };
-#else
-    /* Do accept number as a keyword argument. */
-    static char *keywords[] = { "x", "base", NULL };
-#endif
-
-    /* Read the function argument */
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords,
-                                     &input, &base)) {
-        return NULL;
-    }
-    if (assess_integer_base_input(base, &opts.base)) {
-        return NULL;
-    }
-    /* No arguments returns 0. */
-    if (input == NULL) {
-        if (!Options_Default_Base(&opts)) {
-            PyErr_SetString(PyExc_TypeError, "int() missing string argument");
-            return NULL;
-        }
-        return PyLong_FromLong(0);
-    }
-    Options_Set_Return_Value(opts, input, NULL, true);
-    Options_Set_Disallow_UnicodeCharacter(&opts);
-
-    return PyObject_to_PyNumber(input, INT, &opts);
-}
-
-
-static PyObject *
-#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7)
-fastnumbers_float(PyObject *self, PyObject *args)
-#else
-fastnumbers_float(PyObject *self, PyObject *args, PyObject *kwargs)
-#endif
-{
-    PyObject *input = NULL;
-    Options opts = init_Options_convert;
-    static const char *format = "|O:float";
-
-#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7)
-    /* Read the function argument - do not accept it as a keyword argument. */
-    if (!PyArg_ParseTuple(args, format, &input)) {
-        return NULL;
-    }
-#else
-    static char *keywords[] = { "x", NULL };
-
-    /* Read the function argument - accept it as a keyword argument. */
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords, &input)) {
-        return NULL;
-    }
-#endif
-
-    /* No arguments returns 0.0. */
-    if (input == NULL) {
-        return PyFloat_FromDouble(0.0);
-    }
-    Options_Set_Return_Value(opts, input, NULL, true);
-    Options_Set_Disallow_UnicodeCharacter(&opts);
-
-    return PyObject_to_PyNumber(input, FLOAT, &opts);
-}
-
-
-/* Behaves like float or int, but returns correct type. */
-static PyObject *
-fastnumbers_real(PyObject *self, PyObject *args, PyObject *kwargs)
-{
-    PyObject *input = NULL;
-    Options opts = init_Options_convert;
-    static const char *format = "|O$p:real";
-
-#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7)
-    /* Do not accept number as a keyword argument. */
-    static char *keywords[] = { "", "coerce", NULL };
-#else
-    /* Do accept number as a keyword argument. */
-    static char *keywords[] = { "x", "coerce", NULL };
-#endif
-
-    /* Read the function argument. */
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, format, keywords,
-                                     &input, &opts.coerce)) {
-        return NULL;
-    }
-    /* No arguments returns 0.0 or 0 depending on the state of coerce. */
-    if (input == NULL)
-        return Options_Coerce_True(&opts) ? PyLong_FromLong(0)
-               : PyFloat_FromDouble(0.0);
-    Options_Set_Return_Value(opts, input, NULL, true);
-    Options_Set_Disallow_UnicodeCharacter(&opts);
-
-    return PyObject_to_PyNumber(input, REAL, &opts);
-}
-
-
-/* This defines the methods contained in this module. */
-static PyMethodDef FastnumbersMethods[] = {
-    {   "fast_real", (PyCFunction) fastnumbers_fast_real,
-        METH_VARARGS | METH_KEYWORDS, fast_real__doc__
-    },
-    {   "fast_float", (PyCFunction) fastnumbers_fast_float,
-        METH_VARARGS | METH_KEYWORDS, fast_float__doc__
-    },
-    {   "fast_int", (PyCFunction) fastnumbers_fast_int,
-        METH_VARARGS | METH_KEYWORDS, fast_int__doc__
-    },
-    {   "fast_forceint", (PyCFunction) fastnumbers_fast_forceint,
-        METH_VARARGS | METH_KEYWORDS, fast_forceint__doc__
-    },
-    {   "isreal", (PyCFunction) fastnumbers_isreal,
-        METH_VARARGS | METH_KEYWORDS, isreal__doc__
-    },
-    {   "isfloat", (PyCFunction) fastnumbers_isfloat,
-        METH_VARARGS | METH_KEYWORDS, isfloat__doc__
-    },
-    {   "isint", (PyCFunction) fastnumbers_isint,
-        METH_VARARGS | METH_KEYWORDS, isint__doc__
-    },
-    {   "isintlike", (PyCFunction) fastnumbers_isintlike,
-        METH_VARARGS | METH_KEYWORDS, isintlike__doc__
-    },
-    {   "query_type", (PyCFunction) fastnumbers_query_type,
-        METH_VARARGS | METH_KEYWORDS, query_type__doc__
-    },
-    {   "int", (PyCFunction) fastnumbers_int,
-        METH_VARARGS | METH_KEYWORDS, fastnumbers_int__doc__
-    },
-    {   "float", (PyCFunction) fastnumbers_float,
-#if PY_MAJOR_VERSION > 3 || (PY_MAJOR_VERSION == 3 && PY_MINOR_VERSION >= 7)
-        METH_VARARGS, fastnumbers_float__doc__
-#else
-        METH_VARARGS | METH_KEYWORDS, fastnumbers_float__doc__
-#endif
-    },
-    {   "real", (PyCFunction) fastnumbers_real,
-        METH_VARARGS | METH_KEYWORDS, fastnumbers_real__doc__
-    },
-    {NULL, NULL, 0, NULL} /* Sentinel */
-};
-
-
-/* We want a module-level variable that is the version. */
-static PyObject *fastnumbers__version__;
-
-/* Some constants that may be useful for debugging. */
-static PyObject *fastnumbers_FN_MAX_INT_LEN;
-static PyObject *fastnumbers_FN_DBL_DIG;
-static PyObject *fastnumbers_FN_MAX_EXP;
-static PyObject *fastnumbers_FN_MIN_EXP;
-
-/* Define the module interface. */
-static struct PyModuleDef moduledef = {
-    PyModuleDef_HEAD_INIT,
-    "fastnumbers",
-    fastnumbers__doc__,
-    -1,
-    FastnumbersMethods,
-    NULL,
-    NULL,
-    NULL,
-    NULL
-};
-
-PyObject *
-PyInit_fastnumbers(void)
-{
-    PyObject *m = PyModule_Create(&moduledef);
-    if (m == NULL) {
-        return NULL;
-    }
-
-    /* Add module level constants. */
-    fastnumbers__version__ = PyUnicode_FromString(FASTNUMBERS_VERSION);
-    fastnumbers_FN_MAX_INT_LEN = PyLong_FromLong(FN_MAX_INT_LEN);
-    fastnumbers_FN_DBL_DIG = PyLong_FromLong(FN_DBL_DIG);
-    fastnumbers_FN_MAX_EXP = PyLong_FromLong(FN_MAX_EXP);
-    fastnumbers_FN_MIN_EXP = PyLong_FromLong(FN_MIN_EXP);
-    Py_INCREF(fastnumbers__version__);
-    Py_INCREF(fastnumbers_FN_MAX_INT_LEN);
-    Py_INCREF(fastnumbers_FN_DBL_DIG);
-    Py_INCREF(fastnumbers_FN_MAX_EXP);
-    Py_INCREF(fastnumbers_FN_MIN_EXP);
-    PyModule_AddObject(m, "__version__", fastnumbers__version__);
-    PyModule_AddObject(m, "max_int_len", fastnumbers_FN_MAX_INT_LEN);
-    PyModule_AddObject(m, "dig", fastnumbers_FN_DBL_DIG);
-    PyModule_AddObject(m, "max_exp", fastnumbers_FN_MAX_EXP);
-    PyModule_AddObject(m, "min_exp", fastnumbers_FN_MIN_EXP);
-
-    return m;
-}
-
diff --git a/src/numbers.c b/src/numbers.c
deleted file mode 100644
index 85ccc5a2..00000000
--- a/src/numbers.c
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Functions that will convert/evaluate a Python number.
- *
- * Author: Seth M. Morton
- *
- * July 2018
- */
-
-#include <Python.h>
-#include <float.h>
-#include "fastnumbers/numbers.h"
-#include "fastnumbers/options.h"
-#include "fastnumbers/pstdint.h"
-
-
-/* Ensure 64 bits are handled. */
-#ifndef INT64_MAX
-#error "fastnumbers requires that your compiler support 64 bit integers, but it appears that this compiler does not"
-#endif
-
-
-/*
- * Copy of _Py_dg_stdnan from the Python code base.
- * Dynamically generate an NaN. Needed for Windows.
- * I hate copy/pasting code, but this seems like the only way.
- */
-typedef union {
-    double d;
-    uint32_t L[2];
-} U;
-#ifdef DOUBLE_IS_LITTLE_ENDIAN_IEEE754
-#define word0(x) (x)->L[1]
-#define word1(x) (x)->L[0]
-#else
-#define word0(x) (x)->L[0]
-#define word1(x) (x)->L[1]
-#endif
-#define dval(x) (x)->d
-#define NAN_WORD0 0x7ff80000
-#define NAN_WORD1 0
-#define Sign_bit 0x80000000
-double
-compat_generate_nan(int sign) {
-    U rv;
-    word0(&rv) = NAN_WORD0;
-    word1(&rv) = NAN_WORD1;
-    if (sign) {
-        word0(&rv) |= Sign_bit;
-    }
-    return dval(&rv);
-}
-
-
-static bool
-_PyFloat_is_Intlike(PyObject *obj) {
-    /* NOTE: This code copy/pasted with modification from the CPython source.
-             They did not expose it as public for some reason.
-     */
-    bool retval = false;
-    const double x = PyFloat_AsDouble(obj);
-
-    if (x == -1.0 && PyErr_Occurred()) {
-        return PyErr_Clear(), false;
-    }
-    if (!Py_IS_FINITE(x)) {
-        return false;
-    }
-    errno = 0;
-    retval = (floor(x) == x) ? true : false;
-    if (errno != 0) {
-        return false;
-    }
-    return retval;
-}
-
-
-static PyObject *
-PyNumber_to_PyInt_or_PyFloat(PyObject *pynum, const Options *options)
-{
-    if (Options_Has_NaN_Sub(options) && PyNumber_IsNAN(pynum)) {
-        return Options_Return_NaN_Sub(options);
-    }
-    else if (Options_Has_INF_Sub(options) && PyNumber_IsINF(pynum)) {
-        return Options_Return_INF_Sub(options);
-    }
-    else if (Options_Coerce_True(options)) {
-        if (PyLong_Check(pynum) || PyFloat_is_Intlike(pynum)) {
-            return PyNumber_Long(pynum);
-        }
-        else {
-            return PyNumber_Float(pynum);
-        }
-    }
-    else {
-        return Py_INCREF(pynum), pynum;
-    }
-}
-
-
-static PyObject *
-PyNumber_to_PyFloat(PyObject *pynum, const Options *options)
-{
-    if (Options_Has_NaN_Sub(options) && PyNumber_IsNAN(pynum)) {
-        return Options_Return_NaN_Sub(options);
-    }
-    else if (Options_Has_INF_Sub(options) && PyNumber_IsINF(pynum)) {
-        return Options_Return_INF_Sub(options);
-    }
-    else {
-        return PyNumber_Float(pynum);
-    }
-}
-
-
-static PyObject *
-PyNumber_to_PyInt(PyObject *pynum, const Options *options)
-{
-    if (PyFloat_Check(pynum)) { /* Watch out for un-intable numbers. */
-        const double d = PyFloat_AS_DOUBLE(pynum);
-        if (Py_IS_INFINITY(d)) {
-            if (Options_Should_Raise(options))
-                PyErr_SetString(PyExc_OverflowError,
-                                "cannot convert float infinity to integer");
-            return NULL;
-        }
-        if (Py_IS_NAN(d)) {
-            if (Options_Should_Raise(options))
-                PyErr_SetString(PyExc_ValueError,
-                                "cannot convert float NaN to integer");
-            return NULL;
-        }
-    }
-    return PyNumber_Long(pynum);
-}
-
-
-PyObject *
-PyFloat_to_PyInt(PyObject *fobj, const Options *options)
-{
-    PyObject *tmp = PyNumber_to_PyInt(fobj, options);
-    Py_DECREF(fobj);
-    return tmp;
-}
-
-
-bool
-PyFloat_is_Intlike(PyObject *obj)
-{
-    const double dval = PyFloat_AS_DOUBLE(obj);
-    if (!PyFloat_Check(obj)) {
-        return false;
-    }
-    if (dval < INT64_MAX && dval > INT64_MIN) {
-        return dval == (int64_t) dval;
-    }
-    return _PyFloat_is_Intlike(obj);
-}
-
-
-/* Convert a PyNumber to the desired PyNumber type. */
-PyObject *
-PyNumber_to_PyNumber(PyObject *pynum, const PyNumberType type,
-                     const Options *options)
-{
-    PyObject *pyresult = NULL;
-    switch (type) {
-    case REAL:
-        pyresult = PyNumber_to_PyInt_or_PyFloat(pynum, options);
-        break;
-    case FLOAT:
-        pyresult = PyNumber_to_PyFloat(pynum, options);
-        break;
-    case INT:
-    case FORCEINT:
-    case INTLIKE:
-        pyresult = PyNumber_to_PyInt(pynum, options);
-        break;
-    }
-    /* Clear any error if the result is NULL
-     * and we do not want to raise on errors.
-     */
-    if (pyresult == NULL && !Options_Should_Raise(options)) {
-        PyErr_Clear();
-    }
-    return pyresult;
-}
-
-
-/* Check that a PyNumber is the desired PyNumber type. */
-bool
-PyNumber_is_type(PyObject *obj, const PyNumberType type)
-{
-    register bool result = false;
-    switch (type) {
-    case REAL:
-        result = true;
-        break;
-    case FLOAT:
-        result = PyFloat_Check(obj);
-        break;
-    case INT:
-        result = PyLong_Check(obj);
-        break;
-    case INTLIKE:
-    case FORCEINT:
-        result = PyLong_Check(obj) || PyFloat_is_Intlike(obj);
-        break;
-    }
-    return result;
-}
diff --git a/src/objects.c b/src/objects.c
deleted file mode 100644
index cc983f2a..00000000
--- a/src/objects.c
+++ /dev/null
@@ -1,167 +0,0 @@
-/*
- * Functions that will convert/evaluate an arbitrary Python object.
- *
- * Author: Seth M. Morton
- *
- * July 2018
- */
-
-#include <Python.h>
-#include <limits.h>
-#include "fastnumbers/objects.h"
-#include "fastnumbers/numbers.h"
-#include "fastnumbers/strings.h"
-#include "fastnumbers/unicode_character.h"
-#include "fastnumbers/options.h"
-
-
-#define RETURN_CORRECT_RESULT(ret, opt) \
-    ((ret) ? (ret) : Options_Return_Correct_Result_On_Error(opt))
-
-
-/* Attempt to convert an arbitrary PyObject to a PyNumber. */
-PyObject *
-PyObject_to_PyNumber(PyObject *obj, const PyNumberType type,
-                     const Options *options)
-{
-    PyObject *pyresult = NULL;
-
-    /* Already a number? Simple conversion will work.
-     * Do not accept numbers if base was explicitly given.
-     */
-    if (PyNumber_Check(obj)) {
-        if (Options_Default_Base(options)) {
-            pyresult = PyNumber_to_PyNumber(obj, type, options);
-            return RETURN_CORRECT_RESULT(pyresult, options);
-        }
-        else {
-            SET_ILLEGAL_BASE_ERROR(options);
-            return RETURN_CORRECT_RESULT(NULL, options);
-        }
-    }
-
-    /* Assume a string. */
-    pyresult = PyString_to_PyNumber(obj, type, options);
-    if (errno == ENOMEM) {
-        return NULL;  /* ALWAYS raise on out-of-memory errors. */
-    }
-    if (pyresult != Py_None) {
-        return RETURN_CORRECT_RESULT(pyresult, options);
-    }
-
-    /* If the base was given explicitly, unicode should not be accepted. */
-    if (!Options_Default_Base(options)) {
-        SET_ERR_INVALID_INT(options);
-        return RETURN_CORRECT_RESULT(NULL, options);
-    }
-
-    /* Assume unicode character. */
-    if (PyUnicode_Check(obj)) {
-        if (Options_Allow_UnicodeCharacter(options)) {
-            pyresult = PyUnicodeCharacter_to_PyNumber(obj, type, options);
-            if (pyresult != Py_None) {
-                return RETURN_CORRECT_RESULT(pyresult, options);
-            }
-        }
-        else {
-            /* If unicode characters are not allowed, return an error. */
-            if (type == REAL || type == FLOAT) {
-                SET_ERR_INVALID_FLOAT(options);
-            }
-            else {
-                SET_ERR_INVALID_INT(options);
-            }
-            return NULL;
-        }
-    }
-
-    /* Nothing worked - must be a TypeError */
-    PyErr_Format(PyExc_TypeError,
-                 (type == REAL || type == FLOAT) ?
-                 "float() argument must be a string or a number, not '%s'" :
-                 "int() argument must be a string, a bytes-like object "
-                 "or a number, not '%s'",
-                 Py_TYPE(options->input)->tp_name);
-    return NULL;
-}
-
-
-/* Check if an arbitrary PyObject is a PyNumber. */
-PyObject *
-PyObject_is_number(PyObject *obj, const PyNumberType type,
-                   const Options *options)
-{
-    PyObject *pyresult = NULL;
-
-    /* Already a number? Simple checks will work. */
-    if (PyNumber_Check(obj)) {
-        if (!Options_String_Only(options) && PyNumber_is_type(obj, type)) {
-            Py_RETURN_TRUE;
-        }
-        else {
-            Py_RETURN_FALSE;
-        }
-    }
-
-    /* If we are requiring it to be a number then we must declare false now. */
-    else if (Options_Number_Only(options)) {
-        Py_RETURN_FALSE;
-    }
-
-    /* Assume a string. */
-    pyresult = PyString_is_number(obj, type, options);
-    if (pyresult == NULL || errno == ENOMEM) {
-        return NULL; /* ALWAYS raise on out-of-memory errors. */
-    }
-    else if (pyresult != Py_None) {
-        return pyresult;
-    }
-
-    /* Assume unicode. */
-    pyresult = PyUnicodeCharacter_is_number(obj, type);
-    if (pyresult != Py_None) {
-        return pyresult;
-    }
-
-    /* If we got here, the type was invalid so return False. */
-    Py_RETURN_FALSE;
-}
-
-
-/* Return the type contained the object. */
-PyObject *
-PyObject_contains_type(PyObject *obj, const Options *options)
-{
-    PyObject *pyresult = NULL;
-
-    /* Already a number? Just return the type directly. */
-    if (PyLong_Check(obj) || (Options_Coerce_True(options) &&
-                              PyFloat_is_Intlike(obj))) {
-        pyresult = (PyObject *) &PyLong_Type;
-        Py_INCREF(pyresult);
-        return pyresult;
-    }
-    else if (PyFloat_Check(obj)) {
-        pyresult = (PyObject *) &PyFloat_Type;
-        Py_INCREF(pyresult);
-        return pyresult;
-    }
-
-    /* Assume a string. */
-    pyresult = PyString_contains_type(obj, options);
-    if (pyresult == NULL || errno == ENOMEM) {
-        return NULL; /* ALWAYS raise on out-of-memory errors. */
-    }
-    else if (pyresult != Py_None) {
-        return pyresult;
-    }
-
-    /* Assume unicode. */
-    pyresult = PyUnicodeCharacter_contains_type(obj);
-    if (pyresult != Py_None) {
-        return pyresult;
-    }
-
-    /* If we got here, the type was invalid. */
-    return PyObject_Type(obj);
-}
diff --git a/src/parsing.c b/src/parsing.c
deleted file mode 100644
index 5884a27b..00000000
--- a/src/parsing.c
+++ /dev/null
@@ -1,580 +0,0 @@
-/* Parse strings for numbers.
- *
- * Author: Seth M. Morton
- *
- * July 2018
- */
-#include <Python.h>
-#include <string.h>
-#include "fastnumbers/parsing.h"
-#include "fastnumbers/pstdint.h"
-
-/******************/
-/****** NOTE ******/
-/******************/
-/* All functions in this file assume whitespace has been trimmed
- * from both sides of the string, and that the sign has been removed.
- */
-
-/* Forward declarations. */
-static uint16_t
-number_trailing_zeros(register const char *start, register const char *end);
-static int
-detect_base(register const char *str, register const char *end);
-static long double
-power_of_ten_scaling_factor(const int expon);
-
-
-/* Local convenience macros.
- * These each provide an abstraction over parsing components of a number.
- * The user can provide a "payload" of commands to execute for each
- * iteration of the parsing loop. It is recommended to put the payload
- * in a block.
- */
-#define parse_integer_macro(str, valid, payload) \
-    while (is_valid_digit(str)) { \
-        payload; \
-        (str) += 1; \
-        (valid) = true; \
-    }
-
-#define parse_decimal_macro(str, valid, payload) \
-    if (*(str) == '.') { \
-        (str) += 1; \
-        parse_integer_macro(str, valid, payload); \
-    }
-
-#define parse_exponent_macro(str, valid, negative_payload, payload) \
-    if ((*(str) == 'e' || *(str) == 'E') && (valid)) { \
-        (str) += 1; \
-        if (is_sign(str)) { \
-            if (*(str) == '-') { \
-                negative_payload; \
-            } \
-            (str) += 1; \
-        } \
-        (valid) = false; \
-        while (is_valid_digit(str)) { \
-            payload; \
-            (str) += 1; \
-            (valid) = true; \
-        } \
-    }
-
-
-bool
-string_contains_int(register const char *str, register const char *end,
-                    int base)
-{
-    register bool valid = false;
-
-    if (base == 0) {
-        base = detect_base(str, end);
-    }
-
-    /* If base 10, take fast route. */
-    if (base == 10) {
-        parse_integer_macro(str, valid, {});
-        return valid && str == end;
-    }
-    else if (base == -1) {
-        return false;
-    }
-    else {
-        const register Py_ssize_t len = end - str;
-
-        /* Skip leading characters for non-base 10 ints. */
-        if (len > 1 && str[0] == '0' &&
-                ((base == 16 && (str[1] == 'x' || str[1] == 'X')) ||
-                 (base == 8  && (str[1] == 'o' || str[1] == 'O')) ||
-                 (base == 2  && (str[1] == 'b' || str[1] == 'B')))) {
-            str += 2;
-        }
-
-        /* The rest behaves as normal. */
-        while (is_valid_digit_arbitrary_base(*str, base)) {
-            str += 1;
-            valid = true;
-        }
-        return valid && str == end;
-    }
-}
-
-
-bool
-string_contains_float(register const char *str,
-                      register const char *end,
-                      const bool allow_inf,
-                      const bool allow_nan)
-{
-    register bool valid = false;
-    const register Py_ssize_t len = end - str;
-
-    /* NAN or INF */
-    if (quick_detect_infinity(str, len)) {
-        return allow_inf;
-    }
-    if (quick_detect_nan(str, len)) {
-        return allow_nan;
-    }
-
-    parse_integer_macro(str, valid, {});
-    parse_decimal_macro(str, valid, {});
-    parse_exponent_macro(str, valid, {}, {});
-    return valid && str == end;
-}
-
-
-bool
-string_contains_intlike_float(register const char *str,
-                              register const char *end)
-{
-    register bool valid = false;
-    register bool exp_negative = false;
-    register int16_t expon = 0;
-    register uint16_t dec_length = 0;
-    register const char *int_start, *decimal_start, *decimal_end;
-
-    /* Before decimal. Keep track of number of digits read. */
-    int_start = str;
-    parse_integer_macro(str, valid, {});
-
-    /* Decimal part of float. Keep track of number of digits read */
-    /* as well as beginning and end locations. */
-    decimal_start = str;
-    parse_decimal_macro(str, valid, { dec_length += 1; });
-    decimal_end = str;
-
-    /* Exponential part of float. Parse the magnitude. */
-    parse_exponent_macro(str, valid, { exp_negative = true; }, {
-        expon *= 10;
-        expon += ascii2int(str);
-    });
-
-    if (!valid || str != end) {
-        return false;
-    }
-    else {
-        /* If we "move the decimal place" left or right depending on
-         * exponent sign and magnitude, all digits after the decimal
-         * must be zero.
-         */
-        const uint16_t int_trailing_zeros = number_trailing_zeros(
-                                                int_start, decimal_start
-                                            );
-        const uint16_t dec_trailing_zeros =
-            decimal_start == decimal_end
-            ? 0
-            : number_trailing_zeros(decimal_start + 1, decimal_end);
-
-        if (exp_negative) {
-            return expon <= int_trailing_zeros &&
-                   dec_length == dec_trailing_zeros;
-        }
-        else {
-            return expon >= (dec_length - dec_trailing_zeros);
-        }
-    }
-}
-
-
-long
-parse_int(register const char *str, register const char *end, bool *error)
-{
-    register bool valid = false;
-    register long value = 0L;
-
-    /* Convert digits, if any. */
-    parse_integer_macro(str, valid, {
-        value *= 10L;
-        value += ascii2long(str);
-    });
-
-    *error = !valid || str != end;
-    return value;
-}
-
-
-double
-parse_float(register const char *str, register const char *end, bool *error,
-            const int8_t sign)
-{
-    register bool valid = false;
-    register uint64_t intvalue = 0UL;
-    register uint16_t decimal_len = 0;
-    register int16_t expon = 0;
-    register int16_t exp_sign = 1;
-    long double retval = 0;
-
-    /* Parse integer part. */
-    parse_integer_macro(str, valid, {
-        intvalue *= 10UL;
-        intvalue += ascii2ulong(str);
-    });
-
-    /* Parse decimal part. */
-    parse_decimal_macro(str, valid, {
-        intvalue *= 10UL;
-        intvalue += ascii2ulong(str);
-        decimal_len += 1U;
-    });
-
-    /* Parse exponential part. */
-    parse_exponent_macro(str, valid, { exp_sign = -1; }, {
-        expon *= 10;
-        expon += ascii2int(str);
-    });
-    expon *= exp_sign;
-    expon -= decimal_len; /* Adjust the exponent by the # of decimal places */
-
-    *error = !valid || str != end;
-    if (expon < 0) {
-        retval = (long double) intvalue / power_of_ten_scaling_factor(abs(expon));
-    }
-    else {
-        retval = (long double) intvalue * power_of_ten_scaling_factor(expon);
-    }
-    return (long double) sign * retval;
-}
-
-
-bool
-float_might_overflow(register const char *str, register const Py_ssize_t len)
-{
-    /* Locate the decimal place (if any). */
-    register const char *decimal_loc = (const char *) memchr(str, '.', len);
-    register const bool has_decimal = (bool) decimal_loc;
-
-    /* Find the exponent (if any) in the input.
-     * It is always after the exponent, usually close to the end, so
-     * we will start from the back. No need to include the stop
-     * location since that is either a decimal or the first digit
-     * which cannot be 'e' or 'E'.
-     */
-    register const char *ptr = NULL;
-    register const char *exp = NULL;
-    register const char *stop = decimal_loc ? decimal_loc : str;
-    for (ptr = str + len - 1; ptr > stop; ptr--) {
-        if (*ptr == 'e' || *ptr == 'E') {
-            exp = ptr;
-            break;
-        }
-    }
-
-    /* If the number of pre-exponent digits is greater than the known
-     * value it might overflow.
-     */
-    if ((exp ? (exp - str) : len) - (Py_ssize_t)has_decimal > FN_DBL_DIG) {
-        return true;
-    }
-
-    /* If an exponent was found, ensure it is within chosen range. */
-    if (exp) {
-        register bool neg = *(++exp) == '-'; /* First remove 'e' or 'E'. */
-        register Py_ssize_t exp_len = len - (exp - str) ;
-        if (is_sign(exp)) {
-            exp += 1;
-            exp_len -= 1;
-        }
-        if (!(neg ? neg_exp_ok(exp_len, exp) : pos_exp_ok(exp_len, exp))) {
-            return true;
-        }
-    }
-
-    /* OK. */
-    return false;
-}
-
-
-/* Given string bounds, count the number of zeros at the end. */
-static uint16_t
-number_trailing_zeros(register const char *start, register const char *end)
-{
-    register uint16_t n = 0;
-    for (end = end - 1; end >= start; --end) {
-        if (*end == '0') {
-            n += 1;
-        }
-        else {
-            break;
-        }
-    }
-    return n;
-}
-
-
-/* Return the base of an integer. -1 if invalid. */
-int
-detect_base(register const char *str, register const char *end)
-{
-    register const Py_ssize_t len = end - str;
-    if (str[0] != '0' || len == 1) {
-        return 10;
-    }
-    else if (str[1] == 'x' || str[1] == 'X') {
-        return 16;
-    }
-    else if (str[1] == 'o' || str[1] == 'O') {
-        return 8;
-    }
-    else if (str[1] == 'b' || str[1] == 'B') {
-        return 2;
-    }
-    else
-        /* "old" (C-style) octal literal illegal in 3.x. */
-        if (number_trailing_zeros(str, end) == len) {
-            return 10;
-        }
-        else {
-            return -1;
-        }
-}
-
-
-bool
-is_valid_digit_arbitrary_base(const char c, const int base)
-{
-    if (base < 10) {
-        return c >= '0' && c <= ((int) '0' + base);
-    }
-    else {
-        register const char offset = (char) base - 10;
-        return (c >= '0' && c <= '9') ||
-               (c >= 'a' && c <= 'a' + offset) ||
-               (c >= 'A' && c <= 'A' + offset);
-    }
-}
-
-
-/* Calculate the exponential scaling factor with hard-coded values. */
-long double
-power_of_ten_scaling_factor(const int expon) {
-    switch (expon) {
-    case 0:
-        return 1E0L;
-    case 1:
-        return 1E1L;
-    case 2:
-        return 1E2L;
-    case 3:
-        return 1E3L;
-    case 4:
-        return 1E4L;
-    case 5:
-        return 1E5L;
-    case 6:
-        return 1E6L;
-    case 7:
-        return 1E7L;
-    case 8:
-        return 1E8L;
-    case 9:
-        return 1E9L;
-    case 10:
-        return 1E10L;
-    case 11:
-        return 1E11L;
-    case 12:
-        return 1E12L;
-    case 13:
-        return 1E13L;
-    case 14:
-        return 1E14L;
-    case 15:
-        return 1E15L;
-    case 16:
-        return 1E16L;
-    case 17:
-        return 1E17L;
-    case 18:
-        return 1E18L;
-    case 19:
-        return 1E19L;
-    case 20:
-        return 1E20L;
-    case 21:
-        return 1E21L;
-    case 22:
-        return 1E22L;
-    case 23:
-        return 1E23L;
-    case 24:
-        return 1E24L;
-    case 25:
-        return 1E25L;
-    case 26:
-        return 1E26L;
-    case 27:
-        return 1E27L;
-    case 28:
-        return 1E28L;
-    case 29:
-        return 1E29L;
-    case 30:
-        return 1E30L;
-    case 31:
-        return 1E31L;
-    case 32:
-        return 1E32L;
-    case 33:
-        return 1E33L;
-    case 34:
-        return 1E34L;
-    case 35:
-        return 1E35L;
-    case 36:
-        return 1E36L;
-    case 37:
-        return 1E37L;
-    case 38:
-        return 1E38L;
-    case 39:
-        return 1E39L;
-    case 40:
-        return 1E40L;
-    case 41:
-        return 1E41L;
-    case 42:
-        return 1E42L;
-    case 43:
-        return 1E43L;
-    case 44:
-        return 1E44L;
-    case 45:
-        return 1E45L;
-    case 46:
-        return 1E46L;
-    case 47:
-        return 1E47L;
-    case 48:
-        return 1E48L;
-    case 49:
-        return 1E49L;
-    case 50:
-        return 1E50L;
-    case 51:
-        return 1E51L;
-    case 52:
-        return 1E52L;
-    case 53:
-        return 1E53L;
-    case 54:
-        return 1E54L;
-    case 55:
-        return 1E55L;
-    case 56:
-        return 1E56L;
-    case 57:
-        return 1E57L;
-    case 58:
-        return 1E58L;
-    case 59:
-        return 1E59L;
-    case 60:
-        return 1E60L;
-    case 61:
-        return 1E61L;
-    case 62:
-        return 1E62L;
-    case 63:
-        return 1E63L;
-    case 64:
-        return 1E64L;
-    case 65:
-        return 1E65L;
-    case 66:
-        return 1E66L;
-    case 67:
-        return 1E67L;
-    case 68:
-        return 1E68L;
-    case 69:
-        return 1E69L;
-    case 70:
-        return 1E70L;
-    case 71:
-        return 1E71L;
-    case 72:
-        return 1E72L;
-    case 73:
-        return 1E73L;
-    case 74:
-        return 1E74L;
-    case 75:
-        return 1E75L;
-    case 76:
-        return 1E76L;
-    case 77:
-        return 1E77L;
-    case 78:
-        return 1E78L;
-    case 79:
-        return 1E79L;
-    case 80:
-        return 1E80L;
-    case 81:
-        return 1E81L;
-    case 82:
-        return 1E82L;
-    case 83:
-        return 1E83L;
-    case 84:
-        return 1E84L;
-    case 85:
-        return 1E85L;
-    case 86:
-        return 1E86L;
-    case 87:
-        return 1E87L;
-    case 88:
-        return 1E88L;
-    case 89:
-        return 1E89L;
-    case 90:
-        return 1E90L;
-    case 91:
-        return 1E91L;
-    case 92:
-        return 1E92L;
-    case 93:
-        return 1E93L;
-    case 94:
-        return 1E94L;
-    case 95:
-        return 1E95L;
-    case 96:
-        return 1E96L;
-    case 97:
-        return 1E97L;
-    case 98:
-        return 1E98L;
-    case 99:
-        return 1E99L;
-    case 100:
-        return 1E100L;
-    case 101:
-        return 1E101L;
-    case 102:
-        return 1E102L;
-    case 103:
-        return 1E103L;
-    case 104:
-        return 1E104L;
-    case 105:
-        return 1E105L;
-    case 106:
-        return 1E106L;
-    case 107:
-        return 1E107L;
-    case 108:
-        return 1E108L;
-    case 109:
-        return 1E109L;
-    case 110:
-        return 1E110L;
-    /* We should never see anything larger than 99. */
-    /* This should never be reached. */
-    default:
-        return 1E308L;
-    }
-}
diff --git a/src/strings.c b/src/strings.c
deleted file mode 100644
index e25ec996..00000000
--- a/src/strings.c
+++ /dev/null
@@ -1,657 +0,0 @@
-/*
- *  * Functions that will convert/evaluate a Python string.
- *
- * Author: Seth M. Morton
- *
- * July 2018
- */
-
-#include <Python.h>
-#include <string.h>
-#include <limits.h>
-#include "fastnumbers/strings.h"
-#include "fastnumbers/unicode_character.h"
-#include "fastnumbers/numbers.h"
-#include "fastnumbers/options.h"
-#include "fastnumbers/parsing.h"
-#include "fastnumbers/pstdint.h"
-
-
-#define STRING_TYPE_CHECK(o) (PyBytes_Check(o) || \
-                              PyUnicode_Check(o) || \
-                              PyByteArray_Check(o) \
-                             )
-
-
-/* Forward declarations */
-static PyObject *
-str_to_PyInt(const char *str, const char *end, const Options *options);
-static PyObject *
-str_to_PyFloat(const char *str, const char *end,
-               const Options *options);
-
-
-static PyObject *
-str_to_PyInt_or_PyFloat(const char *str, const char *end,
-                        const Options *options)
-{
-    const char *start = str;
-    PyObject *pyresult = NULL;
-    /* If the input contains an integer, convert to int directly. */
-    consume_sign(start);
-    if (string_contains_int(start, end, 10)) {
-        return str_to_PyInt(str, end, options);
-    }
-
-    /* If not an int, assume the input is a float. */
-    pyresult = str_to_PyFloat(str, end, options);
-    if (pyresult == NULL) {
-        return NULL;
-    }
-
-    /* Coerce to int if needed. Don't do it for NAN or INF. */
-    return (Options_Coerce_True(options)
-            && string_contains_intlike_float(start, end)
-            && !PyNumber_IsNAN(pyresult)
-            && !PyNumber_IsINF(pyresult)
-           )
-           ? PyFloat_to_PyInt(pyresult, options)
-           : pyresult;
-}
-
-
-static PyObject *
-python_lib_str_to_PyFloat(const char *str, const Py_ssize_t len,
-                          const Options *options)
-{
-    const char *nend = (char *) str + len;
-    char *pend = (char *) nend;
-    double result;
-    result = PyOS_string_to_double(str, &pend, NULL);
-    /* Raise exception in the event of a memory error. */
-    if (errno == ENOMEM) {
-        return NULL;
-    }
-    if (pend == nend) {
-        return PyFloat_FromDouble(result);
-    }
-    /* Set error... The above won't because pend is non-NULL. Well, that
-     * statement isn't fully true... if not even the first digit could be
-     * converted then it would raise an error, but we are protected from
-     * that because of the is_likely_float heuristic.
-     */
-    if (Options_Should_Raise(options)) {
-        SET_ERR_INVALID_FLOAT(options);
-    }
-    return NULL;
-}
-
-
-static PyObject *
-str_to_PyFloat(const char *str, const char *end, const Options *options)
-{
-    const char *start = str;
-    const int8_t sign = consume_and_return_sign(start);
-    const Py_ssize_t len = end - start;
-    const Py_ssize_t real_len = end - str;
-
-    /* Use some simple heuristics to determine if the the string
-     * is likely a float - first and last characters must be digits.
-     * Also quick detect NaN and INFINITY.
-     */
-    if (quick_detect_infinity(start, len)) {
-        if (Options_Has_INF_Sub(options)) {
-            return Options_Return_INF_Sub(options);
-        }
-        else {
-            Py_RETURN_INF(sign);
-        }
-    }
-    else if (quick_detect_nan(start, len)) {
-        if (Options_Has_NaN_Sub(options)) {
-            return Options_Return_NaN_Sub(options);
-        }
-        else {
-            return PyFloat_from_NaN(sign < 0);
-        }
-    }
-    else if (!is_likely_float(start, len)) {
-        SET_ERR_INVALID_FLOAT(options);
-        return NULL;
-    }
-
-    /* Perform the actual parse using either the "fast" parser
-     * or Python's built-in parser. Pre-determine if the "fast"
-     * method might overflow based on the length of the string,
-     * and if it might stay on the safe side and go straight to
-     * Python's built-in version, otherwise use the "fast" version.
-     */
-    else if (float_might_overflow(start, len)) {
-        return python_lib_str_to_PyFloat(str, real_len, options);
-    }
-    else {
-        bool error = false;
-        double result = parse_float(start, end, &error, sign);
-        if (error) {
-            SET_ERR_INVALID_FLOAT(options);
-            return NULL;
-        }
-        return PyFloat_FromDouble(result);
-    }
-}
-
-
-static PyObject *
-handle_possible_conversion_error(const char *end, char *pend,
-                                 PyObject *val, const Options *options)
-{
-    /* If the expected end matches the parsed end, it was a success.
-     * This function includes trailing whitespace in "end" definition
-     * so we must do the same. */
-    consume_white_space(end);
-    /* If an error occurred, clear exception (if needed) and return NULL. */
-    if (val == NULL || pend != end) {
-        if (pend != end && Options_Should_Raise(options)) {
-            SET_ERR_INVALID_INT(options);
-        }
-        if (!Options_Should_Raise(options)) {
-            PyErr_Clear();
-        }
-        Py_XDECREF(val);  /* Probably redundant. */
-        val = NULL;  /* Probably redundant. */
-    }
-    return val;
-}
-
-
-static PyObject *
-str_to_PyInt(const char *str, const char *end, const Options *options)
-{
-    const char *start = str;
-    const long sign = consume_and_return_sign(start);
-    Py_ssize_t len = 0;
-
-    /* Use some simple heuristics to determine if the the string
-     * is likely an int - first and last characters must be digits.
-     */
-    len = end - start;
-    if (!is_likely_int(start, len)) {
-        SET_ERR_INVALID_INT(options);
-        return NULL;
-    }
-
-    /* Perform the actual parse using either the "fast" parser
-     * or Python's built-in parser. Pre-determine if the "fast"
-     * method might overflow based on the length of the string,
-     * and if it might stay on the safe side and go straight to
-     * Python's built-in version, otherwise use the "fast" version.
-     */
-    else if (int_might_overflow(start, end)) {
-        /* The way exception building for Python's built-in string->int
-         * conversion function is slow, because it allocates a unicode
-         * object just to put it into the traceback (rather than use the
-         * given input string, and there is no way to tell it to not
-         * build the traceback. For this reason we pre-validate to save
-         * time in the event of an error.
-         */
-        if (string_contains_int(start, end, 10)) {
-            char *pend = (char *)end;
-            PyObject *num = PyLong_FromString((char *) str, &pend, 10);
-            return handle_possible_conversion_error(end, pend, num, options);
-        }
-        else {
-            SET_ERR_INVALID_INT(options);
-            return NULL;
-        }
-    }
-    else {
-        bool error = false;
-        long result = parse_int(start, end, &error);
-        if (error) {
-            SET_ERR_INVALID_INT(options);
-            return NULL;
-        }
-        return PyLong_FromLong(sign * result);
-    }
-}
-
-
-static PyObject *
-str_to_PyInt_forced(const char *str, const char *end,
-                    const Options *options)
-{
-    /* Convert the input to an int or float. */
-    PyObject *pyresult = str_to_PyInt_or_PyFloat(str, end, options);
-    if (pyresult == NULL) {
-        return NULL;
-    }
-
-    /* If a float was returned, convert to an int. Otherwise return as-is. */
-    return PyFloat_Check(pyresult)
-           ? PyFloat_to_PyInt(pyresult, options)
-           : pyresult;
-}
-
-
-/* Run through the unicode and replace unicode decimals with
- * ASCII decimals, and replace weird whitespace with ASCII whitespace.
- * The returned char * WILL need to be freed at a later time.
- * If NULL is returned then a some sort of memory error occurred or
- * the data is invalid - it depends on the value of error.
- * If an error occurred, you will not need to free the result.
- */
-static char *
-PyUnicode_as_ascii_string(PyObject *obj, Py_ssize_t *len, bool *error)
-{
-    const int kind = PyUnicode_KIND(obj);  /* Unicode storage format. */
-    const void *data = PyUnicode_DATA(obj);  /* Raw data */
-    char *ascii = NULL;
-    *error = false;
-    *len = PyUnicode_GET_LENGTH(obj);
-
-    /* It is assumed PyUnicode_Check has already been called. */
-    if (PyUnicode_READY(obj)) {  /* If true, then not OK for conversion. */
-        return NULL;
-    }
-
-    /* Allocate space for the new string. */
-    if ((ascii = calloc(*len + 1, sizeof(char))) == NULL) {
-        PyErr_NoMemory();
-        errno = ENOMEM;
-        return NULL;
-    }
-    else {
-        register Py_ssize_t i;
-        register long n;
-        register Py_UCS4 c;
-        register const Py_ssize_t size = *len;
-
-        /* Convert each character. If a character is out of range then
-         * quit and set the error flag.
-         */
-        for (i = 0; i < size; i++) {
-            c = PyUnicode_READ(kind, data, i);
-            if (c < 127) {
-                ascii[i] = (char) c;
-            }
-            else if ((n = Py_UNICODE_TODECIMAL(c)) > -1) {
-                ascii[i] = '0' + (char) n;
-            }
-            else if (Py_UNICODE_ISSPACE(c)) {
-                ascii[i] = ' ';
-            }
-            else {
-                free(ascii);
-                *error = true;
-                return NULL;
-            }
-        }
-        return ascii;
-    }
-}
-
-
-/* In a generic number, valid underscores are between two digits.
- * A "based" number is necessarily an int, and in those cases the
- * definition of a valid underscore is a bit less well-defined.
- * If NULL is returned an unrecoverable memory error occurred.
- */
-static const char *
-remove_valid_underscores(char *str, const char **end, char **buffer,
-                         const Py_ssize_t len, const bool based)
-{
-    register Py_ssize_t i, offset;
-
-    /* If a buffer has not been created, do so now and copy data to it. */
-    if (*buffer == NULL) {
-        if ((*buffer = calloc(len + 1, sizeof(char))) == NULL) {
-            PyErr_NoMemory();
-            errno = ENOMEM;
-            return NULL;
-        }
-        memcpy(*buffer, str, len);
-        str = *buffer;
-    }
-
-    /* The method that will be used to remove underscores is to
-     * traverse the character array, and when a valid underscore
-     * is found all characters will be shifted one to the left in
-     * order to remove that underscore. Extra characters at the
-     * end of the character array will be overwritten with \0.
-     */
-
-    /* For non-based strings, parsing is "simple" -
-     * a valid underscore is surrounded by two numbers.
-     */
-    if (!based) {
-        for (i = offset = 0; i < len; i++) {
-            if (str[i] == '_' && i > 0 && i < len - 1 &&
-                    is_valid_digit_char(str[i - 1]) &&
-                    is_valid_digit_char(str[i + 1])) {
-                offset += 1;
-                continue;
-            }
-            if (offset) {
-                str[i - offset] = str[i];
-            }
-        }
-    }
-
-    /* For based strings we must incorporate some state at the
-     * beginning of the string before the more simple "surrounded
-     * by two numbers" algorithm kicks in.
-     */
-    else {
-        i = offset = 0;
-        if (is_sign(str)) {
-            i += 1;
-        }
-        /* Skip leading characters for non-base 10 ints. */
-        if ((len - i) > 1 && str[i] == '0' &&
-                (((str[i + 1] == 'x' || str[i + 1] == 'X')) ||
-                 ((str[i + 1] == 'o' || str[i + 1] == 'O')) ||
-                 ((str[i + 1] == 'b' || str[i + 1] == 'B')))) {
-            /* An underscore after the prefix is allowed, e.g. 0x_d4. */
-            if ((len - i > 2) && str[i + 2] == '_') {
-                i += 3;
-                offset += 1;
-            }
-            else {
-                i += 2;
-            }
-        }
-        /* No underscore in the base selector, e.g. 0_b0 is invalid. */
-        else if ((len - i) > 2 && str[i] == '0' && str[i + 1] == '_' &&
-                 (((str[i + 2] == 'x' || str[i + 2] == 'X')) ||
-                  ((str[i + 2] == 'o' || str[i + 2] == 'O')) ||
-                  ((str[i + 2] == 'b' || str[i + 2] == 'B')))) {
-            i += 3;
-        }
-
-        /* Now search for simpler valid underscores.
-         * use hex as the base because it is the most inclusive.
-         */
-        for (; i < len; i++) {
-            if (str[i] == '_' && i > 0 && i < len - 1 &&
-                    is_valid_digit_arbitrary_base(str[i - 1], 16) &&
-                    is_valid_digit_arbitrary_base(str[i + 1], 16)) {
-                offset += 1;
-                continue;
-            }
-            if (offset) {
-                str[i - offset] = str[i];
-            }
-        }
-    }
-
-    /* Update the end position. */
-    *end = str + (i - offset);
-
-    /* Fill the trailing data with nul characters. */
-    for (i = len - offset; i < len; i++) {
-        str[i] = '\0';
-    }
-    return str;
-}
-
-
-/* Try to convert the Python object to bytes (i.e. char*).
- * Possibly convert unicode to bytes object first.
- * If the string contains nul characters, return "\0".
- */
-static const char *
-convert_PyString_to_str(PyObject *input, const char **end,
-                        char **buffer, bool *must_raise, const bool based,
-                        const bool allow_underscores)
-{
-    const char *str = NULL;
-    Py_ssize_t len = 0;
-    Py_buffer view = {NULL, NULL}; /* Reference to a buffer object */
-    *buffer = NULL;
-    *must_raise = false;
-
-    /* If the input was in unicode format, extract as ASCII if we can. */
-    if (PyUnicode_Check(input)) {
-        /* Unicode in ASCII form is stored like bytes! */
-        if (PyUnicode_IS_READY(input) && PyUnicode_IS_COMPACT_ASCII(input)) {
-            str = (const char *) PyUnicode_1BYTE_DATA(input);
-            len = PyUnicode_GET_LENGTH(input);
-        }
-        else
-        {
-            bool has_error = false;
-            *buffer = PyUnicode_as_ascii_string(input, &len, &has_error);
-            if (has_error) {
-                return NULL;  /* Buffer is always NULL if here. */
-            }
-            if (*buffer == NULL) {
-                *must_raise = true;
-                return NULL;
-            }
-            str = *buffer;
-        }
-    }
-
-    /* If the input is already bytes, then just extract the
-     * underlying data and return.
-     */
-    else if (PyBytes_Check(input)) {
-        str = PyBytes_AS_STRING(input);
-        len = PyBytes_GET_SIZE(input);
-    }
-
-    /* Same for byte array. */
-    else if (PyByteArray_Check(input)) {
-        str = PyByteArray_AS_STRING(input);
-        len = PyByteArray_GET_SIZE(input);
-    }
-
-    /* For a buffer, access the raw data in the buffer directly. */
-    else if (PyObject_CheckBuffer(input) &&
-             PyObject_GetBuffer(input, &view, PyBUF_SIMPLE) == 0) {
-        /* This buffer could be a memoryview slice. If this is the case, the
-         * nul termination of the string wil be past the given length, creating
-         * unexpected parsing results. Rather than complicate the parsing and
-         * adding more operations for a low probability event, a copy of the
-         * slice will be made here and it will be implicitly nul terminated
-         * by the use of calloc.
-         */
-        if ((*buffer = calloc(view.len + 1, sizeof(char))) == NULL) {
-            PyErr_NoMemory();
-            errno = ENOMEM;
-            *must_raise = true;
-            return NULL;
-        }
-        memcpy(*buffer, (char *) view.buf, view.len);
-        str = *buffer;
-        len = view.len;
-        /* All we care about is the underlying buffer data, not the obj
-         * which was allocated when we created the buffer. For this reason
-         * it is safe to release the buffer here.
-         */
-        PyBuffer_Release(&view);
-    }
-
-    /* Return NULL if data type was invalid. */
-    if (str == NULL) {
-        free(*buffer);  /* Just in case */
-        return NULL;
-    }
-
-    /* Pre-process the input, first by stripping whitespace. */
-    strip_whitespace(str, *end, len);
-    len = *end - str;
-
-    /* Remove all "valid" underscores from the
-     * string to simplify downstream parsing.
-     */
-    if (allow_underscores && len > 0 && memchr(str, '_', len)) {
-        if ((str = remove_valid_underscores((char *)str,
-                                            end, buffer,
-                                            len, based)) == NULL) {
-            *must_raise = true;
-            free(*buffer);
-            return NULL;
-        }
-    }
-
-    return str;
-}
-
-
-/* Convert numbers in strings. */
-PyObject *
-PyString_to_PyNumber(PyObject *obj, const PyNumberType type,
-                     const Options *options)
-{
-    const char *end;
-    PyObject *pyresult = Py_None;  /* None indicates TypeError, not ValueError. */
-    bool needs_raise = false;
-    char *buf = NULL;
-    const char *str = convert_PyString_to_str(obj, &end, &buf, &needs_raise,
-                                              !Options_Default_Base(options),
-                                              Options_Allow_Underscores(options));
-    if (needs_raise) {
-        /* Never need to free buffer if needs_raise is true. */
-        return NULL;
-    }
-
-    /* If we could extract the string, convert it! */
-    if (str != NULL) {
-        switch (type) {
-        case REAL:
-            pyresult = str_to_PyInt_or_PyFloat(str, end, options);
-            break;
-        case FLOAT:
-            pyresult = str_to_PyFloat(str, end, options);
-            break;
-        case INT:
-            /* To maintain compatibility with Python,
-             * explicit base MUST be a string-like type.
-             */
-            if (!Options_Default_Base(options) && !STRING_TYPE_CHECK(obj))
-            {
-                SET_ILLEGAL_BASE_ERROR(options);
-                pyresult = NULL;
-            }
-            else if (Options_Default_Base(options) || options->base == 10) {
-                pyresult = str_to_PyInt(str, end, options);
-            }
-            else {
-                char *pend = "\0";
-                pyresult = PyLong_FromString((char *) str, &pend, options->base);
-                pyresult = handle_possible_conversion_error(end, pend,
-                                                            pyresult,
-                                                            options);
-            }
-            break;
-        case FORCEINT:
-        case INTLIKE:
-            pyresult = str_to_PyInt_forced(str, end, options);
-            break;
-        }
-    }
-
-    free(buf);
-    return pyresult;
-}
-
-
-/* Detect numbers in strings. */
-PyObject *
-PyString_is_number(PyObject *obj, const PyNumberType type,
-                   const Options *options)
-{
-    const char *end;
-    const int base = Options_Default_Base(options) ? 10 : options->base;
-    bool result = false, needs_raise = false;
-    char *buf = NULL;
-    const char *str = convert_PyString_to_str(obj, &end, &buf, &needs_raise,
-                                              !Options_Default_Base(options),
-                                              Options_Allow_Underscores(options));
-    if (needs_raise) {
-        /* Never need to free buffer if needs_raise is true. */
-        return NULL;
-    }
-
-    if (str != NULL) {
-        consume_sign(str);  /* All functions require sign is stripped. */
-        switch (type) {
-        case REAL:
-        case FLOAT:
-            result = string_contains_float(str, end,
-                                           Options_Allow_Infinity(options),
-                                           Options_Allow_NAN(options));
-            break;
-        case INT:
-            result = string_contains_int(str, end, base);
-            break;
-        case FORCEINT:
-        case INTLIKE:
-            result = string_contains_intlike_float(str, end);
-            break;
-        }
-    }
-    else {
-        /* Not a string. Never need to free if str was NULL. */
-        return Py_None;
-    }
-
-    free(buf);
-    if (result) {
-        Py_RETURN_TRUE;
-    }
-    else {
-        Py_RETURN_FALSE;
-    }
-}
-
-
-/* Return the type contained the string. */
-PyObject *
-PyString_contains_type(PyObject *obj, const Options *options)
-{
-    const char *end;
-    const int base = Options_Default_Base(options) ? 10 : options->base;
-    PyObject *result = NULL;
-    bool needs_raise = false;
-    char *buf = NULL;
-    const char *str = convert_PyString_to_str(obj, &end, &buf, &needs_raise,
-                                              !Options_Default_Base(options),
-                                              Options_Allow_Underscores(options));
-    if (needs_raise) {
-        /* Never need to free buffer if needs_raise is true. */
-        return NULL;
-    }
-
-    if (str != NULL) {
-        consume_sign(str);  /* All functions require sign is stripped. */
-        if (string_contains_int(str, end, base)) {
-            result = (PyObject *) &PyLong_Type;
-            Py_INCREF(result);
-        }
-        else if (Options_Coerce_True(options) &&
-                 string_contains_intlike_float(str, end))
-        {
-            result = (PyObject *) &PyLong_Type;
-            Py_INCREF(result);
-        }
-        else if (string_contains_float(str, end,
-                                       Options_Allow_Infinity(options),
-                                       Options_Allow_NAN(options)))
-        {
-            result = (PyObject *) &PyFloat_Type;
-            Py_INCREF(result);
-        }
-        else {
-            result = PyObject_Type(obj);
-        }
-    }
-    else {
-        /* Not a string. Never need to free if str was NULL. */
-        return Py_None;
-    }
-
-    free(buf);
-    return result;
-}
\ No newline at end of file
diff --git a/src/unicode_character.c b/src/unicode_character.c
deleted file mode 100644
index 1f9aa9d0..00000000
--- a/src/unicode_character.c
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Functions to convert or evaluate a (possibly signed) single
- * unicode character representing a non-decimal numerical value.
- *
- * Author: Seth M. Morton
- *
- * July 2018
- */
-
-#include <Python.h>
-#include "fastnumbers/unicode_character.h"
-#include "fastnumbers/numbers.h"
-#include "fastnumbers/options.h"
-
-/* Obtain a PyLong or PyFloat from this unicode string assuming
- * it is a single unicode non-decimal numeric character.
- * Return NULL if not possible. Return Py_None not PyUnicode.
- */
-PyObject *
-convert_PyUnicode_to_PyNumber(PyObject *input)
-{
-    const int kind = PyUnicode_KIND(input);  /* Unicode storage format. */
-    const void *data = PyUnicode_DATA(input);  /* Raw data */
-    Py_ssize_t len = PyUnicode_GET_LENGTH(input);
-    Py_ssize_t index = 0;
-    bool negative = false;
-
-    /* Ensure input is a valid unicode object. */
-    if (!PyUnicode_Check(input)) {
-        return Py_None;
-    }
-    if (PyUnicode_READY(input)) {  /* If true, then not OK for conversion. */
-        return Py_None;
-    }
-
-    /* Strip whitespace from both ends of the data. */
-    while (Py_UNICODE_ISSPACE(PyUnicode_READ(kind, data, index))) {
-        index += 1;
-        len -= 1;
-    }
-    while (Py_UNICODE_ISSPACE(PyUnicode_READ(kind, data, index + len - 1))) {
-        len -= 1;
-    }
-
-    /* Remove the sign - remember if it is negative. */
-    if (PyUnicode_READ(kind, data, index) == '-') {
-        negative = true;
-        index += 1;
-        len -= 1;
-    }
-    else if (PyUnicode_READ(kind, data, index) == '+') {
-        index += 1;
-        len -= 1;
-    }
-
-    /* Anything longer than a length of 1 is not valid. */
-    if (len == 1) {
-        const Py_UCS4 u = (Py_UCS4) PyUnicode_READ(kind, data, index);
-        const double number = Py_UNICODE_TONUMERIC(u);
-        const long digit = Py_UNICODE_TODIGIT(u);
-        if (number > -1.0) {
-            if (digit > -1) {
-                return PyLong_FromLong(negative ? -digit : digit);
-            }
-            else {
-                return PyFloat_FromDouble(negative ? -number : number);
-            }
-        }
-    }
-    return NULL;
-}
-
-/* Return a number of the desired type from a (possibly signed)
- * single unicode character representing a non-decimal value.
- */
-PyObject *
-PyUnicodeCharacter_to_PyNumber(PyObject *obj, const PyNumberType type,
-                               const Options *options)
-{
-    PyObject *number = convert_PyUnicode_to_PyNumber(obj);
-    PyObject *result = NULL;
-
-    if (number == NULL) {
-        if (type == REAL || type == FLOAT) {
-            SET_ERR_INVALID_FLOAT(options);
-        }
-        else {
-            SET_ERR_INVALID_INT(options);
-        }
-        return NULL;
-    }
-    else if (number != Py_None) {
-        switch (type) {
-        case REAL:
-            if (PyLong_Check(number)) {
-                result = number;
-                Py_INCREF(result);
-                break;
-            }
-            else if (PyFloat_is_Intlike(number)) {
-                result = PyNumber_Long(number);
-                break;
-            }
-        /* Deliberate fall-through to FLOAT. */
-
-        case FLOAT:
-            result = PyNumber_Float(number);
-            break;
-
-        case INT:
-            if (PyLong_Check(number)) {
-                result = number;
-                Py_INCREF(result);
-            }
-            else {
-                SET_ERR_INVALID_INT(options);
-            }
-            break;
-
-        case FORCEINT:
-        case INTLIKE:
-        default:
-            if (PyLong_Check(number)) {
-                result = number;
-                Py_INCREF(result);
-                break;
-            }
-            else {
-                result = PyNumber_Long(number);
-                if (result == NULL) {
-                    PyErr_Clear();
-                    SET_ERR_INVALID_INT(options);
-                }
-            }
-        }
-
-        Py_DECREF(number);
-        return result;
-    }
-    else {
-        return Py_None;  /* Indicates TypeError, not ValueError. */
-    }
-}
-
-/* Evaluate if this Unicode object contains a (possibly signed)
- * single unicode character representing a non-decimal value.
- */
-PyObject *
-PyUnicodeCharacter_is_number(PyObject *obj, const PyNumberType type)
-{
-    PyObject *number = convert_PyUnicode_to_PyNumber(obj);
-    bool isok = false;
-
-    if (number == NULL) {
-        Py_RETURN_FALSE;
-    }
-    else if (number != Py_None) {
-        switch (type) {
-        case REAL:
-        case FLOAT:
-            isok = PyLong_Check(number) || PyFloat_Check(number);
-            break;
-
-        case INT:
-            isok = PyLong_Check(number);
-            break;
-
-        case FORCEINT:
-        case INTLIKE:
-        default:
-            isok = PyLong_Check(number) || PyFloat_is_Intlike(number);
-            break;
-        }
-        Py_DECREF(number);
-        if (isok) {
-            Py_RETURN_TRUE;
-        }
-        else {
-            Py_RETURN_FALSE;
-        }
-    }
-    else {
-        return Py_None;  /* Not unicode. */
-    }
-}
-
-
-PyObject *
-PyUnicodeCharacter_contains_type(PyObject *obj)
-{
-    PyObject *number = convert_PyUnicode_to_PyNumber(obj);
-    PyObject *retval = NULL;
-
-    if (number == NULL) {
-        return PyObject_Type(obj);
-    }
-    else if (number != Py_None) {
-        retval = PyObject_Type(number);
-        Py_DECREF(number);
-        return retval;
-    }
-    else {
-        return Py_None;  /* Not unicode. */
-    }
-}
diff --git a/tox.ini b/tox.ini
index 87814ac5..d71bc3d4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,14 +4,14 @@
 # and then run "tox" from this directory.
 
 [tox]
-envlist = py36, py37, py38, py39, py310
+envlist = py37, py38, py39, py310
 # Other valid environments are:
 #   format
 #   format-check
 #   docs
 #   bump
 #   clean
-#   py{36,37,38}-nb  (to update the Jupyter Notebooks}
+#   py{37,38}-nb  (to update the Jupyter Notebooks}
 
 # Don't error out if a user hasn't installed all python versions.
 skip_missing_interpreters =
@@ -23,6 +23,7 @@ deps =
     pytest
     pytest-faulthandler
     hypothesis
+    cython
     typing_extensions
 commands =
     # All versions need to build and patch doctest for testing the fastnumbers module.
@@ -38,18 +39,18 @@ commands =
 # Check that code is formatted
 [testenv:format-check]
 skip_install = true
-deps = black
-commands = {envpython} dev/check-formatting.py
+deps =
+    black
+    clang-format
+commands = {envpython} dev/formatting.py --check
 
 # Format code
 [testenv:format]
 skip_install = true
-deps = black
-whitelist_externals = astyle
-commands =
-    black .
-    astyle --recursive --options=dev/astyle.cfg --suffix=none src/*.c
-    astyle --recursive --options=dev/astyle.cfg --suffix=none --exclude=fn_bool.h --exclude=pstdint.h --exclude=docstrings.h include/fastnumbers/*.h
+deps =
+    black
+    clang-format
+commands = {envpython} dev/formatting.py
 
 # Type checking
 [testenv:mypy]
@@ -57,7 +58,6 @@ deps =
     mypy
     hypothesis
     pytest
-    typing_extensions
 commands =
     mypy --strict tests
 
@@ -87,11 +87,6 @@ deps =
 commands = {envpython} dev/clean.py
 
 # For running jupyter notebooks.
-[testenv:py36-nb]
-skip_install = false
-deps = jupyter
-commands = jupyter notebook TIMING_36.ipynb
-
 [testenv:py37-nb]
 skip_install = false
 deps = jupyter


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 234cafaf..71038442 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -15,7 +15,7 @@ jobs:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        python-version: [3.6, 3.7, 3.8, 3.9, "3.10"]
+        python-version: [3.7, 3.8, 3.9, "3.10"]
         # Note, clang is used on macos, even though it says gcc
         os: [ubuntu-latest, windows-latest, macos-latest]
 
@@ -43,7 +43,7 @@ jobs:
 
       - name: Run Tests
         run: |
-          python setup.py install
+          python -m pip install --editable .
           python -m doctest fastnumbers
           pytest --hypothesis-profile=slow-tests --doctest-glob=README.rst
 
@@ -55,7 +55,7 @@ jobs:
     name: Tests on aarch64
     strategy:
       matrix:
-        pyver: [cp36-cp36m, cp37-cp37m, cp38-cp38, cp39-cp39, cp310-cp310]
+        pyver: [cp37-cp37m, cp38-cp38, cp39-cp39, cp310-cp310]
       fail-fast: false
     runs-on: ubuntu-latest
     env:
@@ -78,7 +78,7 @@ jobs:
           source .env/bin/activate && \
           echo -e "\e[1;34m Install Dependencies \e[0m" && \
           python -m pip install --upgrade pip && \
-          python -m pip install pytest pytest-faulthandler hypothesis typing_extensions && \
+          python -m pip install pytest pytest-faulthandler hypothesis typing_extensions cython && \
           echo -e "\e[1;34m Patch Doctests \e[0m" && \
           python dev/patch_doctest.py && \
           echo -e "\e[1;34m Set CFLAGS \e[0m" && \
diff --git a/tests/test_builtin_float.py b/tests/test_builtin_float.py
index 38b61088..729b7625 100644
--- a/tests/test_builtin_float.py
+++ b/tests/test_builtin_float.py
@@ -68,9 +68,6 @@ def test_float(self) -> None:
     def test_noargs(self) -> None:
         self.assertEqual(float(), 0.0)
 
-    @unittest.skipUnless(
-        sys.version_info >= (3, 6), "Underscores introduced in Python 3.6"
-    )
     def test_underscores(self) -> None:
         for lit in VALID_UNDERSCORE_LITERALS:
             if not any(ch in lit for ch in "jJxXoObB"):
@@ -133,7 +130,6 @@ def test_float_memoryview(self) -> None:
         self.assertEqual(float(memoryview(b"12.3A")[1:4]), 2.3)
         self.assertEqual(float(memoryview(b"12.34")[1:4]), 2.3)
 
-    @unittest.skipIf(sys.version_info >= (3, 7), "Messages changed in Python 3.7")
     def test_error_message_old(self) -> None:
         testlist = ("\xbd", "123\xbd", "  123 456  ")
         for s in testlist:
@@ -144,7 +140,6 @@ def test_error_message_old(self) -> None:
             else:
                 self.fail("Expected int(%r) to raise a ValueError" % s)
 
-    @unittest.skipUnless(sys.version_info >= (3, 7), "Messages changed in Python 3.7")
     def test_error_message(self) -> None:
         def check(s: Union[str, bytes]) -> None:
             with self.assertRaises(ValueError, msg="float(%r)" % (s,)) as cm:
@@ -261,7 +256,7 @@ def __index__(self) -> int:
                     return self.value
 
             self.assertEqual(float(MyIndex(42)), 42.0)
-            self.assertRaises(OverflowError, float, MyIndex(2 ** 2000))
+            self.assertRaises(OverflowError, float, MyIndex(2**2000))
 
             class MyInt:
                 def __int__(self) -> int:
@@ -269,9 +264,6 @@ def __int__(self) -> int:
 
             self.assertRaises(TypeError, float, MyInt())
 
-    @unittest.skipUnless(
-        sys.version_info >= (3, 7), "Keyword arguments disallowed as of Python 3.7"
-    )
     def test_keyword_args(self) -> None:
         with self.assertRaisesRegex(TypeError, "keyword argument"):
             float(x="3.14")
diff --git a/tests/test_builtin_int.py b/tests/test_builtin_int.py
index 18ce3263..3807e570 100644
--- a/tests/test_builtin_int.py
+++ b/tests/test_builtin_int.py
@@ -273,9 +273,9 @@ def test_int_base_limits(self) -> None:
         with self.assertRaises(ValueError):
             int("0", -909)  # An old magic value base from Python 2.
         with self.assertRaises(ValueError):
-            int("0", base=0 - (2 ** 234))
+            int("0", base=0 - (2**234))
         with self.assertRaises(ValueError):
-            int("0", base=2 ** 234)
+            int("0", base=2**234)
         # Bases 2 through 36 are supported.
         for base in range(2, 37):
             self.assertEqual(int("0", base=base), 0)
@@ -296,14 +296,14 @@ def __index__(self) -> builtins.int:
                 return self.value
 
         # Check out of range bases.
-        for base in 2 ** 100, -(2 ** 100), 1, 37:
+        for base in 2**100, -(2**100), 1, 37:
             with self.assertRaises(ValueError):
                 int("43", base)
 
         # Check in-range bases.
         self.assertEqual(int("101", base=MyIndexable(2)), 5)
         self.assertEqual(int("101", base=MyIndexable(10)), 101)
-        self.assertEqual(int("101", base=MyIndexable(36)), 1 + 36 ** 2)
+        self.assertEqual(int("101", base=MyIndexable(36)), 1 + 36**2)
 
     def test_non_numeric_input_types(self) -> None:
         # Test possible non-numeric types for the argument x, including
@@ -412,7 +412,7 @@ class BadInt(builtins.int):
             def __int__(self) -> builtins.float:  # type: ignore
                 return 42.0
 
-        my_int = MyInt(7)
+        my_int: Union[MyInt, BadInt] = MyInt(7)
         self.assertEqual(my_int, 7)
         self.assertEqual(int(my_int), 42)
 
@@ -440,6 +440,7 @@ class BadInt2(builtins.int):
             def __int__(self) -> bool:
                 return True
 
+        bad_int: Union[BadInt, BadIndex, BadIndex2]
         if sys.version_info >= (3, 8):
             bad_int = BadIndex()
             with self.assertWarns(DeprecationWarning):
@@ -498,7 +499,6 @@ def check(s: Union[str, bytes], base: Optional[builtins.int] = None) -> None:
         check("123\ud800")
         check("123\ud800", 10)
 
-    @unittest.skipUnless(sys.version_info >= (3, 7), "Test introduced in Python 3.7")
     def test_issue31619(self) -> None:
         self.assertEqual(
             int("1_0_1_0_1_0_1_0_1_0_1_0_1_0_1_0_1_0_1_0_1_0_1_0_1_0_1_0_1_0_1", 2),
diff --git a/tests/test_fastnumbers.py b/tests/test_fastnumbers.py
index 8cf93b2c..53f63656 100644
--- a/tests/test_fastnumbers.py
+++ b/tests/test_fastnumbers.py
@@ -3,7 +3,6 @@
 import math
 import random
 import re
-import sys
 import unicodedata
 from functools import partial
 from typing import (
@@ -34,7 +33,6 @@
 
 import fastnumbers
 
-skipif = mark.skipif
 parametrize = mark.parametrize
 
 
@@ -1060,9 +1058,6 @@ def test_returns_true_if_given_int_string_with_non_base_10(self, x: int) -> None
         # Force unicode path
         assert fastnumbers.isint(hex(x).replace("0", "\uFF10"), base=0)
 
-    @skipif(
-        sys.version_info < (3, 6), reason="Underscore handling introduced in Python 3.6"
-    )
     def test_underscores(self) -> None:
         assert fastnumbers.isint("0_0_0")
         assert fastnumbers.isint("0_0_0", base=0)
diff --git a/tests/test_fastnumbers_examples.py b/tests/test_fastnumbers_examples.py
index b6c54f9b..09b79e9a 100644
--- a/tests/test_fastnumbers_examples.py
+++ b/tests/test_fastnumbers_examples.py
@@ -84,6 +84,7 @@ def test_fast_real() -> None:
     assert fastnumbers.fast_real("+") == "+"
     assert fastnumbers.fast_real("-") == "-"
     assert fastnumbers.fast_real("e") == "e"
+    assert fastnumbers.fast_real("e8") == "e8"
     assert fastnumbers.fast_real(".") == "."
     # 17. Default on invalid... 'raise_on_invalid' supersedes
     assert fastnumbers.fast_real("invalid", 90) == 90
@@ -92,10 +93,10 @@ def test_fast_real() -> None:
     with raises(ValueError):
         assert fastnumbers.fast_real("invalid", 90, raise_on_invalid=True)
     # 18. Unicode numbers
-    assert fastnumbers.fast_real(u"⑦") == 7
-    assert fastnumbers.fast_real(u"⁸") == 8
-    assert fastnumbers.fast_real(u"⅔") == 2.0 / 3.0
-    assert fastnumbers.fast_real(u"Ⅴ") == 5
+    assert fastnumbers.fast_real("⑦") == 7
+    assert fastnumbers.fast_real("⁸") == 8
+    assert fastnumbers.fast_real("⅔") == 2.0 / 3.0
+    assert fastnumbers.fast_real("Ⅴ") == 5
     # 19. Function to execute on failure to convert
     assert fastnumbers.fast_real("76.8", on_fail=len) == 76.8
     assert fastnumbers.fast_real("invalid", on_fail=len) == 7
@@ -151,6 +152,7 @@ def test_fast_float() -> None:
     assert fastnumbers.fast_float("+") == "+"
     assert fastnumbers.fast_float("-") == "-"
     assert fastnumbers.fast_float("e") == "e"
+    assert fastnumbers.fast_float("e8") == "e8"
     assert fastnumbers.fast_float(".") == "."
     # 17. Default on invalid... 'raise_on_invalid' supersedes
     assert fastnumbers.fast_float("invalid", 90) == 90
@@ -159,10 +161,10 @@ def test_fast_float() -> None:
     with raises(ValueError):
         assert fastnumbers.fast_float("invalid", 90, raise_on_invalid=True)
     # 18. Unicode numbers
-    assert fastnumbers.fast_float(u"⑦") == 7.0
-    assert fastnumbers.fast_float(u"⁸") == 8.0
-    assert fastnumbers.fast_float(u"⅔") == 2.0 / 3.0
-    assert fastnumbers.fast_float(u"Ⅴ") == 5.0
+    assert fastnumbers.fast_float("⑦") == 7.0
+    assert fastnumbers.fast_float("⁸") == 8.0
+    assert fastnumbers.fast_float("⅔") == 2.0 / 3.0
+    assert fastnumbers.fast_float("Ⅴ") == 5.0
     # 19. Function to execute on failure to convert
     assert fastnumbers.fast_float("76.8", on_fail=len) == 76.8
     assert fastnumbers.fast_float("invalid", on_fail=len) == 7
@@ -211,6 +213,7 @@ def test_fast_int() -> None:
     assert fastnumbers.fast_int("+") == "+"
     assert fastnumbers.fast_int("-") == "-"
     assert fastnumbers.fast_int("e") == "e"
+    assert fastnumbers.fast_int("e8") == "e8"
     assert fastnumbers.fast_int(".") == "."
     # 17. Default on invalid... 'raise_on_invalid' supersedes
     assert fastnumbers.fast_int("invalid", 90) == 90
@@ -219,11 +222,11 @@ def test_fast_int() -> None:
     with raises(ValueError):
         assert fastnumbers.fast_int("invalid", 90, raise_on_invalid=True)
     # 18. Unicode numbers
-    assert fastnumbers.fast_int(u"⑦") == 7
-    assert fastnumbers.fast_int(u"⁸") == 8
-    assert fastnumbers.fast_int(u"⁸", base=10) == u"⁸"
-    assert fastnumbers.fast_int(u"⅔") == u"⅔"
-    assert fastnumbers.fast_int(u"Ⅴ") == u"Ⅴ"
+    assert fastnumbers.fast_int("⑦") == 7
+    assert fastnumbers.fast_int("⁸") == 8
+    assert fastnumbers.fast_int("⁸", base=10) == "⁸"
+    assert fastnumbers.fast_int("⅔") == "⅔"
+    assert fastnumbers.fast_int("Ⅴ") == "Ⅴ"
     # 19. Function to execute on failure to convert
     assert fastnumbers.fast_int("76", on_fail=len) == 76
     assert fastnumbers.fast_int("invalid", on_fail=len) == 7
@@ -273,6 +276,7 @@ def test_fast_forceint() -> None:
     assert fastnumbers.fast_forceint("+") == "+"
     assert fastnumbers.fast_forceint("-") == "-"
     assert fastnumbers.fast_forceint("e") == "e"
+    assert fastnumbers.fast_forceint("e8") == "e8"
     assert fastnumbers.fast_forceint(".") == "."
     # 17. Default on invalid... 'raise_on_invalid' supersedes
     assert fastnumbers.fast_forceint("invalid", 90) == 90
@@ -281,10 +285,10 @@ def test_fast_forceint() -> None:
     with raises(ValueError):
         assert fastnumbers.fast_forceint("invalid", 90, raise_on_invalid=True)
     # 18. Unicode numbers
-    assert fastnumbers.fast_forceint(u"⑦") == 7
-    assert fastnumbers.fast_forceint(u"⁸") == 8
-    assert fastnumbers.fast_forceint(u"⅔") == 0
-    assert fastnumbers.fast_forceint(u"Ⅴ") == 5
+    assert fastnumbers.fast_forceint("⑦") == 7
+    assert fastnumbers.fast_forceint("⁸") == 8
+    assert fastnumbers.fast_forceint("⅔") == 0
+    assert fastnumbers.fast_forceint("Ⅴ") == 5
     # 19. Function to execute on failure to convert
     assert fastnumbers.fast_forceint("76.8", on_fail=len) == 76
     assert fastnumbers.fast_forceint("invalid", on_fail=len) == 7
@@ -337,12 +341,13 @@ def test_isreal() -> None:
     assert not fastnumbers.isreal("+")
     assert not fastnumbers.isreal("-")
     assert not fastnumbers.isreal("e")
+    assert not fastnumbers.isreal("e8")
     assert not fastnumbers.isreal(".")
     # 18. Unicode numbers
-    assert fastnumbers.isreal(u"⑦")
-    assert fastnumbers.isreal(u"⁸")
-    assert fastnumbers.isreal(u"⅔")
-    assert fastnumbers.isreal(u"Ⅴ")
+    assert fastnumbers.isreal("⑦")
+    assert fastnumbers.isreal("⁸")
+    assert fastnumbers.isreal("⅔")
+    assert fastnumbers.isreal("Ⅴ")
 
 
 def test_isfloat() -> None:
@@ -393,12 +398,13 @@ def test_isfloat() -> None:
     assert not fastnumbers.isfloat("+")
     assert not fastnumbers.isfloat("-")
     assert not fastnumbers.isfloat("e")
+    assert not fastnumbers.isfloat("e8")
     assert not fastnumbers.isfloat(".")
     # 18. Unicode numbers
-    assert fastnumbers.isfloat(u"⑦")
-    assert fastnumbers.isfloat(u"⁸")
-    assert fastnumbers.isfloat(u"⅔")
-    assert fastnumbers.isfloat(u"Ⅴ")
+    assert fastnumbers.isfloat("⑦")
+    assert fastnumbers.isfloat("⁸")
+    assert fastnumbers.isfloat("⅔")
+    assert fastnumbers.isfloat("Ⅴ")
 
 
 def test_isint() -> None:
@@ -445,12 +451,13 @@ def test_isint() -> None:
     assert not fastnumbers.isint("+")
     assert not fastnumbers.isint("-")
     assert not fastnumbers.isint("e")
+    assert not fastnumbers.isint("e8")
     assert not fastnumbers.isint(".")
     # 18. Unicode numbers
-    assert fastnumbers.isint(u"⑦")
-    assert fastnumbers.isint(u"⁸")
-    assert not fastnumbers.isint(u"⅔")
-    assert not fastnumbers.isint(u"Ⅴ")
+    assert fastnumbers.isint("⑦")
+    assert fastnumbers.isint("⁸")
+    assert not fastnumbers.isint("⅔")
+    assert not fastnumbers.isint("Ⅴ")
 
 
 def test_isintlike() -> None:
@@ -505,12 +512,13 @@ def test_isintlike() -> None:
     assert not fastnumbers.isintlike("+")
     assert not fastnumbers.isintlike("-")
     assert not fastnumbers.isintlike("e")
+    assert not fastnumbers.isintlike("e8")
     assert not fastnumbers.isintlike(".")
     # 18. Unicode numbers
-    assert fastnumbers.isintlike(u"⑦")
-    assert fastnumbers.isintlike(u"⁸")
-    assert not fastnumbers.isintlike(u"⅔")
-    assert fastnumbers.isintlike(u"Ⅴ")
+    assert fastnumbers.isintlike("⑦")
+    assert fastnumbers.isintlike("⁸")
+    assert not fastnumbers.isintlike("⅔")
+    assert fastnumbers.isintlike("Ⅴ")
 
 
 def test_type() -> None:
@@ -557,12 +565,13 @@ def test_type() -> None:
     assert fastnumbers.query_type("+") is str
     assert fastnumbers.query_type("-") is str
     assert fastnumbers.query_type("e") is str
+    assert fastnumbers.query_type("e8") is str
     assert fastnumbers.query_type(".") is str
     # 18. Unicode numbers
-    assert fastnumbers.query_type(u"⑦") is int
-    assert fastnumbers.query_type(u"⁸") is int
-    assert fastnumbers.query_type(u"⅔") is float
-    assert fastnumbers.query_type(u"Ⅴ") is float
+    assert fastnumbers.query_type("⑦") is int
+    assert fastnumbers.query_type("⁸") is int
+    assert fastnumbers.query_type("⅔") is float
+    assert fastnumbers.query_type("Ⅴ") is float
 
 
 @pytest.fixture()