Snap for 6079754 from 289bd9700073441c2086388c1dc85aa3daea4e08 to simpleperf-release

Change-Id: I12f96b7fd0c297afc2e4d15b71d3eaa1bcb176ad
diff --git a/.travis.yml b/.travis.yml
index 27a83c1..ff9d167 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -92,45 +92,6 @@
       # g++ 4.8 on Linux with C++11
     - env: COMPILER=g++-4.8 BUILD=Debug STANDARD=11
       compiler: gcc
-    - name: Android NDK (Gradle)
-      language: android
-      addons:
-        apt:
-          update: true
-          sources:
-            - ubuntu-toolchain-r-test
-          packages:
-            - ninja-build
-            - curl
-            - tree
-      android:
-        components:
-          - tools
-          - platform-tools
-          - android-25 # 7.0
-          - android-27 # 8.1
-          - android-28 # 9.0
-          - build-tools-28.0.3
-      before_install:
-        # Install Gradle from https://sdkman.io/
-        - curl -s "https://get.sdkman.io" | bash > /dev/null
-        - source "$HOME/.sdkman/bin/sdkman-init.sh"
-        - sdk version
-        - sdk install gradle
-        - sdk use gradle
-        - gradle --version
-      install:
-        # Accept SDK Licenses + Install NDK
-        - yes | sdkmanager --update > /dev/null 2>&1
-        - sdkmanager ndk-bundle > /dev/null 2>&1
-      before_script:
-        - pushd ./support
-      script:
-        - gradle clean
-        - gradle assemble
-      after_success:
-        - popd;
-        - tree ./libs
 
 before_script:
   - if [[ "${TRAVIS_OS_NAME}" == "linux" ]]; then export CXX=${COMPILER}; fi
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6e08aa5..0a17358 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -78,7 +78,7 @@
       -Wcast-align -Wnon-virtual-dtor
       -Wctor-dtor-privacy -Wdisabled-optimization
       -Winvalid-pch -Woverloaded-virtual
-      -Wconversion
+      -Wconversion -Wswitch-enum
       -Wno-ctor-dtor-privacy -Wno-format-nonliteral -Wno-shadow)
   if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
       set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnoexcept
@@ -98,7 +98,7 @@
 
 if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
   set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion
-      -Wno-sign-conversion)
+      -Wno-sign-conversion -Wdeprecated -Wweak-vtables)
   check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING)
   if (HAS_NULLPTR_WARNING)
     set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
@@ -135,10 +135,8 @@
 
 include(CheckSymbolExists)
 if (WIN32)
-  check_symbol_exists(open io.h HAVE_OPEN)
   check_symbol_exists(_strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
 else ()
-  check_symbol_exists(open fcntl.h HAVE_OPEN)
   check_symbol_exists(strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
 endif ()
 
@@ -152,13 +150,8 @@
 
 # Define the fmt library, its includes and the needed defines.
 add_headers(FMT_HEADERS chrono.h color.h compile.h core.h format.h format-inl.h
-                        locale.h ostream.h printf.h ranges.h
-                        safe-duration-cast.h)
-set(FMT_SOURCES src/format.cc)
-if (HAVE_OPEN)
-  add_headers(FMT_HEADERS posix.h)
-  set(FMT_SOURCES ${FMT_SOURCES} src/posix.cc)
-endif ()
+                        locale.h ostream.h posix.h printf.h ranges.h)
+set(FMT_SOURCES src/format.cc src/posix.cc)
 
 add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
 add_library(fmt::fmt ALIAS fmt)
@@ -185,7 +178,7 @@
   DEBUG_POSTFIX d)
 
 if (BUILD_SHARED_LIBS)
-  if (UNIX AND NOT APPLE)
+  if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS")
     # Fix rpmlint warning:
     # unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
     target_link_libraries(fmt -Wl,--as-needed)
@@ -259,7 +252,9 @@
 
   # Install the library and headers.
   install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
-          DESTINATION ${FMT_LIB_DIR})
+          LIBRARY DESTINATION ${FMT_LIB_DIR}
+          ARCHIVE DESTINATION ${FMT_LIB_DIR}
+          RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
 
   install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
           DESTINATION ${FMT_LIB_DIR} OPTIONAL)
diff --git a/ChangeLog.rst b/ChangeLog.rst
index 33a4003..dd71241 100644
--- a/ChangeLog.rst
+++ b/ChangeLog.rst
@@ -1,3 +1,269 @@
+6.1.1 - 2019-12-04
+------------------
+
+* Fixed shared library build on Windows
+  (`#1443 <https://github.com/fmtlib/fmt/pull/1443>`_,
+  `#1445 <https://github.com/fmtlib/fmt/issues/1445>`_,
+  `#1446 <https://github.com/fmtlib/fmt/pull/1446>`_,
+  `#1450 <https://github.com/fmtlib/fmt/issues/1450>`_).
+  Thanks `@egorpugin (Egor Pugin) <https://github.com/egorpugin>`_,
+  `@bbolli (Beat Bolli) <https://github.com/bbolli>`_.
+
+* Added a missing decimal point in exponent notation with trailing zeros.
+
+* Removed deprecated ``format_arg_store::TYPES``.
+
+6.1.0 - 2019-12-01
+------------------
+
+* {fmt} now formats IEEE 754 ``float`` and ``double`` using the shortest decimal
+  representation with correct rounding by default:
+
+  .. code:: c++
+
+     #include <cmath>
+     #include <fmt/core.h>
+
+     int main() {
+       fmt::print("{}", M_PI);
+     }
+
+  prints ``3.141592653589793``.
+
+* Made the fast binary to decimal floating-point formatter the default,
+  simplified it and improved performance. {fmt} is now 15 times faster than
+  libc++'s ``std::ostringstream``, 11 times faster than ``printf`` and 10%
+  faster than double-conversion on `dtoa-benchmark
+  <https://github.com/fmtlib/dtoa-benchmark>`_:
+
+  ==================  =========  =======
+  Function            Time (ns)  Speedup
+  ==================  =========  =======
+  ostringstream        1,346.30    1.00x
+  ostrstream           1,195.74    1.13x
+  sprintf                995.08    1.35x
+  doubleconv              99.10   13.59x
+  fmt                     88.34   15.24x
+  ==================  =========  =======
+
+  .. image:: https://user-images.githubusercontent.com/576385/
+             69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png
+
+* {fmt} no longer converts ``float`` arguments to ``double``. In particular this
+  improves the default (shortest) representation of floats and makes
+  ``fmt::format`` consistent with ``std::format`` specs
+  (`#1336 <https://github.com/fmtlib/fmt/issues/1336>`_,
+  `#1353 <https://github.com/fmtlib/fmt/issues/1353>`_,
+  `#1360 <https://github.com/fmtlib/fmt/pull/1360>`_,
+  `#1361 <https://github.com/fmtlib/fmt/pull/1361>`_):
+
+  .. code:: c++
+
+     fmt::print("{}", 0.1f);
+
+  prints ``0.1`` instead of ``0.10000000149011612``.
+
+  Thanks `@orivej (Orivej Desh) <https://github.com/orivej>`_.
+
+* Made floating-point formatting output consistent with ``printf``/iostreams
+  (`#1376 <https://github.com/fmtlib/fmt/issues/1376>`_,
+  `#1417 <https://github.com/fmtlib/fmt/issues/1417>`_).
+
+* Added support for 128-bit integers
+  (`#1287 <https://github.com/fmtlib/fmt/pull/1287>`_):
+
+  .. code:: c++
+
+     fmt::print("{}", std::numeric_limits<__int128_t>::max());
+
+  prints ``170141183460469231731687303715884105727``.
+
+  Thanks `@denizevrenci (Deniz Evrenci) <https://github.com/denizevrenci>`_.
+
+* The overload of ``print`` that takes ``text_style`` is now atomic, i.e. the
+  output from different threads doesn't interleave
+  (`#1351 <https://github.com/fmtlib/fmt/pull/1351>`_).
+  Thanks `@tankiJong (Tanki Zhang) <https://github.com/tankiJong>`_.
+
+* Made compile time in the header-only mode ~20% faster by reducing the number
+  of template instantiations. ``wchar_t`` overload of ``vprint`` was moved from
+  ``fmt/core.h`` to ``fmt/format.h``.
+
+* Added an overload of ``fmt::join`` that works with tuples
+  (`#1322 <https://github.com/fmtlib/fmt/issues/1322>`_,
+  `#1330 <https://github.com/fmtlib/fmt/pull/1330>`_):
+
+  .. code:: c++
+
+     #include <tuple>
+     #include <fmt/ranges.h>
+
+     int main() {
+       std::tuple<char, int, float> t{'a', 1, 2.0f};
+       fmt::print("{}", t);
+     }
+
+  prints ``('a', 1, 2.0)``.
+
+  Thanks `@jeremyong (Jeremy Ong) <https://github.com/jeremyong>`_.
+
+* Changed formatting of octal zero with prefix from "00" to "0":
+
+  .. code:: c++
+
+     fmt::print("{:#o}", 0);
+
+  prints ``0``.
+
+* The locale is now passed to ostream insertion (``<<``) operators
+  (`#1406 <https://github.com/fmtlib/fmt/pull/1406>`_):
+
+  .. code:: c++
+
+     #include <fmt/locale.h>
+     #include <fmt/ostream.h>
+
+     struct S {
+       double value;
+     };
+
+     std::ostream& operator<<(std::ostream& os, S s) {
+       return os << s.value;
+     }
+
+     int main() {
+       auto s = fmt::format(std::locale("fr_FR.UTF-8"), "{}", S{0.42});
+       // s == "0,42"
+     }
+
+  Thanks `@dlaugt (Daniel Laügt) <https://github.com/dlaugt>`_.
+
+* Locale-specific number formatting now uses grouping
+  (`#1393 <https://github.com/fmtlib/fmt/issues/1393>`_
+  `#1394 <https://github.com/fmtlib/fmt/pull/1394>`_).
+  Thanks `@skrdaniel <https://github.com/skrdaniel>`_.
+
+* Fixed handling of types with deleted implicit rvalue conversion to
+  ``const char**`` (`#1421 <https://github.com/fmtlib/fmt/issues/1421>`_):
+
+  .. code:: c++
+
+     struct mystring {
+       operator const char*() const&;
+       operator const char*() &;
+       operator const char*() const&& = delete;
+       operator const char*() && = delete;
+     };
+     mystring str;
+     fmt::print("{}", str); // now compiles
+
+* Enums are now mapped to correct underlying types instead of ``int``
+  (`#1286 <https://github.com/fmtlib/fmt/pull/1286>`_).
+  Thanks `@agmt (Egor Seredin) <https://github.com/agmt>`_.
+
+* Enum classes are no longer implicitly converted to ``int``
+  (`#1424 <https://github.com/fmtlib/fmt/issues/1424>`_).
+
+* Added ``basic_format_parse_context`` for consistency with C++20
+  ``std::format`` and deprecated ``basic_parse_context``.
+
+* Fixed handling of UTF-8 in precision
+  (`#1389 <https://github.com/fmtlib/fmt/issues/1389>`_,
+  `#1390 <https://github.com/fmtlib/fmt/pull/1390>`_).
+  Thanks `@tajtiattila (Attila Tajti) <https://github.com/tajtiattila>`_.
+
+* {fmt} can now be installed on Linux, macOS and Windows with
+  `Conda <https://docs.conda.io/en/latest/>`__ using its
+  `conda-forge <https://conda-forge.org>`__
+  `package <https://github.com/conda-forge/fmt-feedstock>`__
+  (`#1410 <https://github.com/fmtlib/fmt/pull/1410>`_)::
+
+    conda install -c conda-forge fmt
+
+  Thanks `@tdegeus (Tom de Geus) <https://github.com/tdegeus>`_.
+
+* Added a CUDA test (`#1285 <https://github.com/fmtlib/fmt/pull/1285>`_,
+  `#1317 <https://github.com/fmtlib/fmt/pull/1317>`_).
+  Thanks `@luncliff (Park DongHa) <https://github.com/luncliff>`_ and
+  `@risa2000 <https://github.com/risa2000>`_.
+
+* Improved documentation (`#1276 <https://github.com/fmtlib/fmt/pull/1276>`_,
+  `#1291 <https://github.com/fmtlib/fmt/issues/1291>`_,
+  `#1296 <https://github.com/fmtlib/fmt/issues/1296>`_,
+  `#1315 <https://github.com/fmtlib/fmt/pull/1315>`_,
+  `#1332 <https://github.com/fmtlib/fmt/pull/1332>`_,
+  `#1337 <https://github.com/fmtlib/fmt/pull/1337>`_,
+  `#1395 <https://github.com/fmtlib/fmt/issues/1395>`_
+  `#1418 <https://github.com/fmtlib/fmt/pull/1418>`_).
+  Thanks
+  `@waywardmonkeys (Bruce Mitchener) <https://github.com/waywardmonkeys>`_,
+  `@pauldreik (Paul Dreik) <https://github.com/pauldreik>`_,
+  `@jackoalan (Jack Andersen) <https://github.com/jackoalan>`_.
+
+* Various code improvements
+  (`#1358 <https://github.com/fmtlib/fmt/pull/1358>`_,
+  `#1407 <https://github.com/fmtlib/fmt/pull/1407>`_).
+  Thanks `@orivej (Orivej Desh) <https://github.com/orivej>`_,
+  `@dpacbach (David P. Sicilia) <https://github.com/dpacbach>`_,
+
+* Fixed compile-time format string checks for user-defined types
+  (`#1292 <https://github.com/fmtlib/fmt/issues/1292>`_).
+
+* Worked around a false positive in ``unsigned-integer-overflow`` sanitizer
+  (`#1377 <https://github.com/fmtlib/fmt/issues/1377>`_).
+
+* Fixed various warnings and compilation issues
+  (`#1273 <https://github.com/fmtlib/fmt/issues/1273>`_,
+  `#1278 <https://github.com/fmtlib/fmt/pull/1278>`_,
+  `#1280 <https://github.com/fmtlib/fmt/pull/1280>`_,
+  `#1281 <https://github.com/fmtlib/fmt/issues/1281>`_,
+  `#1288 <https://github.com/fmtlib/fmt/issues/1288>`_,
+  `#1290 <https://github.com/fmtlib/fmt/pull/1290>`_,
+  `#1301 <https://github.com/fmtlib/fmt/pull/1301>`_,
+  `#1305 <https://github.com/fmtlib/fmt/issues/1305>`_,
+  `#1306 <https://github.com/fmtlib/fmt/issues/1306>`_,
+  `#1309 <https://github.com/fmtlib/fmt/issues/1309>`_,
+  `#1312 <https://github.com/fmtlib/fmt/pull/1312>`_,
+  `#1313 <https://github.com/fmtlib/fmt/issues/1313>`_,
+  `#1316 <https://github.com/fmtlib/fmt/issues/1316>`_,
+  `#1319 <https://github.com/fmtlib/fmt/issues/1319>`_,
+  `#1320 <https://github.com/fmtlib/fmt/pull/1320>`_,
+  `#1326 <https://github.com/fmtlib/fmt/pull/1326>`_,
+  `#1328 <https://github.com/fmtlib/fmt/pull/1328>`_,
+  `#1344 <https://github.com/fmtlib/fmt/issues/1344>`_,
+  `#1345 <https://github.com/fmtlib/fmt/pull/1345>`_,
+  `#1347 <https://github.com/fmtlib/fmt/pull/1347>`_,
+  `#1349 <https://github.com/fmtlib/fmt/pull/1349>`_,
+  `#1354 <https://github.com/fmtlib/fmt/issues/1354>`_,
+  `#1362 <https://github.com/fmtlib/fmt/issues/1362>`_,
+  `#1366 <https://github.com/fmtlib/fmt/issues/1366>`_,
+  `#1364 <https://github.com/fmtlib/fmt/pull/1364>`_,
+  `#1370 <https://github.com/fmtlib/fmt/pull/1370>`_,
+  `#1371 <https://github.com/fmtlib/fmt/pull/1371>`_,
+  `#1385 <https://github.com/fmtlib/fmt/issues/1385>`_,
+  `#1388 <https://github.com/fmtlib/fmt/issues/1388>`_,
+  `#1397 <https://github.com/fmtlib/fmt/pull/1397>`_,
+  `#1414 <https://github.com/fmtlib/fmt/pull/1414>`_,
+  `#1416 <https://github.com/fmtlib/fmt/pull/1416>`_,
+  `#1422 <https://github.com/fmtlib/fmt/issues/1422>`_
+  `#1427 <https://github.com/fmtlib/fmt/pull/1427>`_,
+  `#1431 <https://github.com/fmtlib/fmt/issues/1431>`_,
+  `#1433 <https://github.com/fmtlib/fmt/pull/1433>`_).
+  Thanks `@hhb <https://github.com/hhb>`_,
+  `@gsjaardema (Greg Sjaardema) <https://github.com/gsjaardema>`_,
+  `@gabime (Gabi Melman) <https://github.com/gabime>`_,
+  `@neheb (Rosen Penev) <https://github.com/neheb>`_,
+  `@vedranmiletic (Vedran Miletić) <https://github.com/vedranmiletic>`_,
+  `@dkavolis (Daumantas Kavolis) <https://github.com/dkavolis>`_,
+  `@mwinterb <https://github.com/mwinterb>`_,
+  `@orivej (Orivej Desh) <https://github.com/orivej>`_,
+  `@denizevrenci (Deniz Evrenci) <https://github.com/denizevrenci>`_
+  `@leonklingele <https://github.com/leonklingele>`_,
+  `@chronoxor (Ivan Shynkarenka) <https://github.com/chronoxor>`_,
+  `@kent-tri <https://github.com/kent-tri>`_,
+  `@0x8000-0000 (Florin Iucha) <https://github.com/0x8000-0000>`_,
+  `@marti4d (Chris Martin) <https://github.com/marti4d>`_.
+
 6.0.0 - 2019-08-26
 ------------------
 
@@ -183,6 +449,7 @@
 * Modernized the codebase using more C++11 features and removing workarounds.
   Most importantly, ``buffer_context`` is now an alias template, so
   use ``buffer_context<T>`` instead of ``buffer_context<T>::type``.
+  These features require GCC 4.8 or later.
 
 * ``formatter`` specializations now always take precedence over implicit
   conversions to ``int`` and the undocumented ``convert_to_int`` trait
@@ -298,7 +565,7 @@
   `#1222 <https://github.com/fmtlib/fmt/issues/1222>`_).
   Thanks `@alabuzhev (Alex Alabuzhev) <https://github.com/alabuzhev>`_.
 
-* Fixed bugs discovered by fuzzing or during fuzzing integation
+* Fixed bugs discovered by fuzzing or during fuzzing integration
   (`#1124 <https://github.com/fmtlib/fmt/issues/1124>`_,
   `#1127 <https://github.com/fmtlib/fmt/issues/1127>`_,
   `#1132 <https://github.com/fmtlib/fmt/issues/1132>`_,
diff --git a/METADATA b/METADATA
index 157fb95..6ccb557 100644
--- a/METADATA
+++ b/METADATA
@@ -9,10 +9,10 @@
     type: GIT
     value: "https://github.com/fmtlib/fmt.git"
   }
-  version: "6.0.0"
+  version: "6.1.1"
   last_upgrade_date {
     year: 2019
-    month: 8
-    day: 26
+    month: 12
+    day: 5
   }
 }
diff --git a/README.rst b/README.rst
index f08b22f..0d01227 100644
--- a/README.rst
+++ b/README.rst
@@ -7,6 +7,10 @@
 .. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
    :target: https://ci.appveyor.com/project/vitaut/fmt
 
+.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/libfmt.svg
+   :alt: fmt is continuously fuzzed att oss-fuzz
+   :target: https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dlibfmt&can=1
+
 .. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
    :alt: Ask questions at StackOverflow with the tag fmt
    :target: http://stackoverflow.com/questions/tagged/fmt
@@ -24,7 +28,7 @@
 * Replacement-based `format API <https://fmt.dev/dev/api.html>`_ with
   positional arguments for localization.
 * `Format string syntax <https://fmt.dev/dev/syntax.html>`_ similar to the one
-  of `str.format <https://docs.python.org/2/library/stdtypes.html#str.format>`_
+  of `str.format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
   in Python.
 * Safe `printf implementation
   <https://fmt.dev/latest/api.html#printf-formatting>`_ including
@@ -39,12 +43,12 @@
   consists of just three header files, ``core.h``, ``format.h`` and
   ``format-inl.h``) and compiled code. See `Compile time and code bloat`_.
 * Reliability: the library has an extensive set of `unit tests
-  <https://github.com/fmtlib/fmt/tree/master/test>`_.
+  <https://github.com/fmtlib/fmt/tree/master/test>`_ and is continuously fuzzed.
 * Safety: the library is fully type safe, errors in format strings can be
   reported at compile time, automatic memory management prevents buffer overflow
   errors.
 * Ease of use: small self-contained code base, no external dependencies,
-  permissive BSD `license
+  permissive MIT `license
   <https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
 * `Portability <https://fmt.dev/latest/index.html#portability>`_ with
   consistent output across platforms and support for older compilers.
@@ -77,16 +81,15 @@
 .. code:: c++
 
     // test.cc
-    #define FMT_STRING_ALIAS 1
     #include <fmt/format.h>
-    std::string s = format(fmt("{2}"), 42);
+    std::string s = format(FMT_STRING("{2}"), 42);
 
 .. code::
 
     $ c++ -Iinclude -std=c++14 test.cc
     ...
     test.cc:4:17: note: in instantiation of function template specialization 'fmt::v5::format<S, int>' requested here
-    std::string s = format(fmt("{2}"), 42);
+    std::string s = format(FMT_STRING("{2}"), 42);
                     ^
     include/fmt/core.h:778:19: note: non-constexpr function 'on_error' cannot be used in a constant expression
         ErrorHandler::on_error(message);
@@ -118,11 +121,10 @@
 
     template <>
     struct fmt::formatter<date> {
-      template <typename ParseContext>
-      constexpr auto parse(ParseContext &ctx) { return ctx.begin(); }
+      constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
 
       template <typename FormatContext>
-      auto format(const date &d, FormatContext &ctx) {
+      auto format(const date& d, FormatContext& ctx) {
         return format_to(ctx.out(), "{}-{}-{}", d.year, d.month, d.day);
       }
     };
@@ -138,12 +140,12 @@
 .. code:: c++
 
     // Prints formatted error message.
-    void vreport_error(const char *format, fmt::format_args args) {
+    void vreport_error(const char* format, fmt::format_args args) {
       fmt::print("Error: ");
       fmt::vprint(format, args);
     }
     template <typename... Args>
-    void report_error(const char *format, const Args & ... args) {
+    void report_error(const char* format, const Args & ... args) {
       vreport_error(format, fmt::make_format_args(args...));
     }
 
@@ -162,15 +164,14 @@
 ================= ============= ===========
 Library           Method        Run Time, s
 ================= ============= ===========
-libc              printf          1.01
-libc++            std::ostream    3.04
-{fmt} 1632f72     fmt::print      0.86
-tinyformat 2.0.1  tfm::printf     3.23
-Boost Format 1.67 boost::format   7.98
+libc              printf          1.03
+libc++            std::ostream    2.98
+{fmt} 4de41a      fmt::print      0.76
+Boost Format 1.67 boost::format   7.24
 Folly Format      folly::format   2.23
 ================= ============= ===========
 
-{fmt} is the fastest of the benchmarked methods, ~17% faster than ``printf``.
+{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
 
 The above results were generated by building ``tinyformat_test.cpp`` on macOS
 10.14.3 with ``clang++ -O3 -DSPEED_TEST -DHAVE_FORMAT``, and taking the best of
@@ -183,7 +184,7 @@
 formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
 and as fast as `double-conversion <https://github.com/google/double-conversion>`_:
 
-.. image:: https://user-images.githubusercontent.com/576385/54883977-9fe8c000-4e28-11e9-8bde-272d122e7c52.jpg
+.. image:: https://user-images.githubusercontent.com/576385/69767160-cdaca400-112f-11ea-9fc5-347c9f83caad.png
    :target: https://fmt.dev/unknown_mac64_clang10.0.html
 
 Compile time and code bloat
@@ -207,7 +208,6 @@
 printf+string            16.4                   29                 26
 iostreams                31.1                   59                 55
 {fmt}                    19.0                   37                 34
-tinyformat               44.0                  103                 97
 Boost Format             91.9                  226                203
 Folly Format            115.7                  101                 88
 ============= =============== ==================== ==================
@@ -228,14 +228,13 @@
 printf+string            16.0                   33                 30
 iostreams                28.3                   56                 52
 {fmt}                    18.2                   59                 50
-tinyformat               32.6                   88                 82
 Boost Format             54.1                  365                303
 Folly Format             79.9                  445                430
 ============= =============== ==================== ==================
 
 ``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to
-compare formatting function overhead only. Boost Format and tinyformat are
-header-only libraries so they don't provide any linkage options.
+compare formatting function overhead only. Boost Format is a
+header-only library so it doesn't provide any linkage options.
 
 Running the tests
 ~~~~~~~~~~~~~~~~~
@@ -278,6 +277,8 @@
 
 * `Ceph <https://ceph.com/>`_: A scalable distributed storage system
 
+* `ccache <https://ccache.dev/>`_: A compiler cache
+
 * `CUAUV <http://cuauv.org/>`_: Cornell University's autonomous underwater
   vehicle
 
@@ -343,7 +344,7 @@
 * `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: Open-source
   MMORPG framework
 
-`More... <https://github.com/search?q=cppformat&type=Code>`_
+`More... <https://github.com/search?q=fmtlib&type=Code>`_
 
 If you are aware of other projects using this library, please let me know
 by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an
@@ -419,20 +420,6 @@
 It is also quite big and has a heavy dependency, STLSoft, which might be
 too restrictive for using it in some projects.
 
-Loki SafeFormat
-~~~~~~~~~~~~~~~
-
-SafeFormat is a formatting library which uses ``printf``-like format strings and
-is type safe. It doesn't support user-defined types or positional arguments and
-makes unconventional use of ``operator()`` for passing format arguments.
-
-Tinyformat
-~~~~~~~~~~
-
-This library supports ``printf``-like format strings and is very small .
-It doesn't support positional arguments and wrapping it in C++98 is somewhat
-difficult. Tinyformat relies on iostreams which limits its performance.
-
 Boost Spirit.Karma
 ~~~~~~~~~~~~~~~~~~
 
@@ -469,7 +456,7 @@
 License
 -------
 
-{fmt} is distributed under the BSD `license
+{fmt} is distributed under the MIT `license
 <https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.
 
 The `Format String Syntax
@@ -502,7 +489,7 @@
 <http://clang.llvm.org/doxygen/classclang_1_1Diagnostic.html>`_ in
 `Clang <http://clang.llvm.org/>`_.
 Format string syntax and the documentation are based on Python's `str.format
-<http://docs.python.org/2/library/stdtypes.html#str.format>`_.
+<https://docs.python.org/3/library/stdtypes.html#str.format>`_.
 Thanks `Doug Turnbull <https://github.com/softwaredoug>`_ for his valuable
 comments and contribution to the design of the type-safe API and
 `Gregory Czajkowski <https://github.com/gcflymoto>`_ for implementing binary
diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html
index ed19bcd..d5f4b70 100644
--- a/doc/_templates/layout.html
+++ b/doc/_templates/layout.html
@@ -84,7 +84,7 @@
 <div class="jumbotron">
   <div class="tb-container">
     <h1>{fmt}</h1>
-    <p class="lead">Small, safe and fast formatting library</p>
+    <p class="lead">A modern formatting library</p>
     <div class="btn-group" role="group">
       {% set name = 'fmt' if version.split('.')[0]|int >= 3 else 'cppformat' %}
       <a class="btn btn-success"
diff --git a/doc/api.rst b/doc/api.rst
index b5adf4b..0834a87 100644
--- a/doc/api.rst
+++ b/doc/api.rst
@@ -10,12 +10,14 @@
   facilities and a lightweight subset of formatting functions
 * :ref:`fmt/format.h <format-api>`: the full format API providing compile-time
   format string checks, output iterator and user-defined type support
+* :ref:`fmt/ranges.h <ranges-api>`: additional formatting support for ranges
+  and tuples
 * :ref:`fmt/chrono.h <chrono-api>`: date and time formatting
 * :ref:`fmt/ostream.h <ostream-api>`: ``std::ostream`` support
 * :ref:`fmt/printf.h <printf-api>`: ``printf`` formatting
 
 All functions and types provided by the library reside in namespace ``fmt`` and
-macros have prefix ``FMT_`` or ``fmt``.
+macros have prefix ``FMT_``.
 
 .. _core-api:
 
@@ -23,7 +25,8 @@
 ========
 
 ``fmt/core.h`` defines the core API which provides argument handling facilities
-and a lightweight subset of formatting functions.
+and a lightweight subset of formatting functions. In the header-only mode
+include ``fmt/format.h`` instead of ``fmt/core.h``.
 
 The following functions use :ref:`format string syntax <syntax>`
 similar to that of Python's `str.format
@@ -49,7 +52,6 @@
 
 .. doxygenfunction:: print(std::FILE *, const S&, Args&&...)
 .. doxygenfunction:: vprint(std::FILE *, string_view, format_args)
-.. doxygenfunction:: vprint(std::FILE *, wstring_view, wformat_args)
 
 Named Arguments
 ---------------
@@ -94,8 +96,11 @@
 Compile-time Format String Checks
 ---------------------------------
 
+Compile-time checks are supported for built-in and string types as well as
+user-defined types with ``constexpr`` ``parse`` functions in their ``formatter``
+specializations.
+
 .. doxygendefine:: FMT_STRING
-.. doxygendefine:: fmt
 
 Formatting User-defined Types
 -----------------------------
@@ -107,32 +112,56 @@
 
   struct point { double x, y; };
 
-  namespace fmt {
   template <>
-  struct formatter<point> {
-    template <typename ParseContext>
-    constexpr auto parse(ParseContext &ctx) { return ctx.begin(); }
+  struct fmt::formatter<point> {
+    // Presentation format: 'f' - fixed, 'e' - exponential.
+    char presentation = 'f';
 
+    // Parses format specifications of the form ['f' | 'e'].
+    constexpr auto parse(format_parse_context& ctx) {
+      // [ctx.begin(), ctx.end()) is a character range that contains a part of
+      // the format string starting from the format specifications to be parsed,
+      // e.g. in
+      //
+      //   fmt::format("{:f} - point of interest", point{1, 2});
+      //
+      // the range will contain "f} - point of interest". The formatter should
+      // parse specifiers until '}' or the end of the range. In this example
+      // the formatter should parse the 'f' specifier and return an iterator
+      // pointing to '}'.
+
+      // Parse the presentation format and store it in the formatter:
+      auto it = ctx.begin(), end = ctx.end();
+      if (it != end && (*it == 'f' || *it == 'e')) presentation = *it++;
+
+      // Check if reached the end of the range:
+      if (it != end && *it != '}')
+        throw format_error("invalid format");
+
+      // Return an iterator past the end of the parsed range:
+      return it;
+    }
+
+    // Formats the point p using the parsed format specification (presentation)
+    // stored in this formatter.
     template <typename FormatContext>
-    auto format(const point &p, FormatContext &ctx) {
-      return format_to(ctx.out(), "({:.1f}, {:.1f})", p.x, p.y);
+    auto format(const point& p, FormatContext& ctx) {
+      // ctx.out() is an output iterator to write to.
+      return format_to(
+          ctx.out(),
+          presentation == 'f' ? "({:.1f}, {:.1f})" : "({:.1e}, {:.1e})",
+          p.x, p.y);
     }
   };
-  }
 
 Then you can pass objects of type ``point`` to any formatting function::
 
   point p = {1, 2};
-  std::string s = fmt::format("{}", p);
+  std::string s = fmt::format("{:f}", p);
   // s == "(1.0, 2.0)"
 
-In the example above the ``formatter<point>::parse`` function ignores the
-contents of the format string referred to by ``ctx.begin()`` so the object will
-always be formatted in the same way. See ``formatter<tm>::parse`` in
-:file:`fmt/chrono.h` for an advanced example of how to parse the format string and
-customize the formatted output.
-
-You can also reuse existing formatters, for example::
+You can also reuse existing formatters via inheritance or composition, for
+example::
 
   enum class color {red, green, blue};
 
@@ -140,7 +169,7 @@
   struct fmt::formatter<color>: formatter<string_view> {
     // parse is inherited from formatter<string_view>.
     template <typename FormatContext>
-    auto format(color c, FormatContext &ctx) {
+    auto format(color c, FormatContext& ctx) {
       string_view name = "unknown";
       switch (c) {
       case color::red:   name = "red"; break;
@@ -180,6 +209,9 @@
     fmt::print("{}", a); // prints "B"
   }
 
+.. doxygenclass:: fmt::basic_format_parse_context
+   :members:
+
 Output Iterator Support
 -----------------------
 
@@ -263,7 +295,7 @@
     template <typename ...Args>
     inline custom_string format(custom_allocator alloc,
                                 fmt::string_view format_str,
-                                const Args & ... args) {
+                                const Args& ... args) {
       return vformat(alloc, format_str, fmt::make_format_args(args...));
     }
 
@@ -278,21 +310,21 @@
 It is possible to change the way arguments are formatted by providing a
 custom argument formatter class::
 
-  using arg_formatter =
-    fmt::arg_formatter<fmt::back_insert_range<fmt::internal::buffer>>;
+  using arg_formatter = fmt::arg_formatter<fmt::buffer_range<char>>;
 
   // A custom argument formatter that formats negative integers as unsigned
   // with the ``x`` format specifier.
   class custom_arg_formatter : public arg_formatter {
    public:
-    custom_arg_formatter(fmt::format_context &ctx,
-                         fmt::format_specs *spec = nullptr)
-      : arg_formatter(ctx, spec) {}
+    custom_arg_formatter(fmt::format_context& ctx,
+                         fmt::format_parse_context* parse_ctx = nullptr,
+                         fmt::format_specs* spec = nullptr)
+      : arg_formatter(ctx, parse_ctx, spec) {}
 
     using arg_formatter::operator();
 
     auto operator()(int value) {
-      if (spec().type() == 'x')
+      if (specs() && specs()->type == 'x')
         return (*this)(static_cast<unsigned>(value)); // convert to unsigned and format
       return arg_formatter::operator()(value);
     }
@@ -307,7 +339,7 @@
 
   template <typename ...Args>
   inline std::string custom_format(
-      fmt::string_view format_str, const Args &... args) {
+      fmt::string_view format_str, const Args&... args) {
     return custom_vformat(format_str, fmt::make_format_args(args...));
   }
 
@@ -316,6 +348,31 @@
 .. doxygenclass:: fmt::arg_formatter
    :members:
 
+.. _ranges-api:
+
+Ranges and Tuple Formatting
+===========================
+
+The library also supports convenient formatting of ranges and tuples::
+
+  #include <fmt/ranges.h>
+
+  std::tuple<char, int, float> t{'a', 1, 2.0f};
+  // Prints "('a', 1, 2.0)"
+  fmt::print("{}", t);
+
+
+NOTE: currently, the overload of ``fmt::join`` for iterables exists in the main
+``format.h`` header, but expect this to change in the future.
+
+Using ``fmt::join``, you can separate tuple elements with a custom separator::
+
+  #include <fmt/ranges.h>
+
+  std::tuple<int, char> t = {1, 'a'};
+  // Prints "1, a"
+  fmt::print("{}", fmt::join(t, ", "));
+
 .. _chrono-api:
 
 Date and Time Formatting
@@ -349,7 +406,7 @@
   public:
     date(int year, int month, int day): year_(year), month_(month), day_(day) {}
 
-    friend std::ostream &operator<<(std::ostream &os, const date &d) {
+    friend std::ostream& operator<<(std::ostream& os, const date& d) {
       return os << d.year_ << '-' << d.month_ << '-' << d.day_;
     }
   };
diff --git a/doc/build.py b/doc/build.py
index fb4f546..2f0c8b8 100755
--- a/doc/build.py
+++ b/doc/build.py
@@ -6,7 +6,7 @@
 from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE
 from distutils.version import LooseVersion
 
-versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0']
+versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1']
 
 def pip_install(package, commit=None, **kwargs):
   "Install package using pip."
diff --git a/doc/index.rst b/doc/index.rst
index 9480ff6..cbc9154 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -1,17 +1,18 @@
 Overview
 ========
 
-**fmt** (formerly cppformat) is an open-source formatting library.
-It can be used as a fast and safe alternative to printf and IOStreams.
+**{fmt}** is an open-source formatting library providing a fast and safe
+alternative to C stdio and C++ iostreams.
 
 .. raw:: html
 
    <div class="panel panel-default">
      <div class="panel-heading">What users say:</div>
      <div class="panel-body">
-       Thanks for creating this library. It’s been a hole in C++ for a long
-       time. I’ve used both boost::format and loki::SPrintf, and neither felt
-       like the right answer. This does.
+       Thanks for creating this library. It’s been a hole in C++ for
+       aa long time. I’ve used both <code>boost::format</code> and
+       <code>loki::SPrintf</code>, and neither felt like the right answer.
+       This does.
      </div>
    </div>
 
@@ -20,12 +21,13 @@
 Format API
 ----------
 
-The replacement-based Format API provides a safe alternative to ``printf``,
-``sprintf`` and friends with comparable or `better performance
-<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_.
+The format API is similar in spirit to the C ``printf`` family of function but
+is safer, simpler and serveral times `faster
+<http://zverovich.net/2013/09/07/integer-to-string-conversion-in-cplusplus.html>`_
+than common standard library implementations.
 The `format string syntax <syntax.html>`_ is similar to the one used by
-`str.format <http://docs.python.org/3/library/stdtypes.html#str.format>`_
-in Python:
+`str.format <http://docs.python.org/3/library/stdtypes.html#str.format>`_ in
+Python:
 
 .. code:: c++
 
@@ -60,7 +62,7 @@
   fmt::print("I'd rather be {1} than {0}.", "right", "happy");
 
 Named arguments can be created with ``fmt::arg``. This makes it easier to track 
-what goes where when multiple values are being inserted:
+what goes where when multiple arguments are being formatted:
 
 .. code:: c++
 
@@ -72,21 +74,10 @@
 
 .. code:: c++
 
+  using namespace fmt::literals;
   fmt::print("Hello, {name}! The answer is {number}. Goodbye, {name}.",
              "name"_a="World", "number"_a=42);
 
-The ``_format`` suffix may be used to format string literals similar to Python:
-
-.. code:: c++
-
-  std::string message = "{0}{1}{0}"_format("abra", "cad"); 
-
-Other than the placement of the format string on the left of the operator, 
-``_format`` is functionally identical to ``fmt::format``. In order to use the 
-literal operators, they must be made visible with the directive 
-``using namespace fmt::literals;``. Note that this brings in only ``_a`` and 
-``_format`` but nothing else from the ``fmt`` namespace.
-
 .. _safety:
 
 Safety
@@ -106,11 +97,10 @@
 
 .. code:: c++
 
-  format(fmt("The answer is {:d}"), "forty-two");
+  format(FMT_STRING("The answer is {:d}"), "forty-two");
 
 reports a compile-time error for the same reason on compilers that support
-relaxed ``constexpr``. See `here <api.html#c.fmt>`_ for how to enable
-compile-time checks.
+relaxed ``constexpr``. See `here <api.html#c.fmt>`_ for details.
 
 The following code
 
@@ -178,13 +168,13 @@
 * alias templates
 
 These are available since GCC 4.8, Clang 3.0 and MSVC 19.0 (2015). For older
-compilers use fmt `version 4.x
+compilers use {fmt} `version 4.x
 <https://github.com/fmtlib/fmt/releases/tag/4.1.0>`_ which continues to be
 maintained and only requires C++98.
 
 The output of all formatting functions is consistent across platforms. In
 particular, formatting a floating-point infinity always gives ``inf`` while the
-output of ``printf`` is platform-dependent in this case. For example,
+output of ``printf`` is platform-dependent. For example,
 
 .. code::
 
@@ -197,9 +187,9 @@
 Ease of Use
 -----------
 
-fmt has a small self-contained code base with the core library consisting of
+{fmt} has a small self-contained code base with the core library consisting of
 just three header files and no external dependencies.
-A permissive BSD `license <https://github.com/fmtlib/fmt#license>`_ allows
+A permissive MIT `license <https://github.com/fmtlib/fmt#license>`_ allows
 using the library both in open-source and commercial projects.
 
 .. raw:: html
diff --git a/doc/syntax.rst b/doc/syntax.rst
index a6bddf6..1f25dac 100644
--- a/doc/syntax.rst
+++ b/doc/syntax.rst
@@ -101,11 +101,6 @@
 | ``'>'`` | Forces the field to be right-aligned within the          |
 |         | available space (this is the default for numbers).       |
 +---------+----------------------------------------------------------+
-| ``'='`` | Forces the padding to be placed after the sign (if any)  |
-|         | but before the digits.  This is used for printing fields |
-|         | in the form '+000000120'. This alignment option is only  |
-|         | valid for numeric types.                                 |
-+---------+----------------------------------------------------------+
 | ``'^'`` | Forces the field to be centered within the available     |
 |         | space.                                                   |
 +---------+----------------------------------------------------------+
@@ -154,9 +149,11 @@
 *width* is a decimal integer defining the minimum field width.  If not
 specified, then the field width will be determined by the content.
 
-Preceding the *width* field by a zero (``'0'``) character enables
-sign-aware zero-padding for numeric types.  This is equivalent to a *fill*
-character of ``'0'`` with an *alignment* type of ``'='``.
+Preceding the *width* field by a zero (``'0'``) character enables sign-aware
+zero-padding for numeric types. It forces the padding to be placed after the
+sign or base (if any) but before the digits. This is used for printing fields in
+the form '+000000120'. This option is only valid for numeric types and it has no
+effect on formatting of infinity and NaN.
 
 The *precision* is a decimal number indicating how many digits should be
 displayed after the decimal point for a floating-point value formatted with
@@ -264,6 +261,10 @@
 |         | ``'E'`` if the number gets too large. The                |
 |         | representations of infinity and NaN are uppercased, too. |
 +---------+----------------------------------------------------------+
+| ``'n'`` | Number. This is the same as ``'g'``, except that it uses |
+|         | the current locale setting to insert the appropriate     |
+|         | number separator characters.                             |
++---------+----------------------------------------------------------+
 | ``'%'`` | Fixed point as a percentage. This is similar to ``'f'``, |
 |         | but the argument is multiplied by 100 and a percent sign |
 |         | ``%`` is appended.                                       |
diff --git a/doc/usage.rst b/doc/usage.rst
index e71629c..dacb4f0 100644
--- a/doc/usage.rst
+++ b/doc/usage.rst
@@ -103,7 +103,17 @@
   make doc
 
 This will generate the HTML documentation in ``doc/html``.
-  
+
+Conda
+=====
+
+fmt can be installed on Linux, macOS and Windows with
+`Conda <https://docs.conda.io/en/latest/>`__, using its
+`conda-forge <https://conda-forge.org>`__
+`package <https://github.com/conda-forge/fmt-feedstock>`__, as follows::
+
+  conda install -c conda-forge fmt
+
 Android NDK
 ===========
 
diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h
index 57ce9ef..9abe7c4 100644
--- a/include/fmt/chrono.h
+++ b/include/fmt/chrono.h
@@ -16,16 +16,291 @@
 #include <locale>
 #include <sstream>
 
-// enable safe chrono durations, unless explicitly disabled
+FMT_BEGIN_NAMESPACE
+
+// Enable safe chrono durations, unless explicitly disabled.
 #ifndef FMT_SAFE_DURATION_CAST
 #  define FMT_SAFE_DURATION_CAST 1
 #endif
-
 #if FMT_SAFE_DURATION_CAST
-#  include "safe-duration-cast.h"
-#endif
 
-FMT_BEGIN_NAMESPACE
+// For conversion between std::chrono::durations without undefined
+// behaviour or erroneous results.
+// This is a stripped down version of duration_cast, for inclusion in fmt.
+// See https://github.com/pauldreik/safe_duration_cast
+//
+// Copyright Paul Dreik 2019
+namespace safe_duration_cast {
+
+template <typename To, typename From,
+          FMT_ENABLE_IF(!std::is_same<From, To>::value &&
+                        std::numeric_limits<From>::is_signed ==
+                            std::numeric_limits<To>::is_signed)>
+FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+  ec = 0;
+  using F = std::numeric_limits<From>;
+  using T = std::numeric_limits<To>;
+  static_assert(F::is_integer, "From must be integral");
+  static_assert(T::is_integer, "To must be integral");
+
+  // A and B are both signed, or both unsigned.
+  if (F::digits <= T::digits) {
+    // From fits in To without any problem.
+  } else {
+    // From does not always fit in To, resort to a dynamic check.
+    if (from < T::min() || from > T::max()) {
+      // outside range.
+      ec = 1;
+      return {};
+    }
+  }
+  return static_cast<To>(from);
+}
+
+/**
+ * converts From to To, without loss. If the dynamic value of from
+ * can't be converted to To without loss, ec is set.
+ */
+template <typename To, typename From,
+          FMT_ENABLE_IF(!std::is_same<From, To>::value &&
+                        std::numeric_limits<From>::is_signed !=
+                            std::numeric_limits<To>::is_signed)>
+FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+  ec = 0;
+  using F = std::numeric_limits<From>;
+  using T = std::numeric_limits<To>;
+  static_assert(F::is_integer, "From must be integral");
+  static_assert(T::is_integer, "To must be integral");
+
+  if (F::is_signed && !T::is_signed) {
+    // From may be negative, not allowed!
+    if (fmt::internal::is_negative(from)) {
+      ec = 1;
+      return {};
+    }
+
+    // From is positive. Can it always fit in To?
+    if (F::digits <= T::digits) {
+      // yes, From always fits in To.
+    } else {
+      // from may not fit in To, we have to do a dynamic check
+      if (from > static_cast<From>(T::max())) {
+        ec = 1;
+        return {};
+      }
+    }
+  }
+
+  if (!F::is_signed && T::is_signed) {
+    // can from be held in To?
+    if (F::digits < T::digits) {
+      // yes, From always fits in To.
+    } else {
+      // from may not fit in To, we have to do a dynamic check
+      if (from > static_cast<From>(T::max())) {
+        // outside range.
+        ec = 1;
+        return {};
+      }
+    }
+  }
+
+  // reaching here means all is ok for lossless conversion.
+  return static_cast<To>(from);
+
+}  // function
+
+template <typename To, typename From,
+          FMT_ENABLE_IF(std::is_same<From, To>::value)>
+FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
+  ec = 0;
+  return from;
+}  // function
+
+// clang-format off
+/**
+ * converts From to To if possible, otherwise ec is set.
+ *
+ * input                            |    output
+ * ---------------------------------|---------------
+ * NaN                              | NaN
+ * Inf                              | Inf
+ * normal, fits in output           | converted (possibly lossy)
+ * normal, does not fit in output   | ec is set
+ * subnormal                        | best effort
+ * -Inf                             | -Inf
+ */
+// clang-format on
+template <typename To, typename From,
+          FMT_ENABLE_IF(!std::is_same<From, To>::value)>
+FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
+  ec = 0;
+  using T = std::numeric_limits<To>;
+  static_assert(std::is_floating_point<From>::value, "From must be floating");
+  static_assert(std::is_floating_point<To>::value, "To must be floating");
+
+  // catch the only happy case
+  if (std::isfinite(from)) {
+    if (from >= T::lowest() && from <= T::max()) {
+      return static_cast<To>(from);
+    }
+    // not within range.
+    ec = 1;
+    return {};
+  }
+
+  // nan and inf will be preserved
+  return static_cast<To>(from);
+}  // function
+
+template <typename To, typename From,
+          FMT_ENABLE_IF(std::is_same<From, To>::value)>
+FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
+  ec = 0;
+  static_assert(std::is_floating_point<From>::value, "From must be floating");
+  return from;
+}
+
+/**
+ * safe duration cast between integral durations
+ */
+template <typename To, typename FromRep, typename FromPeriod,
+          FMT_ENABLE_IF(std::is_integral<FromRep>::value),
+          FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)>
+To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
+                      int& ec) {
+  using From = std::chrono::duration<FromRep, FromPeriod>;
+  ec = 0;
+  // the basic idea is that we need to convert from count() in the from type
+  // to count() in the To type, by multiplying it with this:
+  struct Factor
+      : std::ratio_divide<typename From::period, typename To::period> {};
+
+  static_assert(Factor::num > 0, "num must be positive");
+  static_assert(Factor::den > 0, "den must be positive");
+
+  // the conversion is like this: multiply from.count() with Factor::num
+  // /Factor::den and convert it to To::rep, all this without
+  // overflow/underflow. let's start by finding a suitable type that can hold
+  // both To, From and Factor::num
+  using IntermediateRep =
+      typename std::common_type<typename From::rep, typename To::rep,
+                                decltype(Factor::num)>::type;
+
+  // safe conversion to IntermediateRep
+  IntermediateRep count =
+      lossless_integral_conversion<IntermediateRep>(from.count(), ec);
+  if (ec) {
+    return {};
+  }
+  // multiply with Factor::num without overflow or underflow
+  if (Factor::num != 1) {
+    const auto max1 = internal::max_value<IntermediateRep>() / Factor::num;
+    if (count > max1) {
+      ec = 1;
+      return {};
+    }
+    const auto min1 = std::numeric_limits<IntermediateRep>::min() / Factor::num;
+    if (count < min1) {
+      ec = 1;
+      return {};
+    }
+    count *= Factor::num;
+  }
+
+  // this can't go wrong, right? den>0 is checked earlier.
+  if (Factor::den != 1) {
+    count /= Factor::den;
+  }
+  // convert to the to type, safely
+  using ToRep = typename To::rep;
+  const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec);
+  if (ec) {
+    return {};
+  }
+  return To{tocount};
+}
+
+/**
+ * safe duration_cast between floating point durations
+ */
+template <typename To, typename FromRep, typename FromPeriod,
+          FMT_ENABLE_IF(std::is_floating_point<FromRep>::value),
+          FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)>
+To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
+                      int& ec) {
+  using From = std::chrono::duration<FromRep, FromPeriod>;
+  ec = 0;
+  if (std::isnan(from.count())) {
+    // nan in, gives nan out. easy.
+    return To{std::numeric_limits<typename To::rep>::quiet_NaN()};
+  }
+  // maybe we should also check if from is denormal, and decide what to do about
+  // it.
+
+  // +-inf should be preserved.
+  if (std::isinf(from.count())) {
+    return To{from.count()};
+  }
+
+  // the basic idea is that we need to convert from count() in the from type
+  // to count() in the To type, by multiplying it with this:
+  struct Factor
+      : std::ratio_divide<typename From::period, typename To::period> {};
+
+  static_assert(Factor::num > 0, "num must be positive");
+  static_assert(Factor::den > 0, "den must be positive");
+
+  // the conversion is like this: multiply from.count() with Factor::num
+  // /Factor::den and convert it to To::rep, all this without
+  // overflow/underflow. let's start by finding a suitable type that can hold
+  // both To, From and Factor::num
+  using IntermediateRep =
+      typename std::common_type<typename From::rep, typename To::rep,
+                                decltype(Factor::num)>::type;
+
+  // force conversion of From::rep -> IntermediateRep to be safe,
+  // even if it will never happen be narrowing in this context.
+  IntermediateRep count =
+      safe_float_conversion<IntermediateRep>(from.count(), ec);
+  if (ec) {
+    return {};
+  }
+
+  // multiply with Factor::num without overflow or underflow
+  if (Factor::num != 1) {
+    constexpr auto max1 = internal::max_value<IntermediateRep>() /
+                          static_cast<IntermediateRep>(Factor::num);
+    if (count > max1) {
+      ec = 1;
+      return {};
+    }
+    constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() /
+                          static_cast<IntermediateRep>(Factor::num);
+    if (count < min1) {
+      ec = 1;
+      return {};
+    }
+    count *= static_cast<IntermediateRep>(Factor::num);
+  }
+
+  // this can't go wrong, right? den>0 is checked earlier.
+  if (Factor::den != 1) {
+    using common_t = typename std::common_type<IntermediateRep, intmax_t>::type;
+    count /= static_cast<common_t>(Factor::den);
+  }
+
+  // convert to the to type, safely
+  using ToRep = typename To::rep;
+
+  const ToRep tocount = safe_float_conversion<ToRep>(count, ec);
+  if (ec) {
+    return {};
+  }
+  return To{tocount};
+}
+}  // namespace safe_duration_cast
+#endif
 
 // Prevents expansion of a preceding token as a function-style macro.
 // Usage: f FMT_NOMACRO()
@@ -403,7 +678,7 @@
   return std::isfinite(value);
 }
 
-// Convers value to int and checks that it's in the range [0, upper).
+// Converts value to int and checks that it's in the range [0, upper).
 template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
 inline int to_nonnegative_int(T value, int upper) {
   FMT_ASSERT(value >= 0 && value <= upper, "invalid value");
@@ -582,8 +857,8 @@
   void write(Rep value, int width) {
     write_sign();
     if (isnan(value)) return write_nan();
-    uint32_or_64_t<int> n = to_unsigned(
-        to_nonnegative_int(value, (std::numeric_limits<int>::max)()));
+    uint32_or_64_or_128_t<int> n =
+        to_unsigned(to_nonnegative_int(value, max_value<int>()));
     int num_digits = internal::count_digits(n);
     if (width > num_digits) out = std::fill_n(out, width - num_digits, '0');
     out = format_decimal<char_type>(out, n, num_digits);
@@ -728,7 +1003,7 @@
 
   struct spec_handler {
     formatter& f;
-    basic_parse_context<Char>& context;
+    basic_format_parse_context<Char>& context;
     basic_string_view<Char> format_str;
 
     template <typename Id> FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) {
@@ -738,8 +1013,7 @@
 
     FMT_CONSTEXPR arg_ref_type make_arg_ref(basic_string_view<Char> arg_id) {
       context.check_arg_id(arg_id);
-      const auto str_val = internal::string_view_metadata(format_str, arg_id);
-      return arg_ref_type(str_val);
+      return arg_ref_type(arg_id);
     }
 
     FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) {
@@ -750,7 +1024,7 @@
     void on_fill(Char fill) { f.specs.fill[0] = fill; }
     void on_align(align_t align) { f.specs.align = align; }
     void on_width(unsigned width) { f.specs.width = width; }
-    void on_precision(unsigned precision) { f.precision = precision; }
+    void on_precision(unsigned _precision) { f.precision = _precision; }
     void end_precision() {}
 
     template <typename Id> void on_dynamic_width(Id arg_id) {
@@ -762,13 +1036,13 @@
     }
   };
 
-  using iterator = typename basic_parse_context<Char>::iterator;
+  using iterator = typename basic_format_parse_context<Char>::iterator;
   struct parse_range {
     iterator begin;
     iterator end;
   };
 
-  FMT_CONSTEXPR parse_range do_parse(basic_parse_context<Char>& ctx) {
+  FMT_CONSTEXPR parse_range do_parse(basic_format_parse_context<Char>& ctx) {
     auto begin = ctx.begin(), end = ctx.end();
     if (begin == end || *begin == '}') return {begin, begin};
     spec_handler handler{*this, ctx, format_str};
@@ -789,7 +1063,7 @@
  public:
   formatter() : precision(-1) {}
 
-  FMT_CONSTEXPR auto parse(basic_parse_context<Char>& ctx)
+  FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
       -> decltype(ctx.begin()) {
     auto range = do_parse(ctx);
     format_str = basic_string_view<Char>(
@@ -806,10 +1080,10 @@
     auto out = std::back_inserter(buf);
     using range = internal::output_range<decltype(ctx.out()), Char>;
     internal::basic_writer<range> w(range(ctx.out()));
-    internal::handle_dynamic_spec<internal::width_checker>(
-        specs.width, width_ref, ctx, format_str.begin());
+    internal::handle_dynamic_spec<internal::width_checker>(specs.width,
+                                                           width_ref, ctx);
     internal::handle_dynamic_spec<internal::precision_checker>(
-        precision, precision_ref, ctx, format_str.begin());
+        precision, precision_ref, ctx);
     if (begin == end || *begin == '}') {
       out = internal::format_chrono_duration_value(out, d.count(), precision);
       internal::format_chrono_duration_unit<Period>(out);
diff --git a/include/fmt/color.h b/include/fmt/color.h
index d9d3155..362a95e 100644
--- a/include/fmt/color.h
+++ b/include/fmt/color.h
@@ -299,15 +299,15 @@
     return static_cast<uint8_t>(ems) != 0;
   }
   FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT {
-    assert(has_foreground() && "no foreground specified for this style");
+    FMT_ASSERT(has_foreground(), "no foreground specified for this style");
     return foreground_color;
   }
   FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT {
-    assert(has_background() && "no background specified for this style");
+    FMT_ASSERT(has_background(), "no background specified for this style");
     return background_color;
   }
   FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
-    assert(has_emphasis() && "no emphasis specified for this style");
+    FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
     return ems;
   }
 
@@ -470,58 +470,41 @@
 }
 
 template <typename Char>
-std::basic_string<Char> vformat(const text_style& ts,
-                                basic_string_view<Char> format_str,
-                                basic_format_args<buffer_context<Char> > args) {
-  basic_memory_buffer<Char> buffer;
+void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
+                basic_string_view<Char> format_str,
+                basic_format_args<buffer_context<Char>> args) {
   bool has_style = false;
   if (ts.has_emphasis()) {
     has_style = true;
-    ansi_color_escape<Char> escape = make_emphasis<Char>(ts.get_emphasis());
-    buffer.append(escape.begin(), escape.end());
+    auto emphasis = internal::make_emphasis<Char>(ts.get_emphasis());
+    buf.append(emphasis.begin(), emphasis.end());
   }
   if (ts.has_foreground()) {
     has_style = true;
-    ansi_color_escape<Char> escape =
-        make_foreground_color<Char>(ts.get_foreground());
-    buffer.append(escape.begin(), escape.end());
+    auto foreground =
+        internal::make_foreground_color<Char>(ts.get_foreground());
+    buf.append(foreground.begin(), foreground.end());
   }
   if (ts.has_background()) {
     has_style = true;
-    ansi_color_escape<Char> escape =
-        make_background_color<Char>(ts.get_background());
-    buffer.append(escape.begin(), escape.end());
+    auto background =
+        internal::make_background_color<Char>(ts.get_background());
+    buf.append(background.begin(), background.end());
   }
-  internal::vformat_to(buffer, format_str, args);
+  vformat_to(buf, format_str, args);
   if (has_style) {
-    reset_color<Char>(buffer);
+    internal::reset_color<Char>(buf);
   }
-  return fmt::to_string(buffer);
 }
 }  // namespace internal
 
-template <typename S, typename Char = char_t<S> >
+template <typename S, typename Char = char_t<S>>
 void vprint(std::FILE* f, const text_style& ts, const S& format,
-            basic_format_args<buffer_context<Char> > args) {
-  bool has_style = false;
-  if (ts.has_emphasis()) {
-    has_style = true;
-    internal::fputs<Char>(internal::make_emphasis<Char>(ts.get_emphasis()), f);
-  }
-  if (ts.has_foreground()) {
-    has_style = true;
-    internal::fputs<Char>(
-        internal::make_foreground_color<Char>(ts.get_foreground()), f);
-  }
-  if (ts.has_background()) {
-    has_style = true;
-    internal::fputs<Char>(
-        internal::make_background_color<Char>(ts.get_background()), f);
-  }
-  vprint(f, format, args);
-  if (has_style) {
-    internal::reset_color<Char>(f);
-  }
+            basic_format_args<buffer_context<Char>> args) {
+  basic_memory_buffer<Char> buf;
+  internal::vformat_to(buf, ts, to_string_view(format), args);
+  buf.push_back(Char(0));
+  internal::fputs(buf.data(), f);
 }
 
 /**
@@ -536,7 +519,7 @@
 void print(std::FILE* f, const text_style& ts, const S& format_str,
            const Args&... args) {
   internal::check_format_string<Args...>(format_str);
-  using context = buffer_context<char_t<S> >;
+  using context = buffer_context<char_t<S>>;
   format_arg_store<context, Args...> as{args...};
   vprint(f, ts, format_str, basic_format_args<context>(as));
 }
@@ -554,11 +537,13 @@
   return print(stdout, ts, format_str, args...);
 }
 
-template <typename S, typename Char = char_t<S> >
+template <typename S, typename Char = char_t<S>>
 inline std::basic_string<Char> vformat(
     const text_style& ts, const S& format_str,
-    basic_format_args<buffer_context<Char> > args) {
-  return internal::vformat(ts, to_string_view(format_str), args);
+    basic_format_args<buffer_context<Char>> args) {
+  basic_memory_buffer<Char> buf;
+  internal::vformat_to(buf, ts, to_string_view(format_str), args);
+  return fmt::to_string(buf);
 }
 
 /**
@@ -573,11 +558,11 @@
                                       "The answer is {}", 42);
   \endrst
 */
-template <typename S, typename... Args, typename Char = char_t<S> >
+template <typename S, typename... Args, typename Char = char_t<S>>
 inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
                                       const Args&... args) {
-  return internal::vformat(ts, to_string_view(format_str),
-                           {internal::make_args_checked(format_str, args...)});
+  return vformat(ts, to_string_view(format_str),
+                 {internal::make_args_checked<Args...>(format_str, args...)});
 }
 
 FMT_END_NAMESPACE
diff --git a/include/fmt/compile.h b/include/fmt/compile.h
index 82625bb..f65f5a7 100644
--- a/include/fmt/compile.h
+++ b/include/fmt/compile.h
@@ -14,250 +14,44 @@
 FMT_BEGIN_NAMESPACE
 namespace internal {
 
+// Part of a compiled format string. It can be either literal text or a
+// replacement field.
 template <typename Char> struct format_part {
- public:
-  struct named_argument_id {
-    FMT_CONSTEXPR named_argument_id(internal::string_view_metadata id)
-        : id(id) {}
-    internal::string_view_metadata id;
+  enum class kind { arg_index, arg_name, text, replacement };
+
+  struct replacement {
+    arg_ref<Char> arg_id;
+    dynamic_format_specs<Char> specs;
   };
 
-  struct argument_id {
-    FMT_CONSTEXPR argument_id() : argument_id(0u) {}
-
-    FMT_CONSTEXPR argument_id(unsigned id)
-        : which(which_arg_id::index), val(id) {}
-
-    FMT_CONSTEXPR argument_id(internal::string_view_metadata id)
-        : which(which_arg_id::named_index), val(id) {}
-
-    enum class which_arg_id { index, named_index };
-
-    which_arg_id which;
-
-    union value {
-      FMT_CONSTEXPR value() : index(0u) {}
-      FMT_CONSTEXPR value(unsigned id) : index(id) {}
-      FMT_CONSTEXPR value(internal::string_view_metadata id)
-          : named_index(id) {}
-
-      unsigned index;
-      internal::string_view_metadata named_index;
-    } val;
-  };
-
-  struct specification {
-    FMT_CONSTEXPR specification() : arg_id(0u) {}
-    FMT_CONSTEXPR specification(unsigned id) : arg_id(id) {}
-
-    FMT_CONSTEXPR specification(internal::string_view_metadata id)
-        : arg_id(id) {}
-
-    argument_id arg_id;
-    internal::dynamic_format_specs<Char> parsed_specs;
-  };
-
-  FMT_CONSTEXPR format_part()
-      : which(kind::argument_id), end_of_argument_id(0u), val(0u) {}
-
-  FMT_CONSTEXPR format_part(internal::string_view_metadata text)
-      : which(kind::text), end_of_argument_id(0u), val(text) {}
-
-  FMT_CONSTEXPR format_part(unsigned id)
-      : which(kind::argument_id), end_of_argument_id(0u), val(id) {}
-
-  FMT_CONSTEXPR format_part(named_argument_id arg_id)
-      : which(kind::named_argument_id), end_of_argument_id(0u), val(arg_id) {}
-
-  FMT_CONSTEXPR format_part(specification spec)
-      : which(kind::specification), end_of_argument_id(0u), val(spec) {}
-
-  enum class kind { argument_id, named_argument_id, text, specification };
-
-  kind which;
-  std::size_t end_of_argument_id;
+  kind part_kind;
   union value {
-    FMT_CONSTEXPR value() : arg_id(0u) {}
-    FMT_CONSTEXPR value(unsigned id) : arg_id(id) {}
-    FMT_CONSTEXPR value(named_argument_id named_id)
-        : named_arg_id(named_id.id) {}
-    FMT_CONSTEXPR value(internal::string_view_metadata t) : text(t) {}
-    FMT_CONSTEXPR value(specification s) : spec(s) {}
-    unsigned arg_id;
-    internal::string_view_metadata named_arg_id;
-    internal::string_view_metadata text;
-    specification spec;
+    unsigned arg_index;
+    basic_string_view<Char> str;
+    replacement repl;
+
+    FMT_CONSTEXPR value(unsigned index = 0) : arg_index(index) {}
+    FMT_CONSTEXPR value(basic_string_view<Char> s) : str(s) {}
+    FMT_CONSTEXPR value(replacement r) : repl(r) {}
   } val;
-};
+  // Position past the end of the argument id.
+  const Char* arg_id_end = nullptr;
 
-template <typename Char, typename PartsContainer>
-class format_preparation_handler : public internal::error_handler {
- private:
-  using part = format_part<Char>;
+  FMT_CONSTEXPR format_part(kind k = kind::arg_index, value v = {})
+      : part_kind(k), val(v) {}
 
- public:
-  using iterator = typename basic_string_view<Char>::iterator;
-
-  FMT_CONSTEXPR format_preparation_handler(basic_string_view<Char> format,
-                                           PartsContainer& parts)
-      : parts_(parts), format_(format), parse_context_(format) {}
-
-  FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
-    if (begin == end) return;
-    const auto offset = begin - format_.data();
-    const auto size = end - begin;
-    parts_.push_back(part(string_view_metadata(offset, size)));
+  static FMT_CONSTEXPR format_part make_arg_index(unsigned index) {
+    return format_part(kind::arg_index, index);
   }
-
-  FMT_CONSTEXPR void on_arg_id() {
-    parts_.push_back(part(parse_context_.next_arg_id()));
+  static FMT_CONSTEXPR format_part make_arg_name(basic_string_view<Char> name) {
+    return format_part(kind::arg_name, name);
   }
-
-  FMT_CONSTEXPR void on_arg_id(unsigned id) {
-    parse_context_.check_arg_id(id);
-    parts_.push_back(part(id));
+  static FMT_CONSTEXPR format_part make_text(basic_string_view<Char> text) {
+    return format_part(kind::text, text);
   }
-
-  FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
-    const auto view = string_view_metadata(format_, id);
-    const auto arg_id = typename part::named_argument_id(view);
-    parts_.push_back(part(arg_id));
+  static FMT_CONSTEXPR format_part make_replacement(replacement repl) {
+    return format_part(kind::replacement, repl);
   }
-
-  FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
-    parts_.back().end_of_argument_id = ptr - format_.begin();
-  }
-
-  FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
-                                            const Char* end) {
-    const auto specs_offset = to_unsigned(begin - format_.begin());
-
-    using parse_context = basic_parse_context<Char>;
-    internal::dynamic_format_specs<Char> parsed_specs;
-    dynamic_specs_handler<parse_context> handler(parsed_specs, parse_context_);
-    begin = parse_format_specs(begin, end, handler);
-
-    if (*begin != '}') on_error("missing '}' in format string");
-
-    auto& last_part = parts_.back();
-    auto specs = last_part.which == part::kind::argument_id
-                     ? typename part::specification(last_part.val.arg_id)
-                     : typename part::specification(last_part.val.named_arg_id);
-    specs.parsed_specs = parsed_specs;
-    last_part = part(specs);
-    last_part.end_of_argument_id = specs_offset;
-    return begin;
-  }
-
- private:
-  PartsContainer& parts_;
-  basic_string_view<Char> format_;
-  basic_parse_context<Char> parse_context_;
-};
-
-template <typename Format, typename PreparedPartsProvider, typename... Args>
-class prepared_format {
- public:
-  using char_type = char_t<Format>;
-  using format_part_t = format_part<char_type>;
-
-  constexpr prepared_format(Format f)
-      : format_(std::move(f)), parts_provider_(to_string_view(format_)) {}
-
-  prepared_format() = delete;
-
-  using context = buffer_context<char_type>;
-
-  template <typename Range, typename Context>
-  auto vformat_to(Range out, basic_format_args<Context> args) const ->
-      typename Context::iterator {
-    const auto format_view = internal::to_string_view(format_);
-    basic_parse_context<char_type> parse_ctx(format_view);
-    Context ctx(out.begin(), args);
-
-    const auto& parts = parts_provider_.parts();
-    for (auto part_it = parts.begin(); part_it != parts.end(); ++part_it) {
-      const auto& part = *part_it;
-      const auto& value = part.val;
-
-      switch (part.which) {
-      case format_part_t::kind::text: {
-        const auto text = value.text.to_view(format_view.data());
-        auto output = ctx.out();
-        auto&& it = internal::reserve(output, text.size());
-        it = std::copy_n(text.begin(), text.size(), it);
-        ctx.advance_to(output);
-      } break;
-
-      case format_part_t::kind::argument_id: {
-        advance_parse_context_to_specification(parse_ctx, part);
-        format_arg<Range>(parse_ctx, ctx, value.arg_id);
-      } break;
-
-      case format_part_t::kind::named_argument_id: {
-        advance_parse_context_to_specification(parse_ctx, part);
-        const auto named_arg_id =
-            value.named_arg_id.to_view(format_view.data());
-        format_arg<Range>(parse_ctx, ctx, named_arg_id);
-      } break;
-      case format_part_t::kind::specification: {
-        const auto& arg_id_value = value.spec.arg_id.val;
-        const auto arg = value.spec.arg_id.which ==
-                                 format_part_t::argument_id::which_arg_id::index
-                             ? ctx.arg(arg_id_value.index)
-                             : ctx.arg(arg_id_value.named_index.to_view(
-                                   to_string_view(format_).data()));
-
-        auto specs = value.spec.parsed_specs;
-
-        handle_dynamic_spec<internal::width_checker>(
-            specs.width, specs.width_ref, ctx, format_view.begin());
-        handle_dynamic_spec<internal::precision_checker>(
-            specs.precision, specs.precision_ref, ctx, format_view.begin());
-
-        check_prepared_specs(specs, arg.type());
-        advance_parse_context_to_specification(parse_ctx, part);
-        ctx.advance_to(
-            visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg));
-      } break;
-      }
-    }
-
-    return ctx.out();
-  }
-
- private:
-  void advance_parse_context_to_specification(
-      basic_parse_context<char_type>& parse_ctx,
-      const format_part_t& part) const {
-    const auto view = to_string_view(format_);
-    const auto specification_begin = view.data() + part.end_of_argument_id;
-    advance_to(parse_ctx, specification_begin);
-  }
-
-  template <typename Range, typename Context, typename Id>
-  void format_arg(basic_parse_context<char_type>& parse_ctx, Context& ctx,
-                  Id arg_id) const {
-    parse_ctx.check_arg_id(arg_id);
-    const auto stopped_at =
-        visit_format_arg(arg_formatter<Range>(ctx), ctx.arg(arg_id));
-    ctx.advance_to(stopped_at);
-  }
-
-  template <typename Char>
-  void check_prepared_specs(const basic_format_specs<Char>& specs,
-                            internal::type arg_type) const {
-    internal::error_handler h;
-    numeric_specs_checker<internal::error_handler> checker(h, arg_type);
-    if (specs.align == align::numeric) checker.require_numeric_argument();
-    if (specs.sign != sign::none) checker.check_sign();
-    if (specs.alt) checker.require_numeric_argument();
-    if (specs.precision >= 0) checker.check_precision();
-  }
-
- private:
-  Format format_;
-  PreparedPartsProvider parts_provider_;
 };
 
 template <typename Char> struct part_counter {
@@ -276,13 +70,13 @@
   FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
                                             const Char* end) {
     // Find the matching brace.
-    unsigned braces_counter = 0;
+    unsigned brace_counter = 0;
     for (; begin != end; ++begin) {
       if (*begin == '{') {
-        ++braces_counter;
+        ++brace_counter;
       } else if (*begin == '}') {
-        if (braces_counter == 0u) break;
-        --braces_counter;
+        if (brace_counter == 0u) break;
+        --brace_counter;
       }
     }
     return begin;
@@ -291,156 +85,486 @@
   FMT_CONSTEXPR void on_error(const char*) {}
 };
 
-template <typename Format> class compiletime_prepared_parts_type_provider {
- private:
-  using char_type = char_t<Format>;
+// Counts the number of parts in a format string.
+template <typename Char>
+FMT_CONSTEXPR unsigned count_parts(basic_string_view<Char> format_str) {
+  part_counter<Char> counter;
+  parse_format_string<true>(format_str, counter);
+  return counter.num_parts;
+}
 
-  static FMT_CONSTEXPR unsigned count_parts() {
-    FMT_CONSTEXPR_DECL const auto text = to_string_view(Format{});
-    part_counter<char_type> counter;
-    internal::parse_format_string</*IS_CONSTEXPR=*/true>(text, counter);
-    return counter.num_parts;
+template <typename Char, typename PartHandler>
+class format_string_compiler : public error_handler {
+ private:
+  using part = format_part<Char>;
+
+  PartHandler handler_;
+  part part_;
+  basic_string_view<Char> format_str_;
+  basic_format_parse_context<Char> parse_context_;
+
+ public:
+  FMT_CONSTEXPR format_string_compiler(basic_string_view<Char> format_str,
+                                       PartHandler handler)
+      : handler_(handler),
+        format_str_(format_str),
+        parse_context_(format_str) {}
+
+  FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
+    if (begin != end)
+      handler_(part::make_text({begin, to_unsigned(end - begin)}));
   }
 
-// Workaround for old compilers. Compiletime parts preparation will not be
-// performed with them anyway.
+  FMT_CONSTEXPR void on_arg_id() {
+    part_ = part::make_arg_index(parse_context_.next_arg_id());
+  }
+
+  FMT_CONSTEXPR void on_arg_id(unsigned id) {
+    parse_context_.check_arg_id(id);
+    part_ = part::make_arg_index(id);
+  }
+
+  FMT_CONSTEXPR void on_arg_id(basic_string_view<Char> id) {
+    part_ = part::make_arg_name(id);
+  }
+
+  FMT_CONSTEXPR void on_replacement_field(const Char* ptr) {
+    part_.arg_id_end = ptr;
+    handler_(part_);
+  }
+
+  FMT_CONSTEXPR const Char* on_format_specs(const Char* begin,
+                                            const Char* end) {
+    auto repl = typename part::replacement();
+    dynamic_specs_handler<basic_format_parse_context<Char>> handler(
+        repl.specs, parse_context_);
+    auto it = parse_format_specs(begin, end, handler);
+    if (*it != '}') on_error("missing '}' in format string");
+    repl.arg_id = part_.part_kind == part::kind::arg_index
+                      ? arg_ref<Char>(part_.val.arg_index)
+                      : arg_ref<Char>(part_.val.str);
+    auto part = part::make_replacement(repl);
+    part.arg_id_end = begin;
+    handler_(part);
+    return it;
+  }
+};
+
+// Compiles a format string and invokes handler(part) for each parsed part.
+template <bool IS_CONSTEXPR, typename Char, typename PartHandler>
+FMT_CONSTEXPR void compile_format_string(basic_string_view<Char> format_str,
+                                         PartHandler handler) {
+  parse_format_string<IS_CONSTEXPR>(
+      format_str,
+      format_string_compiler<Char, PartHandler>(format_str, handler));
+}
+
+template <typename Range, typename Context, typename Id>
+void format_arg(
+    basic_format_parse_context<typename Range::value_type>& parse_ctx,
+    Context& ctx, Id arg_id) {
+  ctx.advance_to(
+      visit_format_arg(arg_formatter<Range>(ctx, &parse_ctx), ctx.arg(arg_id)));
+}
+
+// vformat_to is defined in a subnamespace to prevent ADL.
+namespace cf {
+template <typename Context, typename Range, typename CompiledFormat>
+auto vformat_to(Range out, CompiledFormat& cf, basic_format_args<Context> args)
+    -> typename Context::iterator {
+  using char_type = typename Context::char_type;
+  basic_format_parse_context<char_type> parse_ctx(
+      to_string_view(cf.format_str_));
+  Context ctx(out.begin(), args);
+
+  const auto& parts = cf.parts();
+  for (auto part_it = std::begin(parts); part_it != std::end(parts);
+       ++part_it) {
+    const auto& part = *part_it;
+    const auto& value = part.val;
+
+    using format_part_t = format_part<char_type>;
+    switch (part.part_kind) {
+    case format_part_t::kind::text: {
+      const auto text = value.str;
+      auto output = ctx.out();
+      auto&& it = reserve(output, text.size());
+      it = std::copy_n(text.begin(), text.size(), it);
+      ctx.advance_to(output);
+      break;
+    }
+
+    case format_part_t::kind::arg_index:
+      advance_to(parse_ctx, part.arg_id_end);
+      internal::format_arg<Range>(parse_ctx, ctx, value.arg_index);
+      break;
+
+    case format_part_t::kind::arg_name:
+      advance_to(parse_ctx, part.arg_id_end);
+      internal::format_arg<Range>(parse_ctx, ctx, value.str);
+      break;
+
+    case format_part_t::kind::replacement: {
+      const auto& arg_id_value = value.repl.arg_id.val;
+      const auto arg = value.repl.arg_id.kind == arg_id_kind::index
+                           ? ctx.arg(arg_id_value.index)
+                           : ctx.arg(arg_id_value.name);
+
+      auto specs = value.repl.specs;
+
+      handle_dynamic_spec<width_checker>(specs.width, specs.width_ref, ctx);
+      handle_dynamic_spec<precision_checker>(specs.precision,
+                                             specs.precision_ref, ctx);
+
+      error_handler h;
+      numeric_specs_checker<error_handler> checker(h, arg.type());
+      if (specs.align == align::numeric) checker.require_numeric_argument();
+      if (specs.sign != sign::none) checker.check_sign();
+      if (specs.alt) checker.require_numeric_argument();
+      if (specs.precision >= 0) checker.check_precision();
+
+      advance_to(parse_ctx, part.arg_id_end);
+      ctx.advance_to(
+          visit_format_arg(arg_formatter<Range>(ctx, nullptr, &specs), arg));
+      break;
+    }
+    }
+  }
+  return ctx.out();
+}
+}  // namespace cf
+
+struct basic_compiled_format {};
+
+template <typename S, typename = void>
+struct compiled_format_base : basic_compiled_format {
+  using char_type = char_t<S>;
+  using parts_container = std::vector<internal::format_part<char_type>>;
+
+  parts_container compiled_parts;
+
+  explicit compiled_format_base(basic_string_view<char_type> format_str) {
+    compile_format_string<false>(format_str,
+                                 [this](const format_part<char_type>& part) {
+                                   compiled_parts.push_back(part);
+                                 });
+  }
+
+  const parts_container& parts() const { return compiled_parts; }
+};
+
+template <typename Char, unsigned N> struct format_part_array {
+  format_part<Char> data[N] = {};
+  FMT_CONSTEXPR format_part_array() = default;
+};
+
+template <typename Char, unsigned N>
+FMT_CONSTEXPR format_part_array<Char, N> compile_to_parts(
+    basic_string_view<Char> format_str) {
+  format_part_array<Char, N> parts;
+  unsigned counter = 0;
+  // This is not a lambda for compatibility with older compilers.
+  struct {
+    format_part<Char>* parts;
+    unsigned* counter;
+    FMT_CONSTEXPR void operator()(const format_part<Char>& part) {
+      parts[(*counter)++] = part;
+    }
+  } collector{parts.data, &counter};
+  compile_format_string<true>(format_str, collector);
+  if (counter < N) {
+    parts.data[counter] =
+        format_part<Char>::make_text(basic_string_view<Char>());
+  }
+  return parts;
+}
+
+template <typename T> constexpr const T& constexpr_max(const T& a, const T& b) {
+  return (a < b) ? b : a;
+}
+
+template <typename S>
+struct compiled_format_base<S, enable_if_t<is_compile_string<S>::value>>
+    : basic_compiled_format {
+  using char_type = char_t<S>;
+
+  FMT_CONSTEXPR explicit compiled_format_base(basic_string_view<char_type>) {}
+
+// Workaround for old compilers. Format string compilation will not be
+// performed there anyway.
 #if FMT_USE_CONSTEXPR
-  static FMT_CONSTEXPR_DECL const unsigned number_of_format_parts =
-      compiletime_prepared_parts_type_provider::count_parts();
+  static FMT_CONSTEXPR_DECL const unsigned num_format_parts =
+      constexpr_max(count_parts(to_string_view(S())), 1u);
 #else
-  static const unsigned number_of_format_parts = 0u;
+  static const unsigned num_format_parts = 1;
 #endif
 
- public:
-  template <unsigned N> struct format_parts_array {
-    using value_type = format_part<char_type>;
+  using parts_container = format_part<char_type>[num_format_parts];
 
-    FMT_CONSTEXPR format_parts_array() : arr{} {}
-
-    FMT_CONSTEXPR value_type& operator[](unsigned ind) { return arr[ind]; }
-
-    FMT_CONSTEXPR const value_type* begin() const { return arr; }
-    FMT_CONSTEXPR const value_type* end() const { return begin() + N; }
-
-   private:
-    value_type arr[N];
-  };
-
-  struct empty {
-    // Parts preparator will search for it
-    using value_type = format_part<char_type>;
-  };
-
-  using type = conditional_t<number_of_format_parts != 0,
-                             format_parts_array<number_of_format_parts>, empty>;
-};
-
-template <typename Parts> class compiletime_prepared_parts_collector {
- private:
-  using format_part = typename Parts::value_type;
-
- public:
-  FMT_CONSTEXPR explicit compiletime_prepared_parts_collector(Parts& parts)
-      : parts_{parts}, counter_{0u} {}
-
-  FMT_CONSTEXPR void push_back(format_part part) { parts_[counter_++] = part; }
-
-  FMT_CONSTEXPR format_part& back() { return parts_[counter_ - 1]; }
-
- private:
-  Parts& parts_;
-  unsigned counter_;
-};
-
-template <typename PartsContainer, typename Char>
-FMT_CONSTEXPR PartsContainer prepare_parts(basic_string_view<Char> format) {
-  PartsContainer parts;
-  internal::parse_format_string</*IS_CONSTEXPR=*/false>(
-      format, format_preparation_handler<Char, PartsContainer>(format, parts));
-  return parts;
-}
-
-template <typename PartsContainer, typename Char>
-FMT_CONSTEXPR PartsContainer
-prepare_compiletime_parts(basic_string_view<Char> format) {
-  using collector = compiletime_prepared_parts_collector<PartsContainer>;
-
-  PartsContainer parts;
-  collector c(parts);
-  internal::parse_format_string</*IS_CONSTEXPR=*/true>(
-      format, format_preparation_handler<Char, collector>(format, c));
-  return parts;
-}
-
-template <typename PartsContainer> class runtime_parts_provider {
- public:
-  runtime_parts_provider() = delete;
-  template <typename Char>
-  runtime_parts_provider(basic_string_view<Char> format)
-      : parts_(prepare_parts<PartsContainer>(format)) {}
-
-  const PartsContainer& parts() const { return parts_; }
-
- private:
-  PartsContainer parts_;
-};
-
-template <typename Format, typename PartsContainer>
-struct compiletime_parts_provider {
-  compiletime_parts_provider() = delete;
-  template <typename Char>
-  FMT_CONSTEXPR compiletime_parts_provider(basic_string_view<Char>) {}
-
-  const PartsContainer& parts() const {
-    static FMT_CONSTEXPR_DECL const PartsContainer prepared_parts =
-        prepare_compiletime_parts<PartsContainer>(
-            internal::to_string_view(Format{}));
-
-    return prepared_parts;
+  const parts_container& parts() const {
+    static FMT_CONSTEXPR_DECL const auto compiled_parts =
+        compile_to_parts<char_type, num_format_parts>(
+            internal::to_string_view(S()));
+    return compiled_parts.data;
   }
 };
+
+template <typename S, typename... Args>
+class compiled_format : private compiled_format_base<S> {
+ public:
+  using typename compiled_format_base<S>::char_type;
+
+ private:
+  basic_string_view<char_type> format_str_;
+
+  template <typename Context, typename Range, typename CompiledFormat>
+  friend auto cf::vformat_to(Range out, CompiledFormat& cf,
+                             basic_format_args<Context> args) ->
+      typename Context::iterator;
+
+ public:
+  compiled_format() = delete;
+  explicit constexpr compiled_format(basic_string_view<char_type> format_str)
+      : compiled_format_base<S>(format_str), format_str_(format_str) {}
+};
+
+#ifdef __cpp_if_constexpr
+template <typename... Args> struct type_list {};
+
+// Returns a reference to the argument at index N from [first, rest...].
+template <int N, typename T, typename... Args>
+constexpr const auto& get(const T& first, const Args&... rest) {
+  static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
+  if constexpr (N == 0)
+    return first;
+  else
+    return get<N - 1>(rest...);
+}
+
+template <int N, typename> struct get_type_impl;
+
+template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
+  using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
+};
+
+template <int N, typename T>
+using get_type = typename get_type_impl<N, T>::type;
+
+template <typename Char> struct text {
+  basic_string_view<Char> data;
+  using char_type = Char;
+
+  template <typename OutputIt, typename... Args>
+  OutputIt format(OutputIt out, const Args&...) const {
+    // TODO: reserve
+    return copy_str<Char>(data.begin(), data.end(), out);
+  }
+};
+
+template <typename Char>
+constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
+                               size_t size) {
+  return {{&s[pos], size}};
+}
+
+template <typename Char, typename OutputIt, typename T,
+          std::enable_if_t<std::is_integral_v<T>, int> = 0>
+OutputIt format_default(OutputIt out, T value) {
+  // TODO: reserve
+  format_int fi(value);
+  return std::copy(fi.data(), fi.data() + fi.size(), out);
+}
+
+template <typename Char, typename OutputIt>
+OutputIt format_default(OutputIt out, double value) {
+  writer w(out);
+  w.write(value);
+  return w.out();
+}
+
+template <typename Char, typename OutputIt>
+OutputIt format_default(OutputIt out, Char value) {
+  *out++ = value;
+  return out;
+}
+
+template <typename Char, typename OutputIt>
+OutputIt format_default(OutputIt out, const Char* value) {
+  auto length = std::char_traits<Char>::length(value);
+  return copy_str<Char>(value, value + length, out);
+}
+
+// A replacement field that refers to argument N.
+template <typename Char, typename T, int N> struct field {
+  using char_type = Char;
+
+  template <typename OutputIt, typename... Args>
+  OutputIt format(OutputIt out, const Args&... args) const {
+    // This ensures that the argument type is convertile to `const T&`.
+    const T& arg = get<N>(args...);
+    return format_default<Char>(out, arg);
+  }
+};
+
+template <typename L, typename R> struct concat {
+  L lhs;
+  R rhs;
+  using char_type = typename L::char_type;
+
+  template <typename OutputIt, typename... Args>
+  OutputIt format(OutputIt out, const Args&... args) const {
+    out = lhs.format(out, args...);
+    return rhs.format(out, args...);
+  }
+};
+
+template <typename L, typename R>
+constexpr concat<L, R> make_concat(L lhs, R rhs) {
+  return {lhs, rhs};
+}
+
+struct unknown_format {};
+
+template <typename Char>
+constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
+  for (size_t size = str.size(); pos != size; ++pos) {
+    if (str[pos] == '{' || str[pos] == '}') break;
+  }
+  return pos;
+}
+
+template <typename Args, size_t POS, int ID, typename S>
+constexpr auto compile_format_string(S format_str);
+
+template <typename Args, size_t POS, int ID, typename T, typename S>
+constexpr auto parse_tail(T head, S format_str) {
+  if constexpr (POS != to_string_view(format_str).size()) {
+    constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
+    if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
+                               unknown_format>())
+      return tail;
+    else
+      return make_concat(head, tail);
+  } else {
+    return head;
+  }
+}
+
+// Compiles a non-empty format string and returns the compiled representation
+// or unknown_format() on unrecognized input.
+template <typename Args, size_t POS, int ID, typename S>
+constexpr auto compile_format_string(S format_str) {
+  using char_type = typename S::char_type;
+  constexpr basic_string_view<char_type> str = format_str;
+  if constexpr (str[POS] == '{') {
+    if (POS + 1 == str.size())
+      throw format_error("unmatched '{' in format string");
+    if constexpr (str[POS + 1] == '{') {
+      return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
+    } else if constexpr (str[POS + 1] == '}') {
+      using type = get_type<ID, Args>;
+      if constexpr (std::is_same<type, int>::value) {
+        return parse_tail<Args, POS + 2, ID + 1>(field<char_type, type, ID>(),
+                                                 format_str);
+      } else {
+        return unknown_format();
+      }
+    } else {
+      return unknown_format();
+    }
+  } else if constexpr (str[POS] == '}') {
+    if (POS + 1 == str.size())
+      throw format_error("unmatched '}' in format string");
+    return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
+  } else {
+    constexpr auto end = parse_text(str, POS + 1);
+    return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
+                                     format_str);
+  }
+}
+#endif  // __cpp_if_constexpr
 }  // namespace internal
 
 #if FMT_USE_CONSTEXPR
+#  ifdef __cpp_if_constexpr
 template <typename... Args, typename S,
           FMT_ENABLE_IF(is_compile_string<S>::value)>
-FMT_CONSTEXPR auto compile(S format_str) -> internal::prepared_format<
-    S,
-    internal::compiletime_parts_provider<
-        S,
-        typename internal::compiletime_prepared_parts_type_provider<S>::type>,
-    Args...> {
-  return format_str;
-}
-#endif
-
-template <typename... Args, typename Char, size_t N>
-auto compile(const Char (&format_str)[N]) -> internal::prepared_format<
-    std::basic_string<Char>,
-    internal::runtime_parts_provider<std::vector<internal::format_part<Char>>>,
-    Args...> {
-  return std::basic_string<Char>(format_str, N - 1);
+constexpr auto compile(S format_str) {
+  constexpr basic_string_view<typename S::char_type> str = format_str;
+  if constexpr (str.size() == 0) {
+    return internal::make_text(str, 0, 0);
+  } else {
+    constexpr auto result =
+        internal::compile_format_string<internal::type_list<Args...>, 0, 0>(
+            format_str);
+    if constexpr (std::is_same<remove_cvref_t<decltype(result)>,
+                               internal::unknown_format>()) {
+      return internal::compiled_format<S, Args...>(to_string_view(format_str));
+    } else {
+      return result;
+    }
+  }
 }
 
 template <typename CompiledFormat, typename... Args,
-          typename Char = typename CompiledFormat::char_type>
+          typename Char = typename CompiledFormat::char_type,
+          FMT_ENABLE_IF(!std::is_base_of<internal::basic_compiled_format,
+                                         CompiledFormat>::value)>
 std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
   basic_memory_buffer<Char> buffer;
-  using range = internal::buffer_range<Char>;
+  using range = buffer_range<Char>;
   using context = buffer_context<Char>;
-  cf.template vformat_to<range, context>(range(buffer),
-                                         {make_format_args<context>(args...)});
+  cf.format(std::back_inserter(buffer), args...);
   return to_string(buffer);
 }
 
-template <typename OutputIt, typename CompiledFormat, typename... Args>
+template <typename OutputIt, typename CompiledFormat, typename... Args,
+          FMT_ENABLE_IF(!std::is_base_of<internal::basic_compiled_format,
+                                         CompiledFormat>::value)>
+OutputIt format_to(OutputIt out, const CompiledFormat& cf,
+                   const Args&... args) {
+  return cf.format(out, args...);
+}
+#  else
+template <typename... Args, typename S,
+          FMT_ENABLE_IF(is_compile_string<S>::value)>
+constexpr auto compile(S format_str) -> internal::compiled_format<S, Args...> {
+  return internal::compiled_format<S, Args...>(to_string_view(format_str));
+}
+#  endif  // __cpp_if_constexpr
+#endif    // FMT_USE_CONSTEXPR
+
+// Compiles the format string which must be a string literal.
+template <typename... Args, typename Char, size_t N>
+auto compile(const Char (&format_str)[N])
+    -> internal::compiled_format<const Char*, Args...> {
+  return internal::compiled_format<const Char*, Args...>(
+      basic_string_view<Char>(format_str, N - 1));
+}
+
+template <typename CompiledFormat, typename... Args,
+          typename Char = typename CompiledFormat::char_type,
+          FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
+                                        CompiledFormat>::value)>
+std::basic_string<Char> format(const CompiledFormat& cf, const Args&... args) {
+  basic_memory_buffer<Char> buffer;
+  using range = buffer_range<Char>;
+  using context = buffer_context<Char>;
+  internal::cf::vformat_to<context>(range(buffer), cf,
+                                    {make_format_args<context>(args...)});
+  return to_string(buffer);
+}
+
+template <typename OutputIt, typename CompiledFormat, typename... Args,
+          FMT_ENABLE_IF(std::is_base_of<internal::basic_compiled_format,
+                                        CompiledFormat>::value)>
 OutputIt format_to(OutputIt out, const CompiledFormat& cf,
                    const Args&... args) {
   using char_type = typename CompiledFormat::char_type;
   using range = internal::output_range<OutputIt, char_type>;
   using context = format_context_t<OutputIt, char_type>;
-  return cf.template vformat_to<range, context>(
-      range(out), {make_format_args<context>(args...)});
+  return internal::cf::vformat_to<context>(
+      range(out), cf, {make_format_args<context>(args...)});
 }
 
 template <typename OutputIt, typename CompiledFormat, typename... Args,
@@ -455,10 +579,7 @@
 
 template <typename CompiledFormat, typename... Args>
 std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) {
-  return fmt::format_to(
-             internal::counting_iterator<typename CompiledFormat::char_type>(),
-             cf, args...)
-      .count();
+  return format_to(internal::counting_iterator(), cf, args...).count();
 }
 
 FMT_END_NAMESPACE
diff --git a/include/fmt/core.h b/include/fmt/core.h
index bcce2f5..13d74ba 100644
--- a/include/fmt/core.h
+++ b/include/fmt/core.h
@@ -8,7 +8,6 @@
 #ifndef FMT_CORE_H_
 #define FMT_CORE_H_
 
-#include <cassert>
 #include <cstdio>  // std::FILE
 #include <cstring>
 #include <iterator>
@@ -16,7 +15,7 @@
 #include <type_traits>
 
 // The fmt library version in the form major * 10000 + minor * 100 + patch.
-#define FMT_VERSION 60000
+#define FMT_VERSION 60101
 
 #ifdef __has_feature
 #  define FMT_HAS_FEATURE(x) __has_feature(x)
@@ -49,6 +48,12 @@
 #  define FMT_HAS_GXX_CXX11 0
 #endif
 
+#ifdef __NVCC__
+#  define FMT_NVCC __NVCC__
+#else
+#  define FMT_NVCC 0
+#endif
+
 #ifdef _MSC_VER
 #  define FMT_MSC_VER _MSC_VER
 #else
@@ -60,7 +65,8 @@
 #ifndef FMT_USE_CONSTEXPR
 #  define FMT_USE_CONSTEXPR                                           \
     (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \
-     (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L))
+     (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) &&           \
+        !FMT_NVCC
 #endif
 #if FMT_USE_CONSTEXPR
 #  define FMT_CONSTEXPR constexpr
@@ -133,6 +139,13 @@
 #  endif
 #endif
 
+// Workaround broken [[deprecated]] in the Intel compiler and NVCC.
+#if defined(__INTEL_COMPILER) || FMT_NVCC
+#  define FMT_DEPRECATED_ALIAS
+#else
+#  define FMT_DEPRECATED_ALIAS FMT_DEPRECATED
+#endif
+
 #ifndef FMT_BEGIN_NAMESPACE
 #  if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \
       FMT_MSC_VER >= 1900
@@ -173,10 +186,6 @@
 #  define FMT_EXTERN
 #endif
 
-#ifndef FMT_ASSERT
-#  define FMT_ASSERT(condition, message) assert((condition) && message)
-#endif
-
 // libc++ supports string_view in pre-c++17.
 #if (FMT_HAS_INCLUDE(<string_view>) &&                       \
      (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \
@@ -200,6 +209,8 @@
 using remove_reference_t = typename std::remove_reference<T>::type;
 template <typename T>
 using remove_const_t = typename std::remove_const<T>::type;
+template <typename T>
+using remove_cvref_t = typename std::remove_cv<remove_reference_t<T>>::type;
 
 struct monostate {};
 
@@ -213,6 +224,19 @@
 // A workaround for gcc 4.8 to make void_t work in a SFINAE context.
 template <typename... Ts> struct void_t_impl { using type = void; };
 
+FMT_API void assert_fail(const char* file, int line, const char* message);
+
+#ifndef FMT_ASSERT
+#  ifdef NDEBUG
+#    define FMT_ASSERT(condition, message)
+#  else
+#    define FMT_ASSERT(condition, message) \
+      ((condition)                         \
+           ? void()                        \
+           : fmt::internal::assert_fail(__FILE__, __LINE__, (message)))
+#  endif
+#endif
+
 #if defined(FMT_USE_STRING_VIEW)
 template <typename Char> using std_string_view = std::basic_string_view<Char>;
 #elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW)
@@ -222,7 +246,21 @@
 template <typename T> struct std_string_view {};
 #endif
 
-// Casts nonnegative integer to unsigned.
+#ifdef FMT_USE_INT128
+// Do nothing.
+#elif defined(__SIZEOF_INT128__)
+#  define FMT_USE_INT128 1
+using int128_t = __int128_t;
+using uint128_t = __uint128_t;
+#else
+#  define FMT_USE_INT128 0
+#endif
+#if !FMT_USE_INT128
+struct int128_t {};
+struct uint128_t {};
+#endif
+
+// Casts a nonnegative integer to unsigned.
 template <typename Int>
 FMT_CONSTEXPR typename std::make_unsigned<Int>::type to_unsigned(Int value) {
   FMT_ASSERT(value >= 0, "negative value");
@@ -266,10 +304,11 @@
       : data_(s), size_(std::char_traits<Char>::length(s)) {}
 
   /** Constructs a string reference from a ``std::basic_string`` object. */
-  template <typename Alloc>
-  FMT_CONSTEXPR basic_string_view(const std::basic_string<Char, Alloc>& s)
-      FMT_NOEXCEPT : data_(s.data()),
-                     size_(s.size()) {}
+  template <typename Traits, typename Alloc>
+  FMT_CONSTEXPR basic_string_view(
+      const std::basic_string<Char, Traits, Alloc>& s) FMT_NOEXCEPT
+      : data_(s.data()),
+        size_(s.size()) {}
 
   template <
       typename S,
@@ -286,6 +325,8 @@
   FMT_CONSTEXPR iterator begin() const { return data_; }
   FMT_CONSTEXPR iterator end() const { return data_ + size_; }
 
+  FMT_CONSTEXPR const Char& operator[](size_t pos) const { return data_[pos]; }
+
   FMT_CONSTEXPR void remove_prefix(size_t n) {
     data_ += n;
     size_ -= n;
@@ -357,10 +398,10 @@
   return s;
 }
 
-template <typename Char, typename Traits, typename Allocator>
+template <typename Char, typename Traits, typename Alloc>
 inline basic_string_view<Char> to_string_view(
-    const std::basic_string<Char, Traits, Allocator>& s) {
-  return {s.data(), s.size()};
+    const std::basic_string<Char, Traits, Alloc>& s) {
+  return s;
 }
 
 template <typename Char>
@@ -405,8 +446,8 @@
 };
 
 struct error_handler {
-  FMT_CONSTEXPR error_handler() {}
-  FMT_CONSTEXPR error_handler(const error_handler&) {}
+  FMT_CONSTEXPR error_handler() = default;
+  FMT_CONSTEXPR error_handler(const error_handler&) = default;
 
   // This function is intentionally not constexpr to give a compile-time error.
   FMT_NORETURN FMT_API void on_error(const char* message);
@@ -416,10 +457,24 @@
 /** String's character type. */
 template <typename S> using char_t = typename internal::char_t_impl<S>::type;
 
-// Parsing context consisting of a format string range being parsed and an
-// argument counter for automatic indexing.
+/**
+  \rst
+  Parsing context consisting of a format string range being parsed and an
+  argument counter for automatic indexing.
+
+  You can use one of the following type aliases for common character types:
+
+  +-----------------------+-------------------------------------+
+  | Type                  | Definition                          |
+  +=======================+=====================================+
+  | format_parse_context  | basic_format_parse_context<char>    |
+  +-----------------------+-------------------------------------+
+  | wformat_parse_context | basic_format_parse_context<wchar_t> |
+  +-----------------------+-------------------------------------+
+  \endrst
+ */
 template <typename Char, typename ErrorHandler = internal::error_handler>
-class basic_parse_context : private ErrorHandler {
+class basic_format_parse_context : private ErrorHandler {
  private:
   basic_string_view<Char> format_str_;
   int next_arg_id_;
@@ -428,38 +483,47 @@
   using char_type = Char;
   using iterator = typename basic_string_view<Char>::iterator;
 
-  explicit FMT_CONSTEXPR basic_parse_context(basic_string_view<Char> format_str,
-                                             ErrorHandler eh = ErrorHandler())
+  explicit FMT_CONSTEXPR basic_format_parse_context(
+      basic_string_view<Char> format_str, ErrorHandler eh = ErrorHandler())
       : ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {}
 
-  // Returns an iterator to the beginning of the format string range being
-  // parsed.
+  /**
+    Returns an iterator to the beginning of the format string range being
+    parsed.
+   */
   FMT_CONSTEXPR iterator begin() const FMT_NOEXCEPT {
     return format_str_.begin();
   }
 
-  // Returns an iterator past the end of the format string range being parsed.
+  /**
+    Returns an iterator past the end of the format string range being parsed.
+   */
   FMT_CONSTEXPR iterator end() const FMT_NOEXCEPT { return format_str_.end(); }
 
-  // Advances the begin iterator to ``it``.
+  /** Advances the begin iterator to ``it``. */
   FMT_CONSTEXPR void advance_to(iterator it) {
     format_str_.remove_prefix(internal::to_unsigned(it - begin()));
   }
 
-  // Returns the next argument index.
+  /**
+    Reports an error if using the manual argument indexing; otherwise returns
+    the next argument index and switches to the automatic indexing.
+   */
   FMT_CONSTEXPR int next_arg_id() {
     if (next_arg_id_ >= 0) return next_arg_id_++;
     on_error("cannot switch from manual to automatic argument indexing");
     return 0;
   }
 
-  FMT_CONSTEXPR bool check_arg_id(int) {
-    if (next_arg_id_ > 0) {
+  /**
+    Reports an error if using the automatic argument indexing; otherwise
+    switches to the manual indexing.
+   */
+  FMT_CONSTEXPR void check_arg_id(int) {
+    if (next_arg_id_ > 0)
       on_error("cannot switch from automatic to manual argument indexing");
-      return false;
-    }
-    next_arg_id_ = -1;
-    return true;
+    else
+      next_arg_id_ = -1;
   }
 
   FMT_CONSTEXPR void check_arg_id(basic_string_view<Char>) {}
@@ -471,11 +535,14 @@
   FMT_CONSTEXPR ErrorHandler error_handler() const { return *this; }
 };
 
-using format_parse_context = basic_parse_context<char>;
-using wformat_parse_context = basic_parse_context<wchar_t>;
+using format_parse_context = basic_format_parse_context<char>;
+using wformat_parse_context = basic_format_parse_context<wchar_t>;
 
-using parse_context FMT_DEPRECATED = basic_parse_context<char>;
-using wparse_context FMT_DEPRECATED = basic_parse_context<wchar_t>;
+template <typename Char, typename ErrorHandler = internal::error_handler>
+using basic_parse_context FMT_DEPRECATED_ALIAS =
+    basic_format_parse_context<Char, ErrorHandler>;
+using parse_context FMT_DEPRECATED_ALIAS = basic_format_parse_context<char>;
+using wparse_context FMT_DEPRECATED_ALIAS = basic_format_parse_context<wchar_t>;
 
 template <typename Context> class basic_format_arg;
 template <typename Context> class basic_format_args;
@@ -492,20 +559,17 @@
     : bool_constant<!std::is_arithmetic<T>::value &&
                     std::is_convertible<T, int>::value> {};
 
-namespace internal {
-
 // Specifies if T has an enabled formatter specialization. A type can be
 // formattable even if it doesn't have a formatter e.g. via a conversion.
 template <typename T, typename Context>
 using has_formatter =
     std::is_constructible<typename Context::template formatter_type<T>>;
 
+namespace internal {
+
 /** A contiguous memory buffer with an optional growing ability. */
 template <typename T> class buffer {
  private:
-  buffer(const buffer&) = delete;
-  void operator=(const buffer&) = delete;
-
   T* ptr_;
   std::size_t size_;
   std::size_t capacity_;
@@ -532,7 +596,9 @@
   using value_type = T;
   using const_reference = const T&;
 
-  virtual ~buffer() {}
+  buffer(const buffer&) = delete;
+  void operator=(const buffer&) = delete;
+  virtual ~buffer() = default;
 
   T* begin() FMT_NOEXCEPT { return ptr_; }
   T* end() FMT_NOEXCEPT { return ptr_ + size_; }
@@ -626,10 +692,13 @@
   uint_type,
   long_long_type,
   ulong_long_type,
+  int128_type,
+  uint128_type,
   bool_type,
   char_type,
   last_integer_type = char_type,
   // followed by floating-point types.
+  float_type,
   double_type,
   long_double_type,
   last_numeric_type = long_double_type,
@@ -652,20 +721,23 @@
 FMT_TYPE_CONSTANT(unsigned, uint_type);
 FMT_TYPE_CONSTANT(long long, long_long_type);
 FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type);
+FMT_TYPE_CONSTANT(int128_t, int128_type);
+FMT_TYPE_CONSTANT(uint128_t, uint128_type);
 FMT_TYPE_CONSTANT(bool, bool_type);
 FMT_TYPE_CONSTANT(Char, char_type);
+FMT_TYPE_CONSTANT(float, float_type);
 FMT_TYPE_CONSTANT(double, double_type);
 FMT_TYPE_CONSTANT(long double, long_double_type);
 FMT_TYPE_CONSTANT(const Char*, cstring_type);
 FMT_TYPE_CONSTANT(basic_string_view<Char>, string_type);
 FMT_TYPE_CONSTANT(const void*, pointer_type);
 
-FMT_CONSTEXPR bool is_integral(type t) {
+FMT_CONSTEXPR bool is_integral_type(type t) {
   FMT_ASSERT(t != named_arg_type, "invalid argument type");
   return t > none_type && t <= last_integer_type;
 }
 
-FMT_CONSTEXPR bool is_arithmetic(type t) {
+FMT_CONSTEXPR bool is_arithmetic_type(type t) {
   FMT_ASSERT(t != named_arg_type, "invalid argument type");
   return t > none_type && t <= last_numeric_type;
 }
@@ -676,7 +748,7 @@
 };
 
 template <typename Context> struct custom_value {
-  using parse_context = basic_parse_context<typename Context::char_type>;
+  using parse_context = basic_format_parse_context<typename Context::char_type>;
   const void* value;
   void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx);
 };
@@ -691,8 +763,11 @@
     unsigned uint_value;
     long long long_long_value;
     unsigned long long ulong_long_value;
+    int128_t int128_value;
+    uint128_t uint128_value;
     bool bool_value;
     char_type char_value;
+    float float_value;
     double double_value;
     long double long_double_value;
     const void* pointer;
@@ -705,6 +780,9 @@
   FMT_CONSTEXPR value(unsigned val) : uint_value(val) {}
   value(long long val) : long_long_value(val) {}
   value(unsigned long long val) : ulong_long_value(val) {}
+  value(int128_t val) : int128_value(val) {}
+  value(uint128_t val) : uint128_value(val) {}
+  value(float val) : float_value(val) {}
   value(double val) : double_value(val) {}
   value(long double val) : long_double_value(val) {}
   value(bool val) : bool_value(val) {}
@@ -732,9 +810,9 @@
  private:
   // Formats an argument of a custom type, such as a user-defined class.
   template <typename T, typename Formatter>
-  static void format_custom_arg(const void* arg,
-                                basic_parse_context<char_type>& parse_ctx,
-                                Context& ctx) {
+  static void format_custom_arg(
+      const void* arg, basic_format_parse_context<char_type>& parse_ctx,
+      Context& ctx) {
     Formatter f;
     parse_ctx.advance_to(f.parse(parse_ctx));
     ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx));
@@ -764,6 +842,8 @@
   FMT_CONSTEXPR ulong_type map(unsigned long val) { return val; }
   FMT_CONSTEXPR long long map(long long val) { return val; }
   FMT_CONSTEXPR unsigned long long map(unsigned long long val) { return val; }
+  FMT_CONSTEXPR int128_t map(int128_t val) { return val; }
+  FMT_CONSTEXPR uint128_t map(uint128_t val) { return val; }
   FMT_CONSTEXPR bool map(bool val) { return val; }
 
   template <typename T, FMT_ENABLE_IF(is_char<T>::value)>
@@ -774,7 +854,7 @@
     return val;
   }
 
-  FMT_CONSTEXPR double map(float val) { return static_cast<double>(val); }
+  FMT_CONSTEXPR float map(float val) { return val; }
   FMT_CONSTEXPR double map(double val) { return val; }
   FMT_CONSTEXPR long double map(long double val) { return val; }
 
@@ -793,6 +873,15 @@
   FMT_CONSTEXPR basic_string_view<char_type> map(const T& val) {
     return basic_string_view<char_type>(val);
   }
+  template <
+      typename T,
+      FMT_ENABLE_IF(
+          std::is_constructible<std_string_view<char_type>, T>::value &&
+          !std::is_constructible<basic_string_view<char_type>, T>::value &&
+          !is_string<T>::value)>
+  FMT_CONSTEXPR basic_string_view<char_type> map(const T& val) {
+    return std_string_view<char_type>(val);
+  }
   FMT_CONSTEXPR const char* map(const signed char* val) {
     static_assert(std::is_same<char_type, char>::value, "invalid string type");
     return reinterpret_cast<const char*>(val);
@@ -818,11 +907,14 @@
             FMT_ENABLE_IF(std::is_enum<T>::value &&
                           !has_formatter<T, Context>::value &&
                           !has_fallback_formatter<T, Context>::value)>
-  FMT_CONSTEXPR int map(const T& val) {
-    return static_cast<int>(val);
+  FMT_CONSTEXPR auto map(const T& val) -> decltype(
+      map(static_cast<typename std::underlying_type<T>::type>(val))) {
+    return map(static_cast<typename std::underlying_type<T>::type>(val));
   }
   template <typename T,
             FMT_ENABLE_IF(!is_string<T>::value && !is_char<T>::value &&
+                          !std::is_constructible<basic_string_view<char_type>,
+                                                 T>::value &&
                           (has_formatter<T, Context>::value ||
                            has_fallback_formatter<T, Context>::value))>
   FMT_CONSTEXPR const T& map(const T& val) {
@@ -841,12 +933,13 @@
 // A type constant after applying arg_mapper<Context>.
 template <typename T, typename Context>
 using mapped_type_constant =
-    type_constant<decltype(arg_mapper<Context>().map(std::declval<T>())),
+    type_constant<decltype(arg_mapper<Context>().map(std::declval<const T&>())),
                   typename Context::char_type>;
 
+enum { packed_arg_bits = 5 };
 // Maximum number of arguments with packed types.
-enum { max_packed_args = 15 };
-enum : unsigned long long { is_unpacked_bit = 1ull << 63 };
+enum { max_packed_args = 63 / packed_arg_bits };
+enum : unsigned long long { is_unpacked_bit = 1ULL << 63 };
 
 template <typename Context> class arg_map;
 }  // namespace internal
@@ -877,7 +970,8 @@
    public:
     explicit handle(internal::custom_value<Context> custom) : custom_(custom) {}
 
-    void format(basic_parse_context<char_type>& parse_ctx, Context& ctx) const {
+    void format(basic_format_parse_context<char_type>& parse_ctx,
+                Context& ctx) const {
       custom_.format(custom_.value, parse_ctx, ctx);
     }
 
@@ -893,8 +987,8 @@
 
   internal::type type() const { return type_; }
 
-  bool is_integral() const { return internal::is_integral(type_); }
-  bool is_arithmetic() const { return internal::is_arithmetic(type_); }
+  bool is_integral() const { return internal::is_integral_type(type_); }
+  bool is_arithmetic() const { return internal::is_arithmetic_type(type_); }
 };
 
 /**
@@ -923,10 +1017,22 @@
     return vis(arg.value_.long_long_value);
   case internal::ulong_long_type:
     return vis(arg.value_.ulong_long_value);
+#if FMT_USE_INT128
+  case internal::int128_type:
+    return vis(arg.value_.int128_value);
+  case internal::uint128_type:
+    return vis(arg.value_.uint128_value);
+#else
+  case internal::int128_type:
+  case internal::uint128_type:
+    break;
+#endif
   case internal::bool_type:
     return vis(arg.value_.bool_value);
   case internal::char_type:
     return vis(arg.value_.char_value);
+  case internal::float_type:
+    return vis(arg.value_.float_value);
   case internal::double_type:
     return vis(arg.value_.double_value);
   case internal::long_double_type:
@@ -948,9 +1054,6 @@
 // A map from argument names to their values for named arguments.
 template <typename Context> class arg_map {
  private:
-  arg_map(const arg_map&) = delete;
-  void operator=(const arg_map&) = delete;
-
   using char_type = typename Context::char_type;
 
   struct entry {
@@ -968,6 +1071,8 @@
   }
 
  public:
+  arg_map(const arg_map&) = delete;
+  void operator=(const arg_map&) = delete;
   arg_map() : map_(nullptr), size_(0) {}
   void init(const basic_format_args<Context>& args);
   ~arg_map() { delete[] map_; }
@@ -990,6 +1095,8 @@
   locale_ref() : locale_(nullptr) {}
   template <typename Locale> explicit locale_ref(const Locale& loc);
 
+  explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; }
+
   template <typename Locale> Locale get() const;
 };
 
@@ -998,7 +1105,7 @@
 template <typename Context, typename Arg, typename... Args>
 constexpr unsigned long long encode_types() {
   return mapped_type_constant<Arg, Context>::value |
-         (encode_types<Context, Args...>() << 4);
+         (encode_types<Context, Args...>() << packed_arg_bits);
 }
 
 template <typename Context, typename T>
@@ -1034,14 +1141,13 @@
   internal::arg_map<basic_format_context> map_;
   internal::locale_ref loc_;
 
-  basic_format_context(const basic_format_context&) = delete;
-  void operator=(const basic_format_context&) = delete;
-
  public:
   using iterator = OutputIt;
   using format_arg = basic_format_arg<basic_format_context>;
   template <typename T> using formatter_type = formatter<T, char_type>;
 
+  basic_format_context(const basic_format_context&) = delete;
+  void operator=(const basic_format_context&) = delete;
   /**
    Constructs a ``basic_format_context`` object. References to the arguments are
    stored in the object so make sure they have appropriate lifetimes.
@@ -1100,7 +1206,6 @@
   static constexpr unsigned long long types =
       is_packed ? internal::encode_types<Context, Args...>()
                 : internal::is_unpacked_bit | num_args;
-  FMT_DEPRECATED static constexpr unsigned long long TYPES = types;
 
   format_arg_store(const Args&... args)
       : data_{internal::make_arg<is_packed, Context>(args)...} {}
@@ -1143,8 +1248,9 @@
   bool is_packed() const { return (types_ & internal::is_unpacked_bit) == 0; }
 
   internal::type type(int index) const {
-    int shift = index * 4;
-    return static_cast<internal::type>((types_ & (0xfull << shift)) >> shift);
+    int shift = index * internal::packed_arg_bits;
+    unsigned int mask = (1 << internal::packed_arg_bits) - 1;
+    return static_cast<internal::type>((types_ >> shift) & mask);
   }
 
   friend class internal::arg_map<Context>;
@@ -1371,7 +1477,7 @@
 }
 
 FMT_API void vprint(std::FILE* f, string_view format_str, format_args args);
-FMT_API void vprint(std::FILE* f, wstring_view format_str, wformat_args args);
+FMT_API void vprint(string_view format_str, format_args args);
 
 /**
   \rst
@@ -1391,9 +1497,6 @@
          internal::make_args_checked<Args...>(format_str, args...));
 }
 
-FMT_API void vprint(string_view format_str, format_args args);
-FMT_API void vprint(wstring_view format_str, wformat_args args);
-
 /**
   \rst
   Prints formatted data to ``stdout``.
diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h
index 147062f..72b3046 100644
--- a/include/fmt/format-inl.h
+++ b/include/fmt/format-inl.h
@@ -1,4 +1,4 @@
-// Formatting library for C++
+// Formatting library for C++ - implementation
 //
 // Copyright (c) 2012 - 2016, Victor Zverovich
 // All rights reserved.
@@ -10,14 +10,11 @@
 
 #include "format.h"
 
-#include <string.h>
-
+#include <cassert>
 #include <cctype>
-#include <cerrno>
 #include <climits>
 #include <cmath>
 #include <cstdarg>
-#include <cstddef>  // for std::ptrdiff_t
 #include <cstring>  // for std::memmove
 #include <cwchar>
 #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
@@ -47,25 +44,22 @@
 
 #ifdef _MSC_VER
 #  pragma warning(push)
-#  pragma warning(disable : 4127)  // conditional expression is constant
 #  pragma warning(disable : 4702)  // unreachable code
-// Disable deprecation warning for strerror. The latter is not called but
-// MSVC fails to detect it.
-#  pragma warning(disable : 4996)
 #endif
 
 // Dummy implementations of strerror_r and strerror_s called if corresponding
 // system functions are not available.
-inline fmt::internal::null<> strerror_r(int, char*, ...) {
-  return fmt::internal::null<>();
-}
-inline fmt::internal::null<> strerror_s(char*, std::size_t, ...) {
-  return fmt::internal::null<>();
-}
+inline fmt::internal::null<> strerror_r(int, char*, ...) { return {}; }
+inline fmt::internal::null<> strerror_s(char*, std::size_t, ...) { return {}; }
 
 FMT_BEGIN_NAMESPACE
 namespace internal {
 
+FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
+  print(stderr, "{}:{}: assertion failed: {}", file, line, message);
+  std::abort();
+}
+
 #ifndef _MSC_VER
 #  define FMT_SNPRINTF snprintf
 #else  // _MSC_VER
@@ -81,7 +75,7 @@
 
 using format_func = void (*)(internal::buffer<char>&, int, string_view);
 
-// Portable thread-safe version of strerror.
+// A portable thread-safe version of strerror.
 // Sets buffer to point to a string describing the error code.
 // This can be either a pointer to a string stored in buffer,
 // or a pointer to some static immutable string.
@@ -158,7 +152,7 @@
   static const char ERROR_STR[] = "error ";
   // Subtract 2 to account for terminating null characters in SEP and ERROR_STR.
   std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2;
-  auto abs_value = static_cast<uint32_or_64_t<int>>(error_code);
+  auto abs_value = static_cast<uint32_or_64_or_128_t<int>>(error_code);
   if (internal::is_negative(error_code)) {
     abs_value = 0 - abs_value;
     ++error_code_size;
@@ -206,6 +200,9 @@
   return locale_ ? *static_cast<const std::locale*>(locale_) : std::locale();
 }
 
+template <typename Char> FMT_FUNC std::string grouping_impl(locale_ref loc) {
+  return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>()).grouping();
+}
 template <typename Char> FMT_FUNC Char thousands_sep_impl(locale_ref loc) {
   return std::use_facet<std::numpunct<Char>>(loc.get<std::locale>())
       .thousands_sep();
@@ -217,6 +214,10 @@
 }  // namespace internal
 #else
 template <typename Char>
+FMT_FUNC std::string internal::grouping_impl(locale_ref) {
+  return "\03";
+}
+template <typename Char>
 FMT_FUNC Char internal::thousands_sep_impl(locale_ref) {
   return FMT_STATIC_THOUSANDS_SEPARATOR;
 }
@@ -226,8 +227,8 @@
 }
 #endif
 
-FMT_API FMT_FUNC format_error::~format_error() FMT_NOEXCEPT {}
-FMT_API FMT_FUNC system_error::~system_error() FMT_NOEXCEPT {}
+FMT_API FMT_FUNC format_error::~format_error() FMT_NOEXCEPT = default;
+FMT_API FMT_FUNC system_error::~system_error() FMT_NOEXCEPT = default;
 
 FMT_FUNC void system_error::init(int err_code, string_view format_str,
                                  format_args args) {
@@ -241,7 +242,7 @@
 namespace internal {
 
 template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) {
-  // Assume little endian; pointer formatting is implementation-defined anyway.
+  // fallback_uintptr is always stored in little endian.
   int i = static_cast<int>(sizeof(void*)) - 1;
   while (i > 0 && n.value[i] == 0) --i;
   auto char_digits = std::numeric_limits<unsigned char>::digits / 4;
@@ -249,20 +250,6 @@
 }
 
 template <typename T>
-int format_float(char* buf, std::size_t size, const char* format, int precision,
-                 T value) {
-#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
-  if (precision > 100000)
-    throw std::runtime_error(
-        "fuzz mode - avoid large allocation inside snprintf");
-#endif
-  // Suppress the warning about nonliteral format string.
-  auto snprintf_ptr = FMT_SNPRINTF;
-  return precision < 0 ? snprintf_ptr(buf, size, format, value)
-                       : snprintf_ptr(buf, size, format, precision, value);
-}
-
-template <typename T>
 const char basic_data<T>::digits[] =
     "0001020304050607080910111213141516171819"
     "2021222324252627282930313233343536373839"
@@ -274,14 +261,14 @@
 const char basic_data<T>::hex_digits[] = "0123456789abcdef";
 
 #define FMT_POWERS_OF_10(factor)                                             \
-  factor * 10, factor * 100, factor * 1000, factor * 10000, factor * 100000, \
-      factor * 1000000, factor * 10000000, factor * 100000000,               \
-      factor * 1000000000
+  factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \
+      (factor)*1000000, (factor)*10000000, (factor)*100000000,               \
+      (factor)*1000000000
 
 template <typename T>
 const uint64_t basic_data<T>::powers_of_10_64[] = {
-    1, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ull),
-    10000000000000000000ull};
+    1, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ULL),
+    10000000000000000000ULL};
 
 template <typename T>
 const uint32_t basic_data<T>::zero_or_powers_of_10_32[] = {0,
@@ -289,8 +276,8 @@
 
 template <typename T>
 const uint64_t basic_data<T>::zero_or_powers_of_10_64[] = {
-    0, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ull),
-    10000000000000000000ull};
+    0, FMT_POWERS_OF_10(1), FMT_POWERS_OF_10(1000000000ULL),
+    10000000000000000000ULL};
 
 // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
 // These are generated by support/compute-powers.py.
@@ -346,12 +333,24 @@
 const char basic_data<T>::background_color[] = "\x1b[48;2;";
 template <typename T> const char basic_data<T>::reset_color[] = "\x1b[0m";
 template <typename T> const wchar_t basic_data<T>::wreset_color[] = L"\x1b[0m";
+template <typename T> const char basic_data<T>::signs[] = {0, '-', '+', ' '};
 
 template <typename T> struct bits {
   static FMT_CONSTEXPR_DECL const int value =
       static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits);
 };
 
+class fp;
+template <int SHIFT = 0> fp normalize(fp value);
+
+// Lower (upper) boundary is a value half way between a floating-point value
+// and its predecessor (successor). Boundaries have the same exponent as the
+// value so only significands are stored.
+struct boundaries {
+  uint64_t lower;
+  uint64_t upper;
+};
+
 // A handmade floating-point number f * pow(2, e).
 class fp {
  private:
@@ -363,7 +362,7 @@
   static FMT_CONSTEXPR_DECL const int double_significand_size =
       std::numeric_limits<double>::digits - 1;
   static FMT_CONSTEXPR_DECL const uint64_t implicit_bit =
-      1ull << double_significand_size;
+      1ULL << double_significand_size;
 
  public:
   significand_type f;
@@ -377,95 +376,377 @@
 
   // Constructs fp from an IEEE754 double. It is a template to prevent compile
   // errors on platforms where double is not IEEE754.
-  template <typename Double> explicit fp(Double d) {
+  template <typename Double> explicit fp(Double d) { assign(d); }
+
+  // Normalizes the value converted from double and multiplied by (1 << SHIFT).
+  template <int SHIFT> friend fp normalize(fp value) {
+    // Handle subnormals.
+    const auto shifted_implicit_bit = fp::implicit_bit << SHIFT;
+    while ((value.f & shifted_implicit_bit) == 0) {
+      value.f <<= 1;
+      --value.e;
+    }
+    // Subtract 1 to account for hidden bit.
+    const auto offset =
+        fp::significand_size - fp::double_significand_size - SHIFT - 1;
+    value.f <<= offset;
+    value.e -= offset;
+    return value;
+  }
+
+  // Assigns d to this and return true iff predecessor is closer than successor.
+  template <typename Double, FMT_ENABLE_IF(sizeof(Double) == sizeof(uint64_t))>
+  bool assign(Double d) {
     // Assume double is in the format [sign][exponent][significand].
     using limits = std::numeric_limits<Double>;
     const int exponent_size =
         bits<Double>::value - double_significand_size - 1;  // -1 for sign
     const uint64_t significand_mask = implicit_bit - 1;
-    const uint64_t exponent_mask = (~0ull >> 1) & ~significand_mask;
+    const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
     const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1;
     auto u = bit_cast<uint64_t>(d);
-    auto biased_e = (u & exponent_mask) >> double_significand_size;
     f = u & significand_mask;
+    auto biased_e = (u & exponent_mask) >> double_significand_size;
+    // Predecessor is closer if d is a normalized power of 2 (f == 0) other than
+    // the smallest normalized number (biased_e > 1).
+    bool is_predecessor_closer = f == 0 && biased_e > 1;
     if (biased_e != 0)
       f += implicit_bit;
     else
       biased_e = 1;  // Subnormals use biased exponent 1 (min exponent).
     e = static_cast<int>(biased_e - exponent_bias - double_significand_size);
+    return is_predecessor_closer;
   }
 
-  // Normalizes the value converted from double and multiplied by (1 << SHIFT).
-  template <int SHIFT = 0> void normalize() {
-    // Handle subnormals.
-    auto shifted_implicit_bit = implicit_bit << SHIFT;
-    while ((f & shifted_implicit_bit) == 0) {
-      f <<= 1;
-      --e;
-    }
-    // Subtract 1 to account for hidden bit.
-    auto offset = significand_size - double_significand_size - SHIFT - 1;
-    f <<= offset;
-    e -= offset;
+  template <typename Double, FMT_ENABLE_IF(sizeof(Double) != sizeof(uint64_t))>
+  bool assign(Double) {
+    *this = fp();
+    return false;
   }
 
-  // Compute lower and upper boundaries (m^- and m^+ in the Grisu paper), where
-  // a boundary is a value half way between the number and its predecessor
+  // Assigns d to this together with computing lower and upper boundaries,
+  // where a boundary is a value half way between the number and its predecessor
   // (lower) or successor (upper). The upper boundary is normalized and lower
   // has the same exponent but may be not normalized.
-  void compute_boundaries(fp& lower, fp& upper) const {
-    lower =
-        f == implicit_bit ? fp((f << 2) - 1, e - 2) : fp((f << 1) - 1, e - 1);
-    upper = fp((f << 1) + 1, e - 1);
-    upper.normalize<1>();  // 1 is to account for the exponent shift above.
+  template <typename Double> boundaries assign_with_boundaries(Double d) {
+    bool is_lower_closer = assign(d);
+    fp lower =
+        is_lower_closer ? fp((f << 2) - 1, e - 2) : fp((f << 1) - 1, e - 1);
+    // 1 in normalize accounts for the exponent shift above.
+    fp upper = normalize<1>(fp((f << 1) + 1, e - 1));
     lower.f <<= lower.e - upper.e;
-    lower.e = upper.e;
+    return boundaries{lower.f, upper.f};
+  }
+
+  template <typename Double> boundaries assign_float_with_boundaries(Double d) {
+    assign(d);
+    constexpr int min_normal_e = std::numeric_limits<float>::min_exponent -
+                                 std::numeric_limits<double>::digits;
+    significand_type half_ulp = 1 << (std::numeric_limits<double>::digits -
+                                      std::numeric_limits<float>::digits - 1);
+    if (min_normal_e > e) half_ulp <<= min_normal_e - e;
+    fp upper = normalize<0>(fp(f + half_ulp, e));
+    fp lower = fp(
+        f - (half_ulp >> ((f == implicit_bit && e > min_normal_e) ? 1 : 0)), e);
+    lower.f <<= lower.e - upper.e;
+    return boundaries{lower.f, upper.f};
   }
 };
 
-// Returns an fp number representing x - y. Result may not be normalized.
-inline fp operator-(fp x, fp y) {
-  FMT_ASSERT(x.f >= y.f && x.e == y.e, "invalid operands");
-  return fp(x.f - y.f, x.e);
-}
+inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; }
 
-// Computes an fp number r with r.f = x.f * y.f / pow(2, 64) rounded to nearest
-// with half-up tie breaking, r.e = x.e + y.e + 64. Result may not be
-// normalized.
-FMT_FUNC fp operator*(fp x, fp y) {
-  int exp = x.e + y.e + 64;
+// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking.
+inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
 #if FMT_USE_INT128
-  auto product = static_cast<__uint128_t>(x.f) * y.f;
+  auto product = static_cast<__uint128_t>(lhs) * rhs;
   auto f = static_cast<uint64_t>(product >> 64);
-  if ((static_cast<uint64_t>(product) & (1ULL << 63)) != 0) ++f;
-  return fp(f, exp);
+  return (static_cast<uint64_t>(product) & (1ULL << 63)) != 0 ? f + 1 : f;
 #else
   // Multiply 32-bit parts of significands.
   uint64_t mask = (1ULL << 32) - 1;
-  uint64_t a = x.f >> 32, b = x.f & mask;
-  uint64_t c = y.f >> 32, d = y.f & mask;
+  uint64_t a = lhs >> 32, b = lhs & mask;
+  uint64_t c = rhs >> 32, d = rhs & mask;
   uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d;
   // Compute mid 64-bit of result and round.
   uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31);
-  return fp(ac + (ad >> 32) + (bc >> 32) + (mid >> 32), exp);
+  return ac + (ad >> 32) + (bc >> 32) + (mid >> 32);
 #endif
 }
 
-// Returns cached power (of 10) c_k = c_k.f * pow(2, c_k.e) such that its
-// (binary) exponent satisfies min_exponent <= c_k.e <= min_exponent + 28.
+inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; }
+
+// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its
+// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`.
 FMT_FUNC fp get_cached_power(int min_exponent, int& pow10_exponent) {
-  const double one_over_log2_10 = 0.30102999566398114;  // 1 / log2(10)
+  const uint64_t one_over_log2_10 = 0x4d104d42;  // round(pow(2, 32) / log2(10))
   int index = static_cast<int>(
-      std::ceil((min_exponent + fp::significand_size - 1) * one_over_log2_10));
+      static_cast<int64_t>(
+          (min_exponent + fp::significand_size - 1) * one_over_log2_10 +
+          ((uint64_t(1) << 32) - 1)  // ceil
+          ) >>
+      32  // arithmetic shift
+  );
   // Decimal exponent of the first (smallest) cached power of 10.
   const int first_dec_exp = -348;
   // Difference between 2 consecutive decimal exponents in cached powers of 10.
   const int dec_exp_step = 8;
   index = (index - first_dec_exp - 1) / dec_exp_step + 1;
   pow10_exponent = first_dec_exp + index * dec_exp_step;
-  return fp(data::pow10_significands[index], data::pow10_exponents[index]);
+  return {data::pow10_significands[index], data::pow10_exponents[index]};
 }
 
+// A simple accumulator to hold the sums of terms in bigint::square if uint128_t
+// is not available.
+struct accumulator {
+  uint64_t lower;
+  uint64_t upper;
+
+  accumulator() : lower(0), upper(0) {}
+  explicit operator uint32_t() const { return static_cast<uint32_t>(lower); }
+
+  void operator+=(uint64_t n) {
+    lower += n;
+    if (lower < n) ++upper;
+  }
+  void operator>>=(int shift) {
+    assert(shift == 32);
+    (void)shift;
+    lower = (upper << 32) | (lower >> 32);
+    upper >>= 32;
+  }
+};
+
+class bigint {
+ private:
+  // A bigint is stored as an array of bigits (big digits), with bigit at index
+  // 0 being the least significant one.
+  using bigit = uint32_t;
+  using double_bigit = uint64_t;
+  enum { bigits_capacity = 32 };
+  basic_memory_buffer<bigit, bigits_capacity> bigits_;
+  int exp_;
+
+  static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value;
+
+  friend struct formatter<bigint>;
+
+  void subtract_bigits(int index, bigit other, bigit& borrow) {
+    auto result = static_cast<double_bigit>(bigits_[index]) - other - borrow;
+    bigits_[index] = static_cast<bigit>(result);
+    borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));
+  }
+
+  void remove_leading_zeros() {
+    int num_bigits = static_cast<int>(bigits_.size()) - 1;
+    while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits;
+    bigits_.resize(num_bigits + 1);
+  }
+
+  // Computes *this -= other assuming aligned bigints and *this >= other.
+  void subtract_aligned(const bigint& other) {
+    FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints");
+    FMT_ASSERT(compare(*this, other) >= 0, "");
+    bigit borrow = 0;
+    int i = other.exp_ - exp_;
+    for (int j = 0, n = static_cast<int>(other.bigits_.size()); j != n;
+         ++i, ++j) {
+      subtract_bigits(i, other.bigits_[j], borrow);
+    }
+    while (borrow > 0) subtract_bigits(i, 0, borrow);
+    remove_leading_zeros();
+  }
+
+  void multiply(uint32_t value) {
+    const double_bigit wide_value = value;
+    bigit carry = 0;
+    for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
+      double_bigit result = bigits_[i] * wide_value + carry;
+      bigits_[i] = static_cast<bigit>(result);
+      carry = static_cast<bigit>(result >> bigit_bits);
+    }
+    if (carry != 0) bigits_.push_back(carry);
+  }
+
+  void multiply(uint64_t value) {
+    const bigit mask = ~bigit(0);
+    const double_bigit lower = value & mask;
+    const double_bigit upper = value >> bigit_bits;
+    double_bigit carry = 0;
+    for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
+      double_bigit result = bigits_[i] * lower + (carry & mask);
+      carry =
+          bigits_[i] * upper + (result >> bigit_bits) + (carry >> bigit_bits);
+      bigits_[i] = static_cast<bigit>(result);
+    }
+    while (carry != 0) {
+      bigits_.push_back(carry & mask);
+      carry >>= bigit_bits;
+    }
+  }
+
+ public:
+  bigint() : exp_(0) {}
+  explicit bigint(uint64_t n) { assign(n); }
+  ~bigint() { assert(bigits_.capacity() <= bigits_capacity); }
+
+  bigint(const bigint&) = delete;
+  void operator=(const bigint&) = delete;
+
+  void assign(const bigint& other) {
+    bigits_.resize(other.bigits_.size());
+    auto data = other.bigits_.data();
+    std::copy(data, data + other.bigits_.size(), bigits_.data());
+    exp_ = other.exp_;
+  }
+
+  void assign(uint64_t n) {
+    int num_bigits = 0;
+    do {
+      bigits_[num_bigits++] = n & ~bigit(0);
+      n >>= bigit_bits;
+    } while (n != 0);
+    bigits_.resize(num_bigits);
+    exp_ = 0;
+  }
+
+  int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
+
+  bigint& operator<<=(int shift) {
+    assert(shift >= 0);
+    exp_ += shift / bigit_bits;
+    shift %= bigit_bits;
+    if (shift == 0) return *this;
+    bigit carry = 0;
+    for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
+      bigit c = bigits_[i] >> (bigit_bits - shift);
+      bigits_[i] = (bigits_[i] << shift) + carry;
+      carry = c;
+    }
+    if (carry != 0) bigits_.push_back(carry);
+    return *this;
+  }
+
+  template <typename Int> bigint& operator*=(Int value) {
+    FMT_ASSERT(value > 0, "");
+    multiply(uint32_or_64_or_128_t<Int>(value));
+    return *this;
+  }
+
+  friend int compare(const bigint& lhs, const bigint& rhs) {
+    int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits();
+    if (num_lhs_bigits != num_rhs_bigits)
+      return num_lhs_bigits > num_rhs_bigits ? 1 : -1;
+    int i = static_cast<int>(lhs.bigits_.size()) - 1;
+    int j = static_cast<int>(rhs.bigits_.size()) - 1;
+    int end = i - j;
+    if (end < 0) end = 0;
+    for (; i >= end; --i, --j) {
+      bigit lhs_bigit = lhs.bigits_[i], rhs_bigit = rhs.bigits_[j];
+      if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1;
+    }
+    if (i != j) return i > j ? 1 : -1;
+    return 0;
+  }
+
+  // Returns compare(lhs1 + lhs2, rhs).
+  friend int add_compare(const bigint& lhs1, const bigint& lhs2,
+                         const bigint& rhs) {
+    int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits());
+    int num_rhs_bigits = rhs.num_bigits();
+    if (max_lhs_bigits + 1 < num_rhs_bigits) return -1;
+    if (max_lhs_bigits > num_rhs_bigits) return 1;
+    auto get_bigit = [](const bigint& n, int i) -> bigit {
+      return i >= n.exp_ && i < n.num_bigits() ? n.bigits_[i - n.exp_] : 0;
+    };
+    double_bigit borrow = 0;
+    int min_exp = (std::min)((std::min)(lhs1.exp_, lhs2.exp_), rhs.exp_);
+    for (int i = num_rhs_bigits - 1; i >= min_exp; --i) {
+      double_bigit sum =
+          static_cast<double_bigit>(get_bigit(lhs1, i)) + get_bigit(lhs2, i);
+      bigit rhs_bigit = get_bigit(rhs, i);
+      if (sum > rhs_bigit + borrow) return 1;
+      borrow = rhs_bigit + borrow - sum;
+      if (borrow > 1) return -1;
+      borrow <<= bigit_bits;
+    }
+    return borrow != 0 ? -1 : 0;
+  }
+
+  // Assigns pow(10, exp) to this bigint.
+  void assign_pow10(int exp) {
+    assert(exp >= 0);
+    if (exp == 0) return assign(1);
+    // Find the top bit.
+    int bitmask = 1;
+    while (exp >= bitmask) bitmask <<= 1;
+    bitmask >>= 1;
+    // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by
+    // repeated squaring and multiplication.
+    assign(5);
+    bitmask >>= 1;
+    while (bitmask != 0) {
+      square();
+      if ((exp & bitmask) != 0) *this *= 5;
+      bitmask >>= 1;
+    }
+    *this <<= exp;  // Multiply by pow(2, exp) by shifting.
+  }
+
+  void square() {
+    basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_));
+    int num_bigits = static_cast<int>(bigits_.size());
+    int num_result_bigits = 2 * num_bigits;
+    bigits_.resize(num_result_bigits);
+    using accumulator_t = conditional_t<FMT_USE_INT128, uint128_t, accumulator>;
+    auto sum = accumulator_t();
+    for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) {
+      // Compute bigit at position bigit_index of the result by adding
+      // cross-product terms n[i] * n[j] such that i + j == bigit_index.
+      for (int i = 0, j = bigit_index; j >= 0; ++i, --j) {
+        // Most terms are multiplied twice which can be optimized in the future.
+        sum += static_cast<double_bigit>(n[i]) * n[j];
+      }
+      bigits_[bigit_index] = static_cast<bigit>(sum);
+      sum >>= bits<bigit>::value;  // Compute the carry.
+    }
+    // Do the same for the top half.
+    for (int bigit_index = num_bigits; bigit_index < num_result_bigits;
+         ++bigit_index) {
+      for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;)
+        sum += static_cast<double_bigit>(n[i++]) * n[j--];
+      bigits_[bigit_index] = static_cast<bigit>(sum);
+      sum >>= bits<bigit>::value;
+    }
+    --num_result_bigits;
+    remove_leading_zeros();
+    exp_ *= 2;
+  }
+
+  // Divides this bignum by divisor, assigning the remainder to this and
+  // returning the quotient.
+  int divmod_assign(const bigint& divisor) {
+    FMT_ASSERT(this != &divisor, "");
+    if (compare(*this, divisor) < 0) return 0;
+    int num_bigits = static_cast<int>(bigits_.size());
+    FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1] != 0, "");
+    int exp_difference = exp_ - divisor.exp_;
+    if (exp_difference > 0) {
+      // Align bigints by adding trailing zeros to simplify subtraction.
+      bigits_.resize(num_bigits + exp_difference);
+      for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j)
+        bigits_[j] = bigits_[i];
+      std::uninitialized_fill_n(bigits_.data(), exp_difference, 0);
+      exp_ -= exp_difference;
+    }
+    int quotient = 0;
+    do {
+      subtract_aligned(divisor);
+      ++quotient;
+    } while (compare(*this, divisor) >= 0);
+    return quotient;
+  }
+};
+
 enum round_direction { unknown, up, down };
 
 // Given the divisor (normally a power of 10), the remainder = v % divisor for
@@ -500,13 +781,13 @@
 // error: the size of the region (lower, upper) outside of which numbers
 // definitely do not round to value (Delta in Grisu3).
 template <typename Handler>
-digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
-                                Handler& handler) {
-  fp one(1ull << -value.e, value.e);
+FMT_ALWAYS_INLINE digits::result grisu_gen_digits(fp value, uint64_t error,
+                                                  int& exp, Handler& handler) {
+  const fp one(1ULL << -value.e, value.e);
   // The integral part of scaled value (p1 in Grisu) = value / one. It cannot be
   // zero because it contains a product of two 64-bit numbers with MSB set (due
   // to normalization) - 1, shifted right by at most 60 bits.
-  uint32_t integral = static_cast<uint32_t>(value.f >> -one.e);
+  auto integral = static_cast<uint32_t>(value.f >> -one.e);
   FMT_ASSERT(integral != 0, "");
   FMT_ASSERT(integral == value.f >> -one.e, "");
   // The fractional part of scaled value (p2 in Grisu) c = value % one.
@@ -519,44 +800,39 @@
   // Generate digits for the integral part. This can produce up to 10 digits.
   do {
     uint32_t digit = 0;
-    // This optimization by miloyip reduces the number of integer divisions by
+    auto divmod_integral = [&](uint32_t divisor) {
+      digit = integral / divisor;
+      integral %= divisor;
+    };
+    // This optimization by Milo Yip reduces the number of integer divisions by
     // one per iteration.
     switch (exp) {
     case 10:
-      digit = integral / 1000000000;
-      integral %= 1000000000;
+      divmod_integral(1000000000);
       break;
     case 9:
-      digit = integral / 100000000;
-      integral %= 100000000;
+      divmod_integral(100000000);
       break;
     case 8:
-      digit = integral / 10000000;
-      integral %= 10000000;
+      divmod_integral(10000000);
       break;
     case 7:
-      digit = integral / 1000000;
-      integral %= 1000000;
+      divmod_integral(1000000);
       break;
     case 6:
-      digit = integral / 100000;
-      integral %= 100000;
+      divmod_integral(100000);
       break;
     case 5:
-      digit = integral / 10000;
-      integral %= 10000;
+      divmod_integral(10000);
       break;
     case 4:
-      digit = integral / 1000;
-      integral %= 1000;
+      divmod_integral(1000);
       break;
     case 3:
-      digit = integral / 100;
-      integral %= 100;
+      divmod_integral(100);
       break;
     case 2:
-      digit = integral / 10;
-      integral %= 10;
+      divmod_integral(10);
       break;
     case 1:
       digit = integral;
@@ -640,7 +916,7 @@
 };
 
 // The shortest representation digit handler.
-template <int GRISU_VERSION> struct grisu_shortest_handler {
+struct grisu_shortest_handler {
   char* buf;
   int size;
   // Distance between scaled value and upper bound (wp_W in Grisu3).
@@ -666,11 +942,6 @@
                           uint64_t error, int exp, bool integral) {
     buf[size++] = digit;
     if (remainder >= error) return digits::more;
-    if (GRISU_VERSION != 3) {
-      uint64_t d = integral ? diff : diff * data::powers_of_10_64[-exp];
-      round(d, divisor, remainder, error);
-      return digits::done;
-    }
     uint64_t unit = integral ? 1 : data::powers_of_10_64[-exp];
     uint64_t up = (diff - 1) * unit;  // wp_Wup
     round(up, divisor, remainder, error);
@@ -686,151 +957,289 @@
   }
 };
 
-template <typename Double,
-          enable_if_t<(sizeof(Double) == sizeof(uint64_t)), int>>
-FMT_API bool grisu_format(Double value, buffer<char>& buf, int precision,
-                          unsigned options, int& exp) {
+// Formats value using a variation of the Fixed-Precision Positive
+// Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
+// https://fmt.dev/p372-steele.pdf.
+template <typename Double>
+void fallback_format(Double d, buffer<char>& buf, int& exp10) {
+  bigint numerator;    // 2 * R in (FPP)^2.
+  bigint denominator;  // 2 * S in (FPP)^2.
+  // lower and upper are differences between value and corresponding boundaries.
+  bigint lower;             // (M^- in (FPP)^2).
+  bigint upper_store;       // upper's value if different from lower.
+  bigint* upper = nullptr;  // (M^+ in (FPP)^2).
+  fp value;
+  // Shift numerator and denominator by an extra bit or two (if lower boundary
+  // is closer) to make lower and upper integers. This eliminates multiplication
+  // by 2 during later computations.
+  // TODO: handle float
+  int shift = value.assign(d) ? 2 : 1;
+  uint64_t significand = value.f << shift;
+  if (value.e >= 0) {
+    numerator.assign(significand);
+    numerator <<= value.e;
+    lower.assign(1);
+    lower <<= value.e;
+    if (shift != 1) {
+      upper_store.assign(1);
+      upper_store <<= value.e + 1;
+      upper = &upper_store;
+    }
+    denominator.assign_pow10(exp10);
+    denominator <<= 1;
+  } else if (exp10 < 0) {
+    numerator.assign_pow10(-exp10);
+    lower.assign(numerator);
+    if (shift != 1) {
+      upper_store.assign(numerator);
+      upper_store <<= 1;
+      upper = &upper_store;
+    }
+    numerator *= significand;
+    denominator.assign(1);
+    denominator <<= shift - value.e;
+  } else {
+    numerator.assign(significand);
+    denominator.assign_pow10(exp10);
+    denominator <<= shift - value.e;
+    lower.assign(1);
+    if (shift != 1) {
+      upper_store.assign(1ULL << 1);
+      upper = &upper_store;
+    }
+  }
+  if (!upper) upper = &lower;
+  // Invariant: value == (numerator / denominator) * pow(10, exp10).
+  bool even = (value.f & 1) == 0;
+  int num_digits = 0;
+  char* data = buf.data();
+  for (;;) {
+    int digit = numerator.divmod_assign(denominator);
+    bool low = compare(numerator, lower) - even < 0;  // numerator <[=] lower.
+    // numerator + upper >[=] pow10:
+    bool high = add_compare(numerator, *upper, denominator) + even > 0;
+    data[num_digits++] = static_cast<char>('0' + digit);
+    if (low || high) {
+      if (!low) {
+        ++data[num_digits - 1];
+      } else if (high) {
+        int result = add_compare(numerator, numerator, denominator);
+        // Round half to even.
+        if (result > 0 || (result == 0 && (digit % 2) != 0))
+          ++data[num_digits - 1];
+      }
+      buf.resize(num_digits);
+      exp10 -= num_digits - 1;
+      return;
+    }
+    numerator *= 10;
+    lower *= 10;
+    if (upper != &lower) *upper *= 10;
+  }
+}
+
+// Formats value using the Grisu algorithm
+// (https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf)
+// if T is a IEEE754 binary32 or binary64 and snprintf otherwise.
+template <typename T>
+int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
+  static_assert(!std::is_same<T, float>(), "");
   FMT_ASSERT(value >= 0, "value is negative");
-  bool fixed = (options & grisu_options::fixed) != 0;
+
+  const bool fixed = specs.format == float_format::fixed;
   if (value <= 0) {  // <= instead of == to silence a warning.
     if (precision <= 0 || !fixed) {
-      exp = 0;
       buf.push_back('0');
-    } else {
-      exp = -precision;
-      buf.resize(precision);
-      std::uninitialized_fill_n(buf.data(), precision, '0');
+      return 0;
     }
-    return true;
+    buf.resize(to_unsigned(precision));
+    std::uninitialized_fill_n(buf.data(), precision, '0');
+    return -precision;
   }
 
-  fp fp_value(value);
+  if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf);
+
+  int exp = 0;
   const int min_exp = -60;  // alpha in Grisu.
   int cached_exp10 = 0;     // K in Grisu.
   if (precision != -1) {
-    if (precision > 17) return false;
-    fp_value.normalize();
-    auto cached_pow = get_cached_power(
-        min_exp - (fp_value.e + fp::significand_size), cached_exp10);
-    fp_value = fp_value * cached_pow;
+    if (precision > 17) return snprintf_float(value, precision, specs, buf);
+    fp normalized = normalize(fp(value));
+    const auto cached_pow = get_cached_power(
+        min_exp - (normalized.e + fp::significand_size), cached_exp10);
+    normalized = normalized * cached_pow;
     fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
-    if (grisu_gen_digits(fp_value, 1, exp, handler) == digits::error)
-      return false;
-    buf.resize(to_unsigned(handler.size));
-  } else {
-    fp lower, upper;  // w^- and w^+ in the Grisu paper.
-    fp_value.compute_boundaries(lower, upper);
-    // Find a cached power of 10 such that multiplying upper by it will bring
-    // the exponent in the range [min_exp, -32].
-    auto cached_pow = get_cached_power(  // \tilde{c}_{-k} in Grisu.
-        min_exp - (upper.e + fp::significand_size), cached_exp10);
-    fp_value.normalize();
-    fp_value = fp_value * cached_pow;
-    lower = lower * cached_pow;  // \tilde{M}^- in Grisu.
-    upper = upper * cached_pow;  // \tilde{M}^+ in Grisu.
-    assert(min_exp <= upper.e && upper.e <= -32);
-    auto result = digits::result();
-    int size = 0;
-    if ((options & grisu_options::grisu3) != 0) {
-      --lower.f;  // \tilde{M}^- - 1 ulp -> M^-_{\downarrow}.
-      ++upper.f;  // \tilde{M}^+ + 1 ulp -> M^+_{\uparrow}.
-      // Numbers outside of (lower, upper) definitely do not round to value.
-      grisu_shortest_handler<3> handler{buf.data(), 0, (upper - fp_value).f};
-      result = grisu_gen_digits(upper, upper.f - lower.f, exp, handler);
-      size = handler.size;
-    } else {
-      ++lower.f;  // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}.
-      --upper.f;  // \tilde{M}^+ - 1 ulp -> M^+_{\downarrow}.
-      grisu_shortest_handler<2> handler{buf.data(), 0, (upper - fp_value).f};
-      result = grisu_gen_digits(upper, upper.f - lower.f, exp, handler);
-      size = handler.size;
+    if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error)
+      return snprintf_float(value, precision, specs, buf);
+    int num_digits = handler.size;
+    if (!fixed) {
+      // Remove trailing zeros.
+      while (num_digits > 0 && buf[num_digits - 1] == '0') {
+        --num_digits;
+        ++exp;
+      }
     }
-    if (result == digits::error) return false;
-    buf.resize(to_unsigned(size));
+    buf.resize(to_unsigned(num_digits));
+  } else {
+    fp fp_value;
+    auto boundaries = specs.binary32
+                          ? fp_value.assign_float_with_boundaries(value)
+                          : fp_value.assign_with_boundaries(value);
+    fp_value = normalize(fp_value);
+    // Find a cached power of 10 such that multiplying value by it will bring
+    // the exponent in the range [min_exp, -32].
+    const fp cached_pow = get_cached_power(
+        min_exp - (fp_value.e + fp::significand_size), cached_exp10);
+    // Multiply value and boundaries by the cached power of 10.
+    fp_value = fp_value * cached_pow;
+    boundaries.lower = multiply(boundaries.lower, cached_pow.f);
+    boundaries.upper = multiply(boundaries.upper, cached_pow.f);
+    assert(min_exp <= fp_value.e && fp_value.e <= -32);
+    --boundaries.lower;  // \tilde{M}^- - 1 ulp -> M^-_{\downarrow}.
+    ++boundaries.upper;  // \tilde{M}^+ + 1 ulp -> M^+_{\uparrow}.
+    // Numbers outside of (lower, upper) definitely do not round to value.
+    grisu_shortest_handler handler{buf.data(), 0,
+                                   boundaries.upper - fp_value.f};
+    auto result =
+        grisu_gen_digits(fp(boundaries.upper, fp_value.e),
+                         boundaries.upper - boundaries.lower, exp, handler);
+    if (result == digits::error) {
+      exp += handler.size - cached_exp10 - 1;
+      fallback_format(value, buf, exp);
+      return exp;
+    }
+    buf.resize(to_unsigned(handler.size));
   }
-  exp -= cached_exp10;
-  return true;
+  return exp - cached_exp10;
 }
 
-template <typename Double>
-char* sprintf_format(Double value, internal::buffer<char>& buf,
-                     sprintf_specs specs) {
+template <typename T>
+int snprintf_float(T value, int precision, float_specs specs,
+                   buffer<char>& buf) {
   // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail.
-  FMT_ASSERT(buf.capacity() != 0, "empty buffer");
+  FMT_ASSERT(buf.capacity() > buf.size(), "empty buffer");
+  static_assert(!std::is_same<T, float>(), "");
 
-  // Build format string.
-  enum { max_format_size = 10 };  // longest format: %#-*.*Lg
+  // Subtract 1 to account for the difference in precision since we use %e for
+  // both general and exponent format.
+  if (specs.format == float_format::general ||
+      specs.format == float_format::exp)
+    precision = (precision >= 0 ? precision : 6) - 1;
+
+  // Build the format string.
+  enum { max_format_size = 7 };  // Ths longest format is "%#.*Le".
   char format[max_format_size];
   char* format_ptr = format;
   *format_ptr++ = '%';
-  if (specs.alt || !specs.type) *format_ptr++ = '#';
-  if (specs.precision >= 0) {
+  if (specs.trailing_zeros) *format_ptr++ = '#';
+  if (precision >= 0) {
     *format_ptr++ = '.';
     *format_ptr++ = '*';
   }
-  if (std::is_same<Double, long double>::value) *format_ptr++ = 'L';
-
-  char type = specs.type;
-
-  if (type == '%')
-    type = 'f';
-  else if (type == 0 || type == 'n')
-    type = 'g';
-#if FMT_MSC_VER
-  if (type == 'F') {
-    // MSVC's printf doesn't support 'F'.
-    type = 'f';
-  }
-#endif
-  *format_ptr++ = type;
+  if (std::is_same<T, long double>()) *format_ptr++ = 'L';
+  *format_ptr++ = specs.format != float_format::hex
+                      ? (specs.format == float_format::fixed ? 'f' : 'e')
+                      : (specs.upper ? 'A' : 'a');
   *format_ptr = '\0';
 
   // Format using snprintf.
-  char* start = nullptr;
-  char* decimal_point_pos = nullptr;
+  auto offset = buf.size();
   for (;;) {
-    std::size_t buffer_size = buf.capacity();
-    start = &buf[0];
-    int result =
-        format_float(start, buffer_size, format, specs.precision, value);
-    if (result >= 0) {
-      unsigned n = internal::to_unsigned(result);
-      if (n < buf.capacity()) {
-        // Find the decimal point.
-        auto p = buf.data(), end = p + n;
-        if (*p == '+' || *p == '-') ++p;
-        if (specs.type != 'a' && specs.type != 'A') {
-          while (p < end && *p >= '0' && *p <= '9') ++p;
-          if (p < end && *p != 'e' && *p != 'E') {
-            decimal_point_pos = p;
-            if (!specs.type) {
-              // Keep only one trailing zero after the decimal point.
-              ++p;
-              if (*p == '0') ++p;
-              while (p != end && *p >= '1' && *p <= '9') ++p;
-              char* where = p;
-              while (p != end && *p == '0') ++p;
-              if (p == end || *p < '0' || *p > '9') {
-                if (p != end) std::memmove(where, p, to_unsigned(end - p));
-                n -= static_cast<unsigned>(p - where);
-              }
-            }
-          }
-        }
-        buf.resize(n);
-        break;  // The buffer is large enough - continue with formatting.
-      }
-      buf.reserve(n + 1);
-    } else {
-      // If result is negative we ask to increase the capacity by at least 1,
-      // but as std::vector, the buffer grows exponentially.
-      buf.reserve(buf.capacity() + 1);
+    auto begin = buf.data() + offset;
+    auto capacity = buf.capacity() - offset;
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+    if (precision > 100000)
+      throw std::runtime_error(
+          "fuzz mode - avoid large allocation inside snprintf");
+#endif
+    // Suppress the warning about a nonliteral format string.
+    auto snprintf_ptr = FMT_SNPRINTF;
+    int result = precision >= 0
+                     ? snprintf_ptr(begin, capacity, format, precision, value)
+                     : snprintf_ptr(begin, capacity, format, value);
+    if (result < 0) {
+      buf.reserve(buf.capacity() + 1);  // The buffer will grow exponentially.
+      continue;
     }
+    unsigned size = to_unsigned(result);
+    // Size equal to capacity means that the last character was truncated.
+    if (size >= capacity) {
+      buf.reserve(size + offset + 1);  // Add 1 for the terminating '\0'.
+      continue;
+    }
+    auto is_digit = [](char c) { return c >= '0' && c <= '9'; };
+    if (specs.format == float_format::fixed) {
+      if (precision == 0) {
+        buf.resize(size);
+        return 0;
+      }
+      // Find and remove the decimal point.
+      auto end = begin + size, p = end;
+      do {
+        --p;
+      } while (is_digit(*p));
+      int fraction_size = static_cast<int>(end - p - 1);
+      std::memmove(p, p + 1, fraction_size);
+      buf.resize(size - 1);
+      return -fraction_size;
+    }
+    if (specs.format == float_format::hex) {
+      buf.resize(size + offset);
+      return 0;
+    }
+    // Find and parse the exponent.
+    auto end = begin + size, exp_pos = end;
+    do {
+      --exp_pos;
+    } while (*exp_pos != 'e');
+    char sign = exp_pos[1];
+    assert(sign == '+' || sign == '-');
+    int exp = 0;
+    auto p = exp_pos + 2;  // Skip 'e' and sign.
+    do {
+      assert(is_digit(*p));
+      exp = exp * 10 + (*p++ - '0');
+    } while (p != end);
+    if (sign == '-') exp = -exp;
+    int fraction_size = 0;
+    if (exp_pos != begin + 1) {
+      // Remove trailing zeros.
+      auto fraction_end = exp_pos - 1;
+      while (*fraction_end == '0') --fraction_end;
+      // Move the fractional part left to get rid of the decimal point.
+      fraction_size = static_cast<int>(fraction_end - begin - 1);
+      std::memmove(begin + 1, begin + 2, fraction_size);
+    }
+    buf.resize(fraction_size + offset + 1);
+    return exp - fraction_size;
   }
-  return decimal_point_pos;
 }
 }  // namespace internal
 
+template <> struct formatter<internal::bigint> {
+  format_parse_context::iterator parse(format_parse_context& ctx) {
+    return ctx.begin();
+  }
+
+  format_context::iterator format(const internal::bigint& n,
+                                  format_context& ctx) {
+    auto out = ctx.out();
+    bool first = true;
+    for (auto i = n.bigits_.size(); i > 0; --i) {
+      auto value = n.bigits_[i - 1];
+      if (first) {
+        out = format_to(out, "{:x}", value);
+        first = false;
+        continue;
+      }
+      out = format_to(out, "{:08x}", value);
+    }
+    if (n.exp_ > 0)
+      out = format_to(out, "p{}", n.exp_ * internal::bigint::bigit_bits);
+    return out;
+  }
+};
+
 #if FMT_USE_WINDOWS_H
 
 FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) {
@@ -974,23 +1383,10 @@
   internal::fwrite_fully(buffer.data(), 1, buffer.size(), f);
 }
 
-FMT_FUNC void vprint(std::FILE* f, wstring_view format_str, wformat_args args) {
-  wmemory_buffer buffer;
-  internal::vformat_to(buffer, format_str, args);
-  buffer.push_back(L'\0');
-  if (std::fputws(buffer.data(), f) == -1) {
-    FMT_THROW(system_error(errno, "cannot write to file"));
-  }
-}
-
 FMT_FUNC void vprint(string_view format_str, format_args args) {
   vprint(stdout, format_str, args);
 }
 
-FMT_FUNC void vprint(wstring_view format_str, wformat_args args) {
-  vprint(stdout, format_str, args);
-}
-
 FMT_END_NAMESPACE
 
 #ifdef _MSC_VER
diff --git a/include/fmt/format.h b/include/fmt/format.h
index efec5d6..600b0eb 100644
--- a/include/fmt/format.h
+++ b/include/fmt/format.h
@@ -33,18 +33,16 @@
 #ifndef FMT_FORMAT_H_
 #define FMT_FORMAT_H_
 
+#include "core.h"
+
 #include <algorithm>
-#include <cassert>
+#include <cerrno>
 #include <cmath>
 #include <cstdint>
-#include <cstring>
-#include <iterator>
 #include <limits>
 #include <memory>
 #include <stdexcept>
 
-#include "core.h"
-
 #ifdef __clang__
 #  define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
 #else
@@ -71,6 +69,12 @@
 #  define FMT_HAS_BUILTIN(x) 0
 #endif
 
+#if FMT_HAS_CPP_ATTRIBUTE(fallthrough) >= 201603 && __cplusplus >= 201703
+#  define FMT_FALLTHROUGH [[fallthrough]]
+#else
+#  define FMT_FALLTHROUGH
+#endif
+
 #ifndef FMT_THROW
 #  if FMT_EXCEPTIONS
 #    if FMT_MSC_VER
@@ -84,7 +88,7 @@
 }
 }  // namespace internal
 FMT_END_NAMESPACE
-#      define FMT_THROW(x) fmt::internal::do_throw(x)
+#      define FMT_THROW(x) internal::do_throw(x)
 #    else
 #      define FMT_THROW(x) throw x
 #    endif
@@ -92,7 +96,7 @@
 #    define FMT_THROW(x)              \
       do {                            \
         static_cast<void>(sizeof(x)); \
-        assert(false);                \
+        FMT_ASSERT(false, "");        \
       } while (false)
 #  endif
 #endif
@@ -123,14 +127,6 @@
 #  endif
 #endif
 
-#ifdef FMT_USE_INT128
-// Do nothing.
-#elif defined(__SIZEOF_INT128__)
-#  define FMT_USE_INT128 1
-#else
-#  define FMT_USE_INT128 0
-#endif
-
 // __builtin_clz is broken in clang with Microsoft CodeGen:
 // https://github.com/fmtlib/fmt/issues/519
 #if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz)) && !FMT_MSC_VER
@@ -156,14 +152,14 @@
   unsigned long r = 0;
   _BitScanReverse(&r, x);
 
-  assert(x != 0);
+  FMT_ASSERT(x != 0, "");
   // Static analysis complains about using uninitialized data
   // "r", but the only way that can happen is if "x" is 0,
   // which the callers guarantee to not happen.
 #  pragma warning(suppress : 6102)
   return 31 - r;
 }
-#  define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n)
+#  define FMT_BUILTIN_CLZ(n) internal::clz(n)
 
 #  if defined(_WIN64) && !defined(__clang__)
 #    pragma intrinsic(_BitScanReverse64)
@@ -181,32 +177,36 @@
   _BitScanReverse(&r, static_cast<uint32_t>(x));
 #  endif
 
-  assert(x != 0);
+  FMT_ASSERT(x != 0, "");
   // Static analysis complains about using uninitialized data
   // "r", but the only way that can happen is if "x" is 0,
   // which the callers guarantee to not happen.
 #  pragma warning(suppress : 6102)
   return 63 - r;
 }
-#  define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n)
+#  define FMT_BUILTIN_CLZLL(n) internal::clzll(n)
 }  // namespace internal
 FMT_END_NAMESPACE
 #endif
 
+// Enable the deprecated numeric alignment.
+#ifndef FMT_NUMERIC_ALIGN
+#  define FMT_NUMERIC_ALIGN 1
+#endif
+
+// Enable the deprecated percent specifier.
+#ifndef FMT_DEPRECATED_PERCENT
+#  define FMT_DEPRECATED_PERCENT 0
+#endif
+
 FMT_BEGIN_NAMESPACE
 namespace internal {
 
-// A fallback implementation of uintptr_t for systems that lack it.
-struct fallback_uintptr {
-  unsigned char value[sizeof(void*)];
-};
-#ifdef UINTPTR_MAX
-using uintptr_t = ::uintptr_t;
-#else
-using uintptr_t = fallback_uintptr;
-#endif
+// A helper function to suppress bogus "conditional expression is constant"
+// warnings.
+template <typename T> inline T const_check(T value) { return value; }
 
-// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't produce
+// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't have
 // undefined behavior (e.g. due to type aliasing).
 // Example: uint64_t d = bit_cast<uint64_t>(2.718);
 template <typename Dest, typename Source>
@@ -217,6 +217,50 @@
   return dest;
 }
 
+inline bool is_big_endian() {
+  auto u = 1u;
+  struct bytes {
+    char data[sizeof(u)];
+  };
+  return bit_cast<bytes>(u).data[0] == 0;
+}
+
+// A fallback implementation of uintptr_t for systems that lack it.
+struct fallback_uintptr {
+  unsigned char value[sizeof(void*)];
+
+  fallback_uintptr() = default;
+  explicit fallback_uintptr(const void* p) {
+    *this = bit_cast<fallback_uintptr>(p);
+    if (is_big_endian()) {
+      for (size_t i = 0, j = sizeof(void*) - 1; i < j; ++i, --j)
+        std::swap(value[i], value[j]);
+    }
+  }
+};
+#ifdef UINTPTR_MAX
+using uintptr_t = ::uintptr_t;
+inline uintptr_t to_uintptr(const void* p) { return bit_cast<uintptr_t>(p); }
+#else
+using uintptr_t = fallback_uintptr;
+inline fallback_uintptr to_uintptr(const void* p) {
+  return fallback_uintptr(p);
+}
+#endif
+
+// Returns the largest possible value for type T. Same as
+// std::numeric_limits<T>::max() but shorter and not affected by the max macro.
+template <typename T> constexpr T max_value() {
+  return (std::numeric_limits<T>::max)();
+}
+template <typename T> constexpr int num_bits() {
+  return std::numeric_limits<T>::digits;
+}
+template <> constexpr int num_bits<fallback_uintptr>() {
+  return static_cast<int>(sizeof(void*) *
+                          std::numeric_limits<unsigned char>::digits);
+}
+
 // An approximation of iterator_t for pre-C++20 systems.
 template <typename T>
 using iterator_t = decltype(std::begin(std::declval<T&>()));
@@ -290,19 +334,21 @@
 
 // An output iterator that counts the number of objects written to it and
 // discards them.
-template <typename T> class counting_iterator {
+class counting_iterator {
  private:
   std::size_t count_;
-  mutable T blackhole_;
 
  public:
   using iterator_category = std::output_iterator_tag;
-  using value_type = T;
   using difference_type = std::ptrdiff_t;
-  using pointer = T*;
-  using reference = T&;
+  using pointer = void;
+  using reference = void;
   using _Unchecked_type = counting_iterator;  // Mark iterator as checked.
 
+  struct value_type {
+    template <typename T> void operator=(const T&) {}
+  };
+
   counting_iterator() : count_(0) {}
 
   std::size_t count() const { return count_; }
@@ -318,7 +364,7 @@
     return it;
   }
 
-  T& operator*() const { return blackhole_; }
+  value_type operator*() const { return {}; }
 };
 
 template <typename OutputIt> class truncating_iterator_base {
@@ -413,17 +459,6 @@
   sentinel end() const { return {}; }  // Sentinel is not used yet.
 };
 
-// A range with an iterator appending to a buffer.
-template <typename T>
-class buffer_range
-    : public output_range<std::back_insert_iterator<buffer<T>>, T> {
- public:
-  using iterator = std::back_insert_iterator<buffer<T>>;
-  using output_range<iterator, T>::output_range;
-  buffer_range(buffer<T>& buf)
-      : output_range<iterator, T>(std::back_inserter(buf)) {}
-};
-
 template <typename Char>
 inline size_t count_code_points(basic_string_view<Char> s) {
   return s.size();
@@ -439,6 +474,24 @@
   return num_code_points;
 }
 
+template <typename Char>
+inline size_t code_point_index(basic_string_view<Char> s, size_t n) {
+  size_t size = s.size();
+  return n < size ? n : size;
+}
+
+// Calculates the index of the nth code point in a UTF-8 string.
+inline size_t code_point_index(basic_string_view<char8_t> s, size_t n) {
+  const char8_t* data = s.data();
+  size_t num_code_points = 0;
+  for (size_t i = 0, size = s.size(); i != size; ++i) {
+    if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) {
+      return i;
+    }
+  }
+  return s.size();
+}
+
 inline char8_t to_char8_t(char c) { return static_cast<char8_t>(c); }
 
 template <typename InputIt, typename OutChar>
@@ -460,7 +513,7 @@
 }
 
 #ifndef FMT_USE_GRISU
-#  define FMT_USE_GRISU 0
+#  define FMT_USE_GRISU 1
 #endif
 
 template <typename T> constexpr bool use_grisu() {
@@ -478,6 +531,17 @@
 }
 }  // namespace internal
 
+// A range with an iterator appending to a buffer.
+template <typename T>
+class buffer_range : public internal::output_range<
+                         std::back_insert_iterator<internal::buffer<T>>, T> {
+ public:
+  using iterator = std::back_insert_iterator<internal::buffer<T>>;
+  using internal::output_range<iterator, T>::output_range;
+  buffer_range(internal::buffer<T>& buf)
+      : internal::output_range<iterator, T>(std::back_inserter(buf)) {}
+};
+
 // A UTF-8 string view.
 class u8string_view : public basic_string_view<char8_t> {
  public:
@@ -552,7 +616,7 @@
       : Allocator(alloc) {
     this->set(store_, SIZE);
   }
-  ~basic_memory_buffer() { deallocate(); }
+  ~basic_memory_buffer() FMT_OVERRIDE { deallocate(); }
 
  private:
   // Move data from other to this buffer.
@@ -581,15 +645,15 @@
     of the other object to it.
     \endrst
    */
-  basic_memory_buffer(basic_memory_buffer&& other) { move(other); }
+  basic_memory_buffer(basic_memory_buffer&& other) FMT_NOEXCEPT { move(other); }
 
   /**
     \rst
     Moves the content of the other ``basic_memory_buffer`` object to this one.
     \endrst
    */
-  basic_memory_buffer& operator=(basic_memory_buffer&& other) {
-    assert(this != &other);
+  basic_memory_buffer& operator=(basic_memory_buffer&& other) FMT_NOEXCEPT {
+    FMT_ASSERT(this != &other, "");
     deallocate();
     move(other);
     return *this;
@@ -628,7 +692,11 @@
   explicit format_error(const char* message) : std::runtime_error(message) {}
   explicit format_error(const std::string& message)
       : std::runtime_error(message) {}
-  ~format_error() FMT_NOEXCEPT;
+  format_error(const format_error&) = default;
+  format_error& operator=(const format_error&) = default;
+  format_error(format_error&&) = default;
+  format_error& operator=(format_error&&) = default;
+  ~format_error() FMT_NOEXCEPT FMT_OVERRIDE;
 };
 
 namespace internal {
@@ -644,11 +712,12 @@
   return false;
 }
 
-// Smallest of uint32_t and uint64_t that is large enough to represent all
-// values of T.
+// Smallest of uint32_t, uint64_t, uint128_t that is large enough to
+// represent all values of T.
 template <typename T>
-using uint32_or_64_t =
-    conditional_t<std::numeric_limits<T>::digits <= 32, uint32_t, uint64_t>;
+using uint32_or_64_or_128_t = conditional_t<
+    std::numeric_limits<T>::digits <= 32, uint32_t,
+    conditional_t<std::numeric_limits<T>::digits <= 64, uint64_t, uint128_t>>;
 
 // Static data is placed in this class template for the header-only config.
 template <typename T = void> struct FMT_EXTERN_TEMPLATE_API basic_data {
@@ -663,6 +732,7 @@
   static const char background_color[];
   static const char reset_color[5];
   static const wchar_t wreset_color[5];
+  static const char signs[];
 };
 
 FMT_EXTERN template struct basic_data<void>;
@@ -697,6 +767,23 @@
 }
 #endif
 
+#if FMT_USE_INT128
+inline int count_digits(uint128_t n) {
+  int count = 1;
+  for (;;) {
+    // Integer division is slow so do it for a group of four digits instead
+    // of for every digit. The idea comes from the talk by Alexandrescu
+    // "Three Optimization Tips for C++". See speed-test for a comparison.
+    if (n < 10) return count;
+    if (n < 100) return count + 1;
+    if (n < 1000) return count + 2;
+    if (n < 10000) return count + 3;
+    n /= 10000U;
+    count += 4;
+  }
+}
+#endif
+
 // Counts the number of digits in n. BITS = log2(radix).
 template <unsigned BITS, typename UInt> inline int count_digits(UInt n) {
   int num_digits = 0;
@@ -708,17 +795,14 @@
 
 template <> int count_digits<4>(internal::fallback_uintptr n);
 
-#if FMT_HAS_CPP_ATTRIBUTE(always_inline)
-#  define FMT_ALWAYS_INLINE __attribute__((always_inline))
+#if FMT_GCC_VERSION || FMT_CLANG_VERSION
+#  define FMT_ALWAYS_INLINE inline __attribute__((always_inline))
 #else
 #  define FMT_ALWAYS_INLINE
 #endif
 
-template <typename Handler>
-inline char* lg(uint32_t n, Handler h) FMT_ALWAYS_INLINE;
-
 // Computes g = floor(log10(n)) and calls h.on<g>(n);
-template <typename Handler> inline char* lg(uint32_t n, Handler h) {
+template <typename Handler> FMT_ALWAYS_INLINE char* lg(uint32_t n, Handler h) {
   return n < 100 ? n < 10 ? h.template on<0>(n) : h.template on<1>(n)
                  : n < 1000000
                        ? n < 10000 ? n < 1000 ? h.template on<2>(n)
@@ -779,6 +863,14 @@
 }
 #endif
 
+template <typename Char> FMT_API std::string grouping_impl(locale_ref loc);
+template <typename Char> inline std::string grouping(locale_ref loc) {
+  return grouping_impl<char>(loc);
+}
+template <> inline std::string grouping<wchar_t>(locale_ref loc) {
+  return grouping_impl<wchar_t>(loc);
+}
+
 template <typename Char> FMT_API Char thousands_sep_impl(locale_ref loc);
 template <typename Char> inline Char thousands_sep(locale_ref loc) {
   return Char(thousands_sep_impl<char>(loc));
@@ -808,7 +900,7 @@
     // Integer division is slow so do it for a group of two digits instead
     // of for every digit. The idea comes from the talk by Alexandrescu
     // "Three Optimization Tips for C++". See speed-test for a comparison.
-    unsigned index = static_cast<unsigned>((value % 100) * 2);
+    auto index = static_cast<unsigned>((value % 100) * 2);
     value /= 100;
     *--buffer = static_cast<Char>(data::digits[index + 1]);
     add_thousands_sep(buffer);
@@ -819,20 +911,26 @@
     *--buffer = static_cast<Char>('0' + value);
     return end;
   }
-  unsigned index = static_cast<unsigned>(value * 2);
+  auto index = static_cast<unsigned>(value * 2);
   *--buffer = static_cast<Char>(data::digits[index + 1]);
   add_thousands_sep(buffer);
   *--buffer = static_cast<Char>(data::digits[index]);
   return end;
 }
 
+template <typename Int> constexpr int digits10() noexcept {
+  return std::numeric_limits<Int>::digits10;
+}
+template <> constexpr int digits10<int128_t>() noexcept { return 38; }
+template <> constexpr int digits10<uint128_t>() noexcept { return 38; }
+
 template <typename Char, typename UInt, typename Iterator, typename F>
 inline Iterator format_decimal(Iterator out, UInt value, int num_digits,
                                F add_thousands_sep) {
   FMT_ASSERT(num_digits >= 0, "invalid digit count");
   // Buffer should be large enough to hold all digits (<= digits10 + 1).
-  enum { max_size = std::numeric_limits<UInt>::digits10 + 1 };
-  Char buffer[max_size + max_size / 3];
+  enum { max_size = digits10<UInt>() + 1 };
+  Char buffer[2 * max_size];
   auto end = format_decimal(buffer, value, num_digits, add_thousands_sep);
   return internal::copy_str<Char>(buffer, end, out);
 }
@@ -881,7 +979,7 @@
 template <unsigned BASE_BITS, typename Char, typename It, typename UInt>
 inline It format_uint(It out, UInt value, int num_digits, bool upper = false) {
   // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1).
-  char buffer[std::numeric_limits<UInt>::digits / BASE_BITS + 1];
+  char buffer[num_bits<UInt>() / BASE_BITS + 1];
   format_uint<BASE_BITS>(buffer, value, num_digits, upper);
   return internal::copy_str<Char>(buffer, buffer + num_digits, out);
 }
@@ -929,9 +1027,8 @@
   FMT_API int convert(wstring_view s);
 };
 
-FMT_API void format_windows_error(fmt::internal::buffer<char>& out,
-                                  int error_code,
-                                  fmt::string_view message) FMT_NOEXCEPT;
+FMT_API void format_windows_error(internal::buffer<char>& out, int error_code,
+                                  string_view message) FMT_NOEXCEPT;
 #endif
 
 template <typename T = void> struct null {};
@@ -991,9 +1088,29 @@
 
 namespace internal {
 
+// A floating-point presentation format.
+enum class float_format : unsigned char {
+  general,  // General: exponent notation or fixed point based on magnitude.
+  exp,      // Exponent notation with the default precision of 6, e.g. 1.2e-3.
+  fixed,    // Fixed point with the default precision of 6, e.g. 0.0012.
+  hex
+};
+
+struct float_specs {
+  int precision;
+  float_format format : 8;
+  sign_t sign : 8;
+  bool upper : 1;
+  bool locale : 1;
+  bool percent : 1;
+  bool binary32 : 1;
+  bool use_grisu : 1;
+  bool trailing_zeros : 1;
+};
+
 // Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
 template <typename Char, typename It> It write_exponent(int exp, It it) {
-  FMT_ASSERT(-1000 < exp && exp < 1000, "exponent out of range");
+  FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range");
   if (exp < 0) {
     *it++ = static_cast<Char>('-');
     exp = -exp;
@@ -1001,7 +1118,9 @@
     *it++ = static_cast<Char>('+');
   }
   if (exp >= 100) {
-    *it++ = static_cast<Char>(static_cast<char>('0' + exp / 100));
+    const char* top = data::digits + (exp / 100) * 2;
+    if (exp >= 1000) *it++ = static_cast<Char>(top[0]);
+    *it++ = static_cast<Char>(top[1]);
     exp %= 100;
   }
   const char* d = data::digits + exp * 2;
@@ -1010,103 +1129,122 @@
   return it;
 }
 
-struct gen_digits_params {
-  int num_digits;
-  bool fixed;
-  bool upper;
-  bool trailing_zeros;
-};
+template <typename Char> class float_writer {
+ private:
+  // The number is given as v = digits_ * pow(10, exp_).
+  const char* digits_;
+  int num_digits_;
+  int exp_;
+  size_t size_;
+  float_specs specs_;
+  Char decimal_point_;
 
-// The number is given as v = digits * pow(10, exp).
-template <typename Char, typename It>
-It grisu_prettify(const char* digits, int size, int exp, It it,
-                  gen_digits_params params, Char decimal_point) {
-  // pow(10, full_exp - 1) <= v <= pow(10, full_exp).
-  int full_exp = size + exp;
-  if (!params.fixed) {
-    // Insert a decimal point after the first digit and add an exponent.
-    *it++ = static_cast<Char>(*digits);
-    if (size > 1) *it++ = decimal_point;
-    exp += size - 1;
-    it = copy_str<Char>(digits + 1, digits + size, it);
-    if (size < params.num_digits)
-      it = std::fill_n(it, params.num_digits - size, static_cast<Char>('0'));
-    *it++ = static_cast<Char>(params.upper ? 'E' : 'e');
-    return write_exponent<Char>(exp, it);
-  }
-  if (size <= full_exp) {
-    // 1234e7 -> 12340000000[.0+]
-    it = copy_str<Char>(digits, digits + size, it);
-    it = std::fill_n(it, full_exp - size, static_cast<Char>('0'));
-    int num_zeros = (std::max)(params.num_digits - full_exp, 1);
-    if (params.trailing_zeros) {
-      *it++ = decimal_point;
+  template <typename It> It prettify(It it) const {
+    // pow(10, full_exp - 1) <= v <= pow(10, full_exp).
+    int full_exp = num_digits_ + exp_;
+    if (specs_.format == float_format::exp) {
+      // Insert a decimal point after the first digit and add an exponent.
+      *it++ = static_cast<Char>(*digits_);
+      int num_zeros = specs_.precision - num_digits_;
+      bool trailing_zeros = num_zeros > 0 && specs_.trailing_zeros;
+      if (num_digits_ > 1 || trailing_zeros) *it++ = decimal_point_;
+      it = copy_str<Char>(digits_ + 1, digits_ + num_digits_, it);
+      if (trailing_zeros)
+        it = std::fill_n(it, num_zeros, static_cast<Char>('0'));
+      *it++ = static_cast<Char>(specs_.upper ? 'E' : 'e');
+      return write_exponent<Char>(full_exp - 1, it);
+    }
+    if (num_digits_ <= full_exp) {
+      // 1234e7 -> 12340000000[.0+]
+      it = copy_str<Char>(digits_, digits_ + num_digits_, it);
+      it = std::fill_n(it, full_exp - num_digits_, static_cast<Char>('0'));
+      if (specs_.trailing_zeros) {
+        *it++ = decimal_point_;
+        int num_zeros = specs_.precision - full_exp;
+        if (num_zeros <= 0) {
+          if (specs_.format != float_format::fixed)
+            *it++ = static_cast<Char>('0');
+          return it;
+        }
 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
-      if (num_zeros > 1000)
-        throw std::runtime_error("fuzz mode - avoiding excessive cpu use");
+        if (num_zeros > 1000)
+          throw std::runtime_error("fuzz mode - avoiding excessive cpu use");
 #endif
-      it = std::fill_n(it, num_zeros, static_cast<Char>('0'));
+        it = std::fill_n(it, num_zeros, static_cast<Char>('0'));
+      }
+    } else if (full_exp > 0) {
+      // 1234e-2 -> 12.34[0+]
+      it = copy_str<Char>(digits_, digits_ + full_exp, it);
+      if (!specs_.trailing_zeros) {
+        // Remove trailing zeros.
+        int num_digits = num_digits_;
+        while (num_digits > full_exp && digits_[num_digits - 1] == '0')
+          --num_digits;
+        if (num_digits != full_exp) *it++ = decimal_point_;
+        return copy_str<Char>(digits_ + full_exp, digits_ + num_digits, it);
+      }
+      *it++ = decimal_point_;
+      it = copy_str<Char>(digits_ + full_exp, digits_ + num_digits_, it);
+      if (specs_.precision > num_digits_) {
+        // Add trailing zeros.
+        int num_zeros = specs_.precision - num_digits_;
+        it = std::fill_n(it, num_zeros, static_cast<Char>('0'));
+      }
+    } else {
+      // 1234e-6 -> 0.001234
+      *it++ = static_cast<Char>('0');
+      int num_zeros = -full_exp;
+      if (specs_.precision >= 0 && specs_.precision < num_zeros)
+        num_zeros = specs_.precision;
+      int num_digits = num_digits_;
+      if (!specs_.trailing_zeros)
+        while (num_digits > 0 && digits_[num_digits - 1] == '0') --num_digits;
+      if (num_zeros != 0 || num_digits != 0) {
+        *it++ = decimal_point_;
+        it = std::fill_n(it, num_zeros, static_cast<Char>('0'));
+        it = copy_str<Char>(digits_, digits_ + num_digits, it);
+      }
     }
-  } else if (full_exp > 0) {
-    // 1234e-2 -> 12.34[0+]
-    it = copy_str<Char>(digits, digits + full_exp, it);
-    if (!params.trailing_zeros) {
-      // Remove trailing zeros.
-      while (size > full_exp && digits[size - 1] == '0') --size;
-      if (size != full_exp) *it++ = decimal_point;
-      return copy_str<Char>(digits + full_exp, digits + size, it);
-    }
-    *it++ = decimal_point;
-    it = copy_str<Char>(digits + full_exp, digits + size, it);
-    if (params.num_digits > size) {
-      // Add trailing zeros.
-      int num_zeros = params.num_digits - size;
-      it = std::fill_n(it, num_zeros, static_cast<Char>('0'));
-    }
-  } else {
-    // 1234e-6 -> 0.001234
-    *it++ = static_cast<Char>('0');
-    int num_zeros = -full_exp;
-    if (params.num_digits >= 0 && params.num_digits < num_zeros)
-      num_zeros = params.num_digits;
-    if (!params.trailing_zeros)
-      while (size > 0 && digits[size - 1] == '0') --size;
-    if (num_zeros != 0 || size != 0) {
-      *it++ = decimal_point;
-      it = std::fill_n(it, num_zeros, static_cast<Char>('0'));
-      it = copy_str<Char>(digits, digits + size, it);
-    }
+    return it;
   }
-  return it;
-}
 
-namespace grisu_options {
-enum { fixed = 1, grisu3 = 2 };
-}
+ public:
+  float_writer(const char* digits, int num_digits, int exp, float_specs specs,
+               Char decimal_point)
+      : digits_(digits),
+        num_digits_(num_digits),
+        exp_(exp),
+        specs_(specs),
+        decimal_point_(decimal_point) {
+    int full_exp = num_digits + exp - 1;
+    int precision = specs.precision > 0 ? specs.precision : 16;
+    if (specs_.format == float_format::general &&
+        !(full_exp >= -4 && full_exp < precision)) {
+      specs_.format = float_format::exp;
+    }
+    size_ = prettify(counting_iterator()).count();
+    size_ += specs.sign ? 1 : 0;
+  }
 
-// Formats value using the Grisu algorithm:
-// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf
-template <typename Double, FMT_ENABLE_IF(sizeof(Double) == sizeof(uint64_t))>
-FMT_API bool grisu_format(Double, buffer<char>&, int, unsigned, int&);
-template <typename Double, FMT_ENABLE_IF(sizeof(Double) != sizeof(uint64_t))>
-inline bool grisu_format(Double, buffer<char>&, int, unsigned, int&) {
-  return false;
-}
+  size_t size() const { return size_; }
+  size_t width() const { return size(); }
 
-struct sprintf_specs {
-  int precision;
-  char type;
-  bool alt : 1;
-
-  template <typename Char>
-  constexpr sprintf_specs(basic_format_specs<Char> specs)
-      : precision(specs.precision), type(specs.type), alt(specs.alt) {}
-
-  constexpr bool has_precision() const { return precision >= 0; }
+  template <typename It> void operator()(It&& it) {
+    if (specs_.sign) *it++ = static_cast<Char>(data::signs[specs_.sign]);
+    it = prettify(it);
+  }
 };
 
-template <typename Double>
-char* sprintf_format(Double, internal::buffer<char>&, sprintf_specs);
+template <typename T>
+int format_float(T value, int precision, float_specs specs, buffer<char>& buf);
+
+// Formats a floating-point number with snprintf.
+template <typename T>
+int snprintf_float(T value, int precision, float_specs specs,
+                   buffer<char>& buf);
+
+template <typename T> T promote_float(T value) { return value; }
+inline double promote_float(float value) { return value; }
 
 template <typename Handler>
 FMT_CONSTEXPR void handle_int_type_spec(char spec, Handler&& handler) {
@@ -1134,36 +1272,56 @@
   }
 }
 
-template <typename Handler>
-FMT_CONSTEXPR void handle_float_type_spec(char spec, Handler&& handler) {
-  switch (spec) {
+template <typename ErrorHandler = error_handler, typename Char>
+FMT_CONSTEXPR float_specs parse_float_type_spec(
+    const basic_format_specs<Char>& specs, ErrorHandler&& eh = {}) {
+  auto result = float_specs();
+  result.trailing_zeros = specs.alt;
+  switch (specs.type) {
   case 0:
-  case 'g':
+    result.format = float_format::general;
+    result.trailing_zeros |= specs.precision != 0;
+    break;
   case 'G':
-    handler.on_general();
+    result.upper = true;
+    FMT_FALLTHROUGH;
+  case 'g':
+    result.format = float_format::general;
     break;
-  case 'e':
   case 'E':
-    handler.on_exp();
+    result.upper = true;
+    FMT_FALLTHROUGH;
+  case 'e':
+    result.format = float_format::exp;
+    result.trailing_zeros |= specs.precision != 0;
     break;
-  case 'f':
   case 'F':
-    handler.on_fixed();
+    result.upper = true;
+    FMT_FALLTHROUGH;
+  case 'f':
+    result.format = float_format::fixed;
+    result.trailing_zeros |= specs.precision != 0;
     break;
+#if FMT_DEPRECATED_PERCENT
   case '%':
-    handler.on_percent();
+    result.format = float_format::fixed;
+    result.percent = true;
     break;
-  case 'a':
+#endif
   case 'A':
-    handler.on_hex();
+    result.upper = true;
+    FMT_FALLTHROUGH;
+  case 'a':
+    result.format = float_format::hex;
     break;
   case 'n':
-    handler.on_num();
+    result.locale = true;
     break;
   default:
-    handler.on_error();
+    eh.on_error("invalid type specifier");
     break;
   }
+  return result;
 }
 
 template <typename Char, typename Handler>
@@ -1212,24 +1370,6 @@
 };
 
 template <typename ErrorHandler>
-class float_type_checker : private ErrorHandler {
- public:
-  FMT_CONSTEXPR explicit float_type_checker(ErrorHandler eh)
-      : ErrorHandler(eh) {}
-
-  FMT_CONSTEXPR void on_general() {}
-  FMT_CONSTEXPR void on_exp() {}
-  FMT_CONSTEXPR void on_fixed() {}
-  FMT_CONSTEXPR void on_percent() {}
-  FMT_CONSTEXPR void on_hex() {}
-  FMT_CONSTEXPR void on_num() {}
-
-  FMT_CONSTEXPR void on_error() {
-    ErrorHandler::on_error("invalid type specifier");
-  }
-};
-
-template <typename ErrorHandler>
 class char_specs_checker : public ErrorHandler {
  private:
   char type_;
@@ -1271,6 +1411,20 @@
   }
 }
 
+template <typename Char> struct nonfinite_writer {
+  sign_t sign;
+  const char* str;
+  static constexpr size_t str_size = 3;
+
+  size_t size() const { return str_size + (sign ? 1 : 0); }
+  size_t width() const { return size(); }
+
+  template <typename It> void operator()(It&& it) const {
+    if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+    it = copy_str<Char>(str, str + str_size, it);
+  }
+};
+
 // This template provides operations for formatting and writing data into a
 // character range.
 template <typename Range> class basic_writer {
@@ -1281,7 +1435,7 @@
 
  private:
   iterator out_;  // Output iterator.
-  internal::locale_ref locale_;
+  locale_ref locale_;
 
   // Attempts to reserve space for n extra characters in the output range.
   // Returns a pointer to the reserved range or a reference to out_.
@@ -1301,7 +1455,7 @@
 
     template <typename It> void operator()(It&& it) const {
       if (prefix.size() != 0)
-        it = internal::copy_str<char_type>(prefix.begin(), prefix.end(), it);
+        it = copy_str<char_type>(prefix.begin(), prefix.end(), it);
       it = std::fill_n(it, padding, fill);
       f(it);
     }
@@ -1312,18 +1466,18 @@
   // where <digits> are written by f(it).
   template <typename F>
   void write_int(int num_digits, string_view prefix, format_specs specs, F f) {
-    std::size_t size = prefix.size() + internal::to_unsigned(num_digits);
+    std::size_t size = prefix.size() + to_unsigned(num_digits);
     char_type fill = specs.fill[0];
     std::size_t padding = 0;
     if (specs.align == align::numeric) {
-      auto unsiged_width = internal::to_unsigned(specs.width);
+      auto unsiged_width = to_unsigned(specs.width);
       if (unsiged_width > size) {
         padding = unsiged_width - size;
         size = unsiged_width;
       }
     } else if (specs.precision > num_digits) {
-      size = prefix.size() + internal::to_unsigned(specs.precision);
-      padding = internal::to_unsigned(specs.precision - num_digits);
+      size = prefix.size() + to_unsigned(specs.precision);
+      padding = to_unsigned(specs.precision - num_digits);
       fill = static_cast<char_type>('0');
     }
     if (specs.align == align::none) specs.align = align::right;
@@ -1332,19 +1486,19 @@
 
   // Writes a decimal integer.
   template <typename Int> void write_decimal(Int value) {
-    auto abs_value = static_cast<uint32_or_64_t<Int>>(value);
-    bool is_negative = internal::is_negative(value);
-    if (is_negative) abs_value = 0 - abs_value;
-    int num_digits = internal::count_digits(abs_value);
-    auto&& it =
-        reserve((is_negative ? 1 : 0) + static_cast<size_t>(num_digits));
-    if (is_negative) *it++ = static_cast<char_type>('-');
-    it = internal::format_decimal<char_type>(it, abs_value, num_digits);
+    auto abs_value = static_cast<uint32_or_64_or_128_t<Int>>(value);
+    bool negative = is_negative(value);
+    // Don't do -abs_value since it trips unsigned-integer-overflow sanitizer.
+    if (negative) abs_value = ~abs_value + 1;
+    int num_digits = count_digits(abs_value);
+    auto&& it = reserve((negative ? 1 : 0) + static_cast<size_t>(num_digits));
+    if (negative) *it++ = static_cast<char_type>('-');
+    it = format_decimal<char_type>(it, abs_value, num_digits);
   }
 
   // The handle_int_type_spec handler that writes an integer.
   template <typename Int, typename Specs> struct int_writer {
-    using unsigned_type = uint32_or_64_t<Int>;
+    using unsigned_type = uint32_or_64_or_128_t<Int>;
 
     basic_writer<Range>& writer;
     const Specs& specs;
@@ -1359,7 +1513,7 @@
           specs(s),
           abs_value(static_cast<unsigned_type>(value)),
           prefix_size(0) {
-      if (internal::is_negative(value)) {
+      if (is_negative(value)) {
         prefix[0] = '-';
         ++prefix_size;
         abs_value = 0 - abs_value;
@@ -1379,7 +1533,7 @@
     };
 
     void on_dec() {
-      int num_digits = internal::count_digits(abs_value);
+      int num_digits = count_digits(abs_value);
       writer.write_int(num_digits, get_prefix(), specs,
                        dec_writer{abs_value, num_digits});
     }
@@ -1389,8 +1543,8 @@
       int num_digits;
 
       template <typename It> void operator()(It&& it) const {
-        it = internal::format_uint<4, char_type>(it, self.abs_value, num_digits,
-                                                 self.specs.type != 'x');
+        it = format_uint<4, char_type>(it, self.abs_value, num_digits,
+                                       self.specs.type != 'x');
       }
     };
 
@@ -1399,7 +1553,7 @@
         prefix[prefix_size++] = '0';
         prefix[prefix_size++] = specs.type;
       }
-      int num_digits = internal::count_digits<4>(abs_value);
+      int num_digits = count_digits<4>(abs_value);
       writer.write_int(num_digits, get_prefix(), specs,
                        hex_writer{*this, num_digits});
     }
@@ -1409,7 +1563,7 @@
       int num_digits;
 
       template <typename It> void operator()(It&& it) const {
-        it = internal::format_uint<BITS, char_type>(it, abs_value, num_digits);
+        it = format_uint<BITS, char_type>(it, abs_value, num_digits);
       }
     };
 
@@ -1418,14 +1572,14 @@
         prefix[prefix_size++] = '0';
         prefix[prefix_size++] = static_cast<char>(specs.type);
       }
-      int num_digits = internal::count_digits<1>(abs_value);
+      int num_digits = count_digits<1>(abs_value);
       writer.write_int(num_digits, get_prefix(), specs,
                        bin_writer<1>{abs_value, num_digits});
     }
 
     void on_oct() {
-      int num_digits = internal::count_digits<3>(abs_value);
-      if (specs.alt && specs.precision <= num_digits) {
+      int num_digits = count_digits<3>(abs_value);
+      if (specs.alt && specs.precision <= num_digits && abs_value != 0) {
         // Octal prefix '0' is counted as a digit, so only add it if precision
         // is not greater than the number of digits.
         prefix[prefix_size++] = '0';
@@ -1439,30 +1593,50 @@
     struct num_writer {
       unsigned_type abs_value;
       int size;
+      const std::string& groups;
       char_type sep;
 
       template <typename It> void operator()(It&& it) const {
         basic_string_view<char_type> s(&sep, sep_size);
         // Index of a decimal digit with the least significant digit having
         // index 0.
-        unsigned digit_index = 0;
-        it = internal::format_decimal<char_type>(
-            it, abs_value, size, [s, &digit_index](char_type*& buffer) {
-              if (++digit_index % 3 != 0) return;
+        int digit_index = 0;
+        std::string::const_iterator group = groups.cbegin();
+        it = format_decimal<char_type>(
+            it, abs_value, size,
+            [this, s, &group, &digit_index](char_type*& buffer) {
+              if (*group <= 0 || ++digit_index % *group != 0 ||
+                  *group == max_value<char>())
+                return;
+              if (group + 1 != groups.cend()) {
+                digit_index = 0;
+                ++group;
+              }
               buffer -= s.size();
               std::uninitialized_copy(s.data(), s.data() + s.size(),
-                                      internal::make_checked(buffer, s.size()));
+                                      make_checked(buffer, s.size()));
             });
       }
     };
 
     void on_num() {
-      char_type sep = internal::thousands_sep<char_type>(writer.locale_);
+      std::string groups = grouping<char_type>(writer.locale_);
+      if (groups.empty()) return on_dec();
+      auto sep = thousands_sep<char_type>(writer.locale_);
       if (!sep) return on_dec();
-      int num_digits = internal::count_digits(abs_value);
-      int size = num_digits + sep_size * ((num_digits - 1) / 3);
+      int num_digits = count_digits(abs_value);
+      int size = num_digits;
+      std::string::const_iterator group = groups.cbegin();
+      while (group != groups.cend() && num_digits > *group && *group > 0 &&
+             *group != max_value<char>()) {
+        size += sep_size;
+        num_digits -= *group;
+        ++group;
+      }
+      if (group == groups.cend())
+        size += sep_size * ((num_digits - 1) / groups.back());
       writer.write_int(size, get_prefix(), specs,
-                       num_writer{abs_value, size, sep});
+                       num_writer{abs_value, size, groups, sep});
     }
 
     FMT_NORETURN void on_error() {
@@ -1470,98 +1644,17 @@
     }
   };
 
-  enum { inf_size = 3 };  // This is an enum to workaround a bug in MSVC.
-
-  struct inf_or_nan_writer {
-    char sign;
-    bool as_percentage;
-    const char* str;
-
-    size_t size() const {
-      return static_cast<std::size_t>(inf_size + (sign ? 1 : 0) +
-                                      (as_percentage ? 1 : 0));
-    }
-    size_t width() const { return size(); }
-
-    template <typename It> void operator()(It&& it) const {
-      if (sign) *it++ = static_cast<char_type>(sign);
-      it = internal::copy_str<char_type>(
-          str, str + static_cast<std::size_t>(inf_size), it);
-      if (as_percentage) *it++ = static_cast<char_type>('%');
-    }
-  };
-
-  struct double_writer {
-    char sign;
-    internal::buffer<char>& buffer;
-    char* decimal_point_pos;
-    char_type decimal_point;
-
-    size_t size() const { return buffer.size() + (sign ? 1 : 0); }
-    size_t width() const { return size(); }
-
-    template <typename It> void operator()(It&& it) {
-      if (sign) *it++ = static_cast<char_type>(sign);
-      auto begin = buffer.begin();
-      if (decimal_point_pos) {
-        it = internal::copy_str<char_type>(begin, decimal_point_pos, it);
-        *it++ = decimal_point;
-        begin = decimal_point_pos + 1;
-      }
-      it = internal::copy_str<char_type>(begin, buffer.end(), it);
-    }
-  };
-
-  class grisu_writer {
-   private:
-    internal::buffer<char>& digits_;
-    size_t size_;
-    char sign_;
-    int exp_;
-    internal::gen_digits_params params_;
-    char_type decimal_point_;
-
-   public:
-    grisu_writer(char sign, internal::buffer<char>& digits, int exp,
-                 const internal::gen_digits_params& params,
-                 char_type decimal_point)
-        : digits_(digits),
-          sign_(sign),
-          exp_(exp),
-          params_(params),
-          decimal_point_(decimal_point) {
-      int num_digits = static_cast<int>(digits.size());
-      int full_exp = num_digits + exp - 1;
-      int precision = params.num_digits > 0 ? params.num_digits : 11;
-      params_.fixed |= full_exp >= -4 && full_exp < precision;
-      auto it = internal::grisu_prettify<char>(
-          digits.data(), num_digits, exp, internal::counting_iterator<char>(),
-          params_, '.');
-      size_ = it.count();
-    }
-
-    size_t size() const { return size_ + (sign_ ? 1 : 0); }
-    size_t width() const { return size(); }
-
-    template <typename It> void operator()(It&& it) {
-      if (sign_) *it++ = static_cast<char_type>(sign_);
-      int num_digits = static_cast<int>(digits_.size());
-      it = internal::grisu_prettify<char_type>(digits_.data(), num_digits, exp_,
-                                               it, params_, decimal_point_);
-    }
-  };
-
   template <typename Char> struct str_writer {
     const Char* s;
     size_t size_;
 
     size_t size() const { return size_; }
     size_t width() const {
-      return internal::count_code_points(basic_string_view<Char>(s, size_));
+      return count_code_points(basic_string_view<Char>(s, size_));
     }
 
     template <typename It> void operator()(It&& it) const {
-      it = internal::copy_str<char_type>(s, s + size_, it);
+      it = copy_str<char_type>(s, s + size_, it);
     }
   };
 
@@ -1575,14 +1668,12 @@
     template <typename It> void operator()(It&& it) const {
       *it++ = static_cast<char_type>('0');
       *it++ = static_cast<char_type>('x');
-      it = internal::format_uint<4, char_type>(it, value, num_digits);
+      it = format_uint<4, char_type>(it, value, num_digits);
     }
   };
 
  public:
-  /** Constructs a ``basic_writer`` object. */
-  explicit basic_writer(Range out,
-                        internal::locale_ref loc = internal::locale_ref())
+  explicit basic_writer(Range out, locale_ref loc = locale_ref())
       : out_(out.begin()), locale_(loc) {}
 
   iterator out() const { return out_; }
@@ -1621,32 +1712,70 @@
   void write(unsigned long value) { write_decimal(value); }
   void write(unsigned long long value) { write_decimal(value); }
 
-  // Writes a formatted integer.
+#if FMT_USE_INT128
+  void write(int128_t value) { write_decimal(value); }
+  void write(uint128_t value) { write_decimal(value); }
+#endif
+
   template <typename T, typename Spec>
   void write_int(T value, const Spec& spec) {
-    internal::handle_int_type_spec(spec.type,
-                                   int_writer<T, Spec>(*this, value, spec));
+    handle_int_type_spec(spec.type, int_writer<T, Spec>(*this, value, spec));
   }
 
-  void write(double value, const format_specs& specs = format_specs()) {
-    write_double(value, specs);
+  template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+  void write(T value, format_specs specs = {}) {
+    float_specs fspecs = parse_float_type_spec(specs);
+    fspecs.sign = specs.sign;
+    if (std::signbit(value)) {  // value < 0 is false for NaN so use signbit.
+      fspecs.sign = sign::minus;
+      value = -value;
+    } else if (fspecs.sign == sign::minus) {
+      fspecs.sign = sign::none;
+    }
+
+    if (!std::isfinite(value)) {
+      auto str = std::isinf(value) ? (fspecs.upper ? "INF" : "inf")
+                                   : (fspecs.upper ? "NAN" : "nan");
+      return write_padded(specs, nonfinite_writer<char_type>{fspecs.sign, str});
+    }
+
+    if (specs.align == align::none) {
+      specs.align = align::right;
+    } else if (specs.align == align::numeric) {
+      if (fspecs.sign) {
+        auto&& it = reserve(1);
+        *it++ = static_cast<char_type>(data::signs[fspecs.sign]);
+        fspecs.sign = sign::none;
+        if (specs.width != 0) --specs.width;
+      }
+      specs.align = align::right;
+    }
+
+    memory_buffer buffer;
+    if (fspecs.format == float_format::hex) {
+      if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]);
+      snprintf_float(promote_float(value), specs.precision, fspecs, buffer);
+      write_padded(specs, str_writer<char>{buffer.data(), buffer.size()});
+      return;
+    }
+    int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6;
+    if (fspecs.format == float_format::exp) ++precision;
+    if (const_check(std::is_same<T, float>())) fspecs.binary32 = true;
+    fspecs.use_grisu = use_grisu<T>();
+    if (const_check(FMT_DEPRECATED_PERCENT) && fspecs.percent) value *= 100;
+    int exp = format_float(promote_float(value), precision, fspecs, buffer);
+    if (const_check(FMT_DEPRECATED_PERCENT) && fspecs.percent) {
+      buffer.push_back('%');
+      --exp;  // Adjust decimal place position.
+    }
+    fspecs.precision = precision;
+    char_type point = fspecs.locale ? decimal_point<char_type>(locale_)
+                                    : static_cast<char_type>('.');
+    write_padded(specs, float_writer<char_type>(buffer.data(),
+                                                static_cast<int>(buffer.size()),
+                                                exp, fspecs, point));
   }
 
-  /**
-    \rst
-    Formats *value* using the general format for floating-point numbers
-    (``'g'``) and writes it to the buffer.
-    \endrst
-   */
-  void write(long double value, const format_specs& specs = format_specs()) {
-    write_double(value, specs);
-  }
-
-  // Formats a floating-point number (double or long double).
-  template <typename T, bool USE_GRISU = fmt::internal::use_grisu<T>()>
-  void write_double(T value, const format_specs& specs);
-
-  /** Writes a character to the buffer. */
   void write(char value) {
     auto&& it = reserve(1);
     *it++ = value;
@@ -1658,14 +1787,9 @@
     *it++ = value;
   }
 
-  /**
-    \rst
-    Writes *value* to the buffer.
-    \endrst
-   */
   void write(string_view value) {
     auto&& it = reserve(value.size());
-    it = internal::copy_str<char_type>(value.begin(), value.end(), it);
+    it = copy_str<char_type>(value.begin(), value.end(), it);
   }
   void write(wstring_view value) {
     static_assert(std::is_same<char_type, wchar_t>::value, "");
@@ -1673,25 +1797,23 @@
     it = std::copy(value.begin(), value.end(), it);
   }
 
-  // Writes a formatted string.
   template <typename Char>
   void write(const Char* s, std::size_t size, const format_specs& specs) {
     write_padded(specs, str_writer<Char>{s, size});
   }
 
   template <typename Char>
-  void write(basic_string_view<Char> s,
-             const format_specs& specs = format_specs()) {
+  void write(basic_string_view<Char> s, const format_specs& specs = {}) {
     const Char* data = s.data();
     std::size_t size = s.size();
-    if (specs.precision >= 0 && internal::to_unsigned(specs.precision) < size)
-      size = internal::to_unsigned(specs.precision);
+    if (specs.precision >= 0 && to_unsigned(specs.precision) < size)
+      size = code_point_index(s, to_unsigned(specs.precision));
     write(data, size, specs);
   }
 
   template <typename UIntPtr>
   void write_pointer(UIntPtr value, const format_specs* specs) {
-    int num_digits = internal::count_digits<4>(value);
+    int num_digits = count_digits<4>(value);
     auto pw = pointer_writer<UIntPtr>{value, num_digits};
     if (!specs) return pw(reserve(to_unsigned(num_digits) + 2));
     format_specs specs_copy = *specs;
@@ -1702,6 +1824,10 @@
 
 using writer = basic_writer<buffer_range<char>>;
 
+template <typename T> struct is_integral : std::is_integral<T> {};
+template <> struct is_integral<int128_t> : std::true_type {};
+template <> struct is_integral<uint128_t> : std::true_type {};
+
 template <typename Range, typename ErrorHandler = internal::error_handler>
 class arg_formatter_base {
  public:
@@ -1731,7 +1857,7 @@
   }
 
   void write_pointer(const void* p) {
-    writer_.write_pointer(internal::bit_cast<internal::uintptr_t>(p), specs_);
+    writer_.write_pointer(internal::to_uintptr(p), specs_);
   }
 
  protected:
@@ -1764,7 +1890,7 @@
     return out();
   }
 
-  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+  template <typename T, FMT_ENABLE_IF(is_integral<T>::value)>
   iterator operator()(T value) {
     if (specs_)
       writer_.write_int(value, *specs_);
@@ -1787,7 +1913,7 @@
 
   template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
   iterator operator()(T value) {
-    writer_.write_double(value, specs_ ? *specs_ : format_specs());
+    writer_.write(value, specs_ ? *specs_ : format_specs());
     return out();
   }
 
@@ -1852,14 +1978,14 @@
 template <typename Char, typename ErrorHandler>
 FMT_CONSTEXPR int parse_nonnegative_int(const Char*& begin, const Char* end,
                                         ErrorHandler&& eh) {
-  assert(begin != end && '0' <= *begin && *begin <= '9');
+  FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', "");
   if (*begin == '0') {
     ++begin;
     return 0;
   }
   unsigned value = 0;
   // Convert to unsigned to prevent a warning.
-  constexpr unsigned max_int = (std::numeric_limits<int>::max)();
+  constexpr unsigned max_int = max_value<int>();
   unsigned big = max_int / 10;
   do {
     // Check for overflow.
@@ -1878,11 +2004,11 @@
  private:
   using char_type = typename Context::char_type;
 
-  basic_parse_context<char_type>& parse_ctx_;
+  basic_format_parse_context<char_type>& parse_ctx_;
   Context& ctx_;
 
  public:
-  explicit custom_formatter(basic_parse_context<char_type>& parse_ctx,
+  explicit custom_formatter(basic_format_parse_context<char_type>& parse_ctx,
                             Context& ctx)
       : parse_ctx_(parse_ctx), ctx_(ctx) {}
 
@@ -1896,7 +2022,7 @@
 
 template <typename T>
 using is_integer =
-    bool_constant<std::is_integral<T>::value && !std::is_same<T, bool>::value &&
+    bool_constant<is_integral<T>::value && !std::is_same<T, bool>::value &&
                   !std::is_same<T, char>::value &&
                   !std::is_same<T, wchar_t>::value>;
 
@@ -1981,20 +2107,20 @@
       : error_handler_(eh), arg_type_(arg_type) {}
 
   FMT_CONSTEXPR void require_numeric_argument() {
-    if (!is_arithmetic(arg_type_))
+    if (!is_arithmetic_type(arg_type_))
       error_handler_.on_error("format specifier requires numeric argument");
   }
 
   FMT_CONSTEXPR void check_sign() {
     require_numeric_argument();
-    if (is_integral(arg_type_) && arg_type_ != int_type &&
+    if (is_integral_type(arg_type_) && arg_type_ != int_type &&
         arg_type_ != long_long_type && arg_type_ != internal::char_type) {
       error_handler_.on_error("format specifier requires signed argument");
     }
   }
 
   FMT_CONSTEXPR void check_precision() {
-    if (is_integral(arg_type_) || arg_type_ == internal::pointer_type)
+    if (is_integral_type(arg_type_) || arg_type_ == internal::pointer_type)
       error_handler_.on_error("precision not allowed for this argument type");
   }
 
@@ -2049,14 +2175,12 @@
   numeric_specs_checker<Handler> checker_;
 };
 
-template <template <typename> class Handler, typename T, typename FormatArg,
+template <template <typename> class Handler, typename FormatArg,
           typename ErrorHandler>
-FMT_CONSTEXPR void set_dynamic_spec(T& value, FormatArg arg, ErrorHandler eh) {
-  unsigned long long big_value =
-      visit_format_arg(Handler<ErrorHandler>(eh), arg);
-  if (big_value > to_unsigned((std::numeric_limits<int>::max)()))
-    eh.on_error("number is too big");
-  value = static_cast<T>(big_value);
+FMT_CONSTEXPR int get_dynamic_spec(FormatArg arg, ErrorHandler eh) {
+  unsigned long long value = visit_format_arg(Handler<ErrorHandler>(eh), arg);
+  if (value > to_unsigned(max_value<int>())) eh.on_error("number is too big");
+  return static_cast<int>(value);
 }
 
 struct auto_id {};
@@ -2081,13 +2205,13 @@
         context_(ctx) {}
 
   template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) {
-    set_dynamic_spec<width_checker>(this->specs_.width, get_arg(arg_id),
-                                    context_.error_handler());
+    this->specs_.width = get_dynamic_spec<width_checker>(
+        get_arg(arg_id), context_.error_handler());
   }
 
   template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) {
-    set_dynamic_spec<precision_checker>(this->specs_.precision, get_arg(arg_id),
-                                        context_.error_handler());
+    this->specs_.precision = get_dynamic_spec<precision_checker>(
+        get_arg(arg_id), context_.error_handler());
   }
 
   void on_error(const char* message) { context_.on_error(message); }
@@ -2114,24 +2238,6 @@
   Context& context_;
 };
 
-struct string_view_metadata {
-  FMT_CONSTEXPR string_view_metadata() : offset_(0u), size_(0u) {}
-  template <typename Char>
-  FMT_CONSTEXPR string_view_metadata(basic_string_view<Char> primary_string,
-                                     basic_string_view<Char> view)
-      : offset_(to_unsigned(view.data() - primary_string.data())),
-        size_(view.size()) {}
-  FMT_CONSTEXPR string_view_metadata(std::size_t offset, std::size_t size)
-      : offset_(offset), size_(size) {}
-  template <typename Char>
-  FMT_CONSTEXPR basic_string_view<Char> to_view(const Char* str) const {
-    return {str + offset_, size_};
-  }
-
-  std::size_t offset_;
-  std::size_t size_;
-};
-
 enum class arg_id_kind { none, index, name };
 
 // An argument reference.
@@ -2139,7 +2245,7 @@
   FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {}
   FMT_CONSTEXPR explicit arg_ref(int index)
       : kind(arg_id_kind::index), val(index) {}
-  FMT_CONSTEXPR explicit arg_ref(string_view_metadata name)
+  FMT_CONSTEXPR explicit arg_ref(basic_string_view<Char> name)
       : kind(arg_id_kind::name), val(name) {}
 
   FMT_CONSTEXPR arg_ref& operator=(int idx) {
@@ -2150,12 +2256,11 @@
 
   arg_id_kind kind;
   union value {
-    FMT_CONSTEXPR value() : index(0u) {}
-    FMT_CONSTEXPR value(int id) : index(id) {}
-    FMT_CONSTEXPR value(string_view_metadata n) : name(n) {}
+    FMT_CONSTEXPR value(int id = 0) : index{id} {}
+    FMT_CONSTEXPR value(basic_string_view<Char> n) : name(n) {}
 
     int index;
-    string_view_metadata name;
+    basic_string_view<Char> name;
   } val;
 };
 
@@ -2213,8 +2318,7 @@
     context_.check_arg_id(arg_id);
     basic_string_view<char_type> format_str(
         context_.begin(), to_unsigned(context_.end() - context_.begin()));
-    const auto id_metadata = string_view_metadata(format_str, arg_id);
-    return arg_ref_type(id_metadata);
+    return arg_ref_type(arg_id);
   }
 
   dynamic_format_specs<char_type>& specs_;
@@ -2224,18 +2328,24 @@
 template <typename Char, typename IDHandler>
 FMT_CONSTEXPR const Char* parse_arg_id(const Char* begin, const Char* end,
                                        IDHandler&& handler) {
-  assert(begin != end);
+  FMT_ASSERT(begin != end, "");
   Char c = *begin;
-  if (c == '}' || c == ':') return handler(), begin;
+  if (c == '}' || c == ':') {
+    handler();
+    return begin;
+  }
   if (c >= '0' && c <= '9') {
     int index = parse_nonnegative_int(begin, end, handler);
     if (begin == end || (*begin != '}' && *begin != ':'))
-      return handler.on_error("invalid format string"), begin;
-    handler(index);
+      handler.on_error("invalid format string");
+    else
+      handler(index);
     return begin;
   }
-  if (!is_name_start(c))
-    return handler.on_error("invalid format string"), begin;
+  if (!is_name_start(c)) {
+    handler.on_error("invalid format string");
+    return begin;
+  }
   auto it = begin;
   do {
     ++it;
@@ -2294,9 +2404,11 @@
     case '>':
       align = align::right;
       break;
+#if FMT_NUMERIC_ALIGN
     case '=':
       align = align::numeric;
       break;
+#endif
     case '^':
       align = align::center;
       break;
@@ -2439,7 +2551,7 @@
 template <bool IS_CONSTEXPR, typename Char, typename Handler>
 FMT_CONSTEXPR void parse_format_string(basic_string_view<Char> format_str,
                                        Handler&& handler) {
-  struct writer {
+  struct pfs_writer {
     FMT_CONSTEXPR void operator()(const Char* begin, const Char* end) {
       if (begin == end) return;
       for (;;) {
@@ -2497,10 +2609,9 @@
       conditional_t<internal::mapped_type_constant<T, context>::value !=
                         internal::custom_type,
                     decltype(arg_mapper<context>().map(std::declval<T>())), T>;
-  conditional_t<has_formatter<mapped_type, context>::value,
-                formatter<mapped_type, char_type>,
-                internal::fallback_formatter<T, char_type>>
-      f;
+  auto f = conditional_t<has_formatter<mapped_type, context>::value,
+                         formatter<mapped_type, char_type>,
+                         internal::fallback_formatter<T, char_type>>();
   return f.parse(ctx);
 }
 
@@ -2509,7 +2620,7 @@
  public:
   explicit FMT_CONSTEXPR format_string_checker(
       basic_string_view<Char> format_str, ErrorHandler eh)
-      : arg_id_((std::numeric_limits<unsigned>::max)()),
+      : arg_id_(max_value<unsigned>()),
         context_(format_str, eh),
         parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {}
 
@@ -2540,7 +2651,7 @@
   }
 
  private:
-  using parse_context_type = basic_parse_context<Char, ErrorHandler>;
+  using parse_context_type = basic_format_parse_context<Char, ErrorHandler>;
   enum { num_args = sizeof...(Args) };
 
   FMT_CONSTEXPR void check_arg_id() {
@@ -2573,32 +2684,29 @@
   (void)invalid_format;
 }
 
-template <template <typename> class Handler, typename Spec, typename Context>
-void handle_dynamic_spec(Spec& value, arg_ref<typename Context::char_type> ref,
-                         Context& ctx,
-                         const typename Context::char_type* format_str) {
+template <template <typename> class Handler, typename Context>
+void handle_dynamic_spec(int& value, arg_ref<typename Context::char_type> ref,
+                         Context& ctx) {
   switch (ref.kind) {
   case arg_id_kind::none:
     break;
   case arg_id_kind::index:
-    internal::set_dynamic_spec<Handler>(value, ctx.arg(ref.val.index),
-                                        ctx.error_handler());
+    value = internal::get_dynamic_spec<Handler>(ctx.arg(ref.val.index),
+                                                ctx.error_handler());
     break;
-  case arg_id_kind::name: {
-    const auto arg_id = ref.val.name.to_view(format_str);
-    internal::set_dynamic_spec<Handler>(value, ctx.arg(arg_id),
-                                        ctx.error_handler());
+  case arg_id_kind::name:
+    value = internal::get_dynamic_spec<Handler>(ctx.arg(ref.val.name),
+                                                ctx.error_handler());
     break;
   }
-  }
 }
 }  // namespace internal
 
 template <typename Range>
-using basic_writer FMT_DEPRECATED = internal::basic_writer<Range>;
-using writer FMT_DEPRECATED = internal::writer;
-using wwriter FMT_DEPRECATED =
-    internal::basic_writer<internal::buffer_range<wchar_t>>;
+using basic_writer FMT_DEPRECATED_ALIAS = internal::basic_writer<Range>;
+using writer FMT_DEPRECATED_ALIAS = internal::writer;
+using wwriter FMT_DEPRECATED_ALIAS =
+    internal::basic_writer<buffer_range<wchar_t>>;
 
 /** The default argument formatter. */
 template <typename Range>
@@ -2609,7 +2717,7 @@
   using context_type = basic_format_context<typename base::iterator, char_type>;
 
   context_type& ctx_;
-  basic_parse_context<char_type>* parse_ctx_;
+  basic_format_parse_context<char_type>* parse_ctx_;
 
  public:
   using range = Range;
@@ -2623,9 +2731,10 @@
     *specs* contains format specifier information for standard argument types.
     \endrst
    */
-  explicit arg_formatter(context_type& ctx,
-                         basic_parse_context<char_type>* parse_ctx = nullptr,
-                         format_specs* specs = nullptr)
+  explicit arg_formatter(
+      context_type& ctx,
+      basic_format_parse_context<char_type>* parse_ctx = nullptr,
+      format_specs* specs = nullptr)
       : base(Range(ctx.out()), specs, ctx.locale()),
         ctx_(ctx),
         parse_ctx_(parse_ctx) {}
@@ -2635,7 +2744,7 @@
   /** Formats an argument of a user-defined type. */
   iterator operator()(typename basic_format_arg<context_type>::handle handle) {
     handle.format(*parse_ctx_, ctx_);
-    return this->out();
+    return ctx_.out();
   }
 };
 
@@ -2676,7 +2785,11 @@
       : std::runtime_error("") {
     init(error_code, message, make_format_args(args...));
   }
-  ~system_error() FMT_NOEXCEPT;
+  system_error(const system_error&) = default;
+  system_error& operator=(const system_error&) = default;
+  system_error(system_error&&) = default;
+  system_error& operator=(system_error&&) = default;
+  ~system_error() FMT_NOEXCEPT FMT_OVERRIDE;
 
   int error_code() const { return error_code_; }
 };
@@ -2698,126 +2811,7 @@
   \endrst
  */
 FMT_API void format_system_error(internal::buffer<char>& out, int error_code,
-                                 fmt::string_view message) FMT_NOEXCEPT;
-
-struct float_spec_handler {
-  char type;
-  bool upper;
-  bool fixed;
-  bool as_percentage;
-  bool use_locale;
-
-  explicit float_spec_handler(char t)
-      : type(t),
-        upper(false),
-        fixed(false),
-        as_percentage(false),
-        use_locale(false) {}
-
-  void on_general() {
-    if (type == 'G') upper = true;
-  }
-
-  void on_exp() {
-    if (type == 'E') upper = true;
-  }
-
-  void on_fixed() {
-    fixed = true;
-    if (type == 'F') upper = true;
-  }
-
-  void on_percent() {
-    fixed = true;
-    as_percentage = true;
-  }
-
-  void on_hex() {
-    if (type == 'A') upper = true;
-  }
-
-  void on_num() { use_locale = true; }
-
-  FMT_NORETURN void on_error() {
-    FMT_THROW(format_error("invalid type specifier"));
-  }
-};
-
-template <typename Range>
-template <typename T, bool USE_GRISU>
-void internal::basic_writer<Range>::write_double(T value,
-                                                 const format_specs& specs) {
-  // Check type.
-  float_spec_handler handler(static_cast<char>(specs.type));
-  internal::handle_float_type_spec(handler.type, handler);
-
-  char sign = 0;
-  // Use signbit instead of value < 0 since the latter is always false for NaN.
-  if (std::signbit(value)) {
-    sign = '-';
-    value = -value;
-  } else if (specs.sign != sign::none) {
-    if (specs.sign == sign::plus)
-      sign = '+';
-    else if (specs.sign == sign::space)
-      sign = ' ';
-  }
-
-  if (!std::isfinite(value)) {
-    // Format infinity and NaN ourselves because sprintf's output is not
-    // consistent across platforms.
-    const char* str = std::isinf(value) ? (handler.upper ? "INF" : "inf")
-                                        : (handler.upper ? "NAN" : "nan");
-    return write_padded(specs,
-                        inf_or_nan_writer{sign, handler.as_percentage, str});
-  }
-
-  if (handler.as_percentage) value *= 100;
-
-  memory_buffer buffer;
-  int exp = 0;
-  int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6;
-  unsigned options = handler.fixed ? internal::grisu_options::fixed : 0;
-  bool use_grisu = USE_GRISU &&
-                   (specs.type != 'a' && specs.type != 'A' &&
-                    specs.type != 'e' && specs.type != 'E') &&
-                   internal::grisu_format(static_cast<double>(value), buffer,
-                                          precision, options, exp);
-  char* decimal_point_pos = nullptr;
-  if (!use_grisu)
-    decimal_point_pos = internal::sprintf_format(value, buffer, specs);
-
-  if (handler.as_percentage) {
-    buffer.push_back('%');
-    --exp;  // Adjust decimal place position.
-  }
-  format_specs as = specs;
-  if (specs.align == align::numeric) {
-    if (sign) {
-      auto&& it = reserve(1);
-      *it++ = static_cast<char_type>(sign);
-      sign = 0;
-      if (as.width) --as.width;
-    }
-    as.align = align::right;
-  } else if (specs.align == align::none) {
-    as.align = align::right;
-  }
-  char_type decimal_point = handler.use_locale
-                                ? internal::decimal_point<char_type>(locale_)
-                                : static_cast<char_type>('.');
-  if (use_grisu) {
-    auto params = internal::gen_digits_params();
-    params.fixed = handler.fixed;
-    params.num_digits = precision;
-    params.trailing_zeros =
-        (precision != 0 && (handler.fixed || !specs.type)) || specs.alt;
-    write_padded(as, grisu_writer(sign, buffer, exp, params, decimal_point));
-  } else {
-    write_padded(as,
-                 double_writer{sign, buffer, decimal_point_pos, decimal_point});
-  }
-}
+                                 string_view message) FMT_NOEXCEPT;
 
 // Reports a system error without throwing an exception.
 // Can be used to report errors from destructors.
@@ -2889,7 +2883,7 @@
       // Integer division is slow so do it for a group of two digits instead
       // of for every digit. The idea comes from the talk by Alexandrescu
       // "Three Optimization Tips for C++". See speed-test for a comparison.
-      unsigned index = static_cast<unsigned>((value % 100) * 2);
+      auto index = static_cast<unsigned>((value % 100) * 2);
       value /= 100;
       *--ptr = internal::data::digits[index + 1];
       *--ptr = internal::data::digits[index];
@@ -2898,14 +2892,14 @@
       *--ptr = static_cast<char>('0' + value);
       return ptr;
     }
-    unsigned index = static_cast<unsigned>(value * 2);
+    auto index = static_cast<unsigned>(value * 2);
     *--ptr = internal::data::digits[index + 1];
     *--ptr = internal::data::digits[index];
     return ptr;
   }
 
   void format_signed(long long value) {
-    unsigned long long abs_value = static_cast<unsigned long long>(value);
+    auto abs_value = static_cast<unsigned long long>(value);
     bool negative = value < 0;
     if (negative) abs_value = 0 - abs_value;
     str_ = format_decimal(abs_value);
@@ -2954,13 +2948,12 @@
 struct formatter<T, Char,
                  enable_if_t<internal::type_constant<T, Char>::value !=
                              internal::custom_type>> {
-  FMT_CONSTEXPR formatter() : format_str_(nullptr) {}
+  FMT_CONSTEXPR formatter() = default;
 
   // Parses format specifiers stopping either at the end of the range or at the
   // terminating '}'.
   template <typename ParseContext>
   FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
-    format_str_ = ctx.begin();
     using handler_type = internal::dynamic_specs_handler<ParseContext>;
     auto type = internal::type_constant<T, Char>::value;
     internal::specs_checker<handler_type> handler(handler_type(specs_, ctx),
@@ -2976,6 +2969,8 @@
     case internal::uint_type:
     case internal::long_long_type:
     case internal::ulong_long_type:
+    case internal::int128_type:
+    case internal::uint128_type:
     case internal::bool_type:
       handle_int_type_spec(specs_.type,
                            internal::int_type_checker<decltype(eh)>(eh));
@@ -2984,10 +2979,10 @@
       handle_char_specs(
           &specs_, internal::char_specs_checker<decltype(eh)>(specs_.type, eh));
       break;
+    case internal::float_type:
     case internal::double_type:
     case internal::long_double_type:
-      handle_float_type_spec(specs_.type,
-                             internal::float_type_checker<decltype(eh)>(eh));
+      internal::parse_float_type_spec(specs_, eh);
       break;
     case internal::cstring_type:
       internal::handle_cstring_type_spec(
@@ -3010,9 +3005,9 @@
   template <typename FormatContext>
   auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) {
     internal::handle_dynamic_spec<internal::width_checker>(
-        specs_.width, specs_.width_ref, ctx, format_str_);
+        specs_.width, specs_.width_ref, ctx);
     internal::handle_dynamic_spec<internal::precision_checker>(
-        specs_.precision, specs_.precision_ref, ctx, format_str_);
+        specs_.precision, specs_.precision_ref, ctx);
     using range_type =
         internal::output_range<typename FormatContext::iterator,
                                typename FormatContext::char_type>;
@@ -3022,7 +3017,6 @@
 
  private:
   internal::dynamic_format_specs<Char> specs_;
-  const Char* format_str_;
 };
 
 #define FMT_FORMAT_AS(Type, Base)                                             \
@@ -3040,7 +3034,6 @@
 FMT_FORMAT_AS(unsigned short, unsigned);
 FMT_FORMAT_AS(long, long long);
 FMT_FORMAT_AS(unsigned long, unsigned long long);
-FMT_FORMAT_AS(float, double);
 FMT_FORMAT_AS(Char*, const Char*);
 FMT_FORMAT_AS(std::basic_string<Char>, basic_string_view<Char>);
 FMT_FORMAT_AS(std::nullptr_t, const void*);
@@ -3123,9 +3116,9 @@
  private:
   template <typename Context> void handle_specs(Context& ctx) {
     internal::handle_dynamic_spec<internal::width_checker>(
-        specs_.width, specs_.width_ref, ctx, format_str_);
+        specs_.width, specs_.width_ref, ctx);
     internal::handle_dynamic_spec<internal::precision_checker>(
-        specs_.precision, specs_.precision_ref, ctx, format_str_);
+        specs_.precision, specs_.precision_ref, ctx);
   }
 
   internal::dynamic_format_specs<Char> specs_;
@@ -3142,8 +3135,8 @@
 }
 
 template <typename Char, typename ErrorHandler>
-FMT_CONSTEXPR void advance_to(basic_parse_context<Char, ErrorHandler>& ctx,
-                              const Char* p) {
+FMT_CONSTEXPR void advance_to(
+    basic_format_parse_context<Char, ErrorHandler>& ctx, const Char* p) {
   ctx.advance_to(ctx.begin() + (p - &*ctx.begin()));
 }
 
@@ -3175,10 +3168,8 @@
 
   void on_replacement_field(const Char* p) {
     advance_to(parse_context, p);
-    internal::custom_formatter<Context> f(parse_context, context);
-    if (!visit_format_arg(f, arg))
-      context.advance_to(
-          visit_format_arg(ArgFormatter(context, &parse_context), arg));
+    context.advance_to(
+        visit_format_arg(ArgFormatter(context, &parse_context), arg));
   }
 
   const Char* on_format_specs(const Char* begin, const Char* end) {
@@ -3187,7 +3178,7 @@
     if (visit_format_arg(f, arg)) return parse_context.begin();
     basic_format_specs<Char> specs;
     using internal::specs_handler;
-    using parse_context_t = basic_parse_context<Char>;
+    using parse_context_t = basic_format_parse_context<Char>;
     internal::specs_checker<specs_handler<parse_context_t, Context>> handler(
         specs_handler<parse_context_t, Context>(specs, parse_context, context),
         arg.type());
@@ -3199,7 +3190,7 @@
     return begin;
   }
 
-  basic_parse_context<Char> parse_context;
+  basic_format_parse_context<Char> parse_context;
   Context context;
   basic_format_arg<Context> arg;
 };
@@ -3396,7 +3387,7 @@
 
 template <typename OutputIt, typename Char = typename OutputIt::value_type>
 using format_to_n_context =
-    format_context_t<fmt::internal::truncating_iterator<OutputIt>, Char>;
+    format_context_t<internal::truncating_iterator<OutputIt>, Char>;
 
 template <typename OutputIt, typename Char = typename OutputIt::value_type>
 using format_to_n_args = basic_format_args<format_to_n_context<OutputIt, Char>>;
@@ -3443,7 +3434,7 @@
     basic_format_args<buffer_context<Char>> args) {
   basic_memory_buffer<Char> buffer;
   internal::vformat_to(buffer, format_str, args);
-  return fmt::to_string(buffer);
+  return to_string(buffer);
 }
 
 /**
@@ -3452,8 +3443,22 @@
  */
 template <typename... Args>
 inline std::size_t formatted_size(string_view format_str, const Args&... args) {
-  auto it = format_to(internal::counting_iterator<char>(), format_str, args...);
-  return it.count();
+  return format_to(internal::counting_iterator(), format_str, args...).count();
+}
+
+template <typename Char, FMT_ENABLE_IF(std::is_same<Char, wchar_t>::value)>
+void vprint(std::FILE* f, basic_string_view<Char> format_str,
+            wformat_args args) {
+  wmemory_buffer buffer;
+  internal::vformat_to(buffer, format_str, args);
+  buffer.push_back(L'\0');
+  if (std::fputws(buffer.data(), f) == -1)
+    FMT_THROW(system_error(errno, "cannot write to file"));
+}
+
+template <typename Char, FMT_ENABLE_IF(std::is_same<Char, wchar_t>::value)>
+void vprint(basic_string_view<Char> format_str, wformat_args args) {
+  vprint(stdout, format_str, args);
 }
 
 #if FMT_USE_USER_DEFINED_LITERALS
@@ -3466,7 +3471,7 @@
   std::basic_string<Char> operator()(Args&&... args) const {
     FMT_CONSTEXPR_DECL Char s[] = {CHARS..., '\0'};
     FMT_CONSTEXPR_DECL bool invalid_format =
-        do_check_format_string<Char, error_handler, Args...>(
+        do_check_format_string<Char, error_handler, remove_cvref_t<Args>...>(
             basic_string_view<Char>(s, sizeof...(CHARS)));
     (void)invalid_format;
     return format(s, std::forward<Args>(args)...);
@@ -3547,6 +3552,22 @@
 #endif  // FMT_USE_USER_DEFINED_LITERALS
 FMT_END_NAMESPACE
 
+#define FMT_STRING_IMPL(s, ...)                                         \
+  [] {                                                                  \
+    struct str : fmt::compile_string {                                  \
+      using char_type = typename std::remove_cv<std::remove_pointer<    \
+          typename std::decay<decltype(s)>::type>::type>::type;         \
+      __VA_ARGS__ FMT_CONSTEXPR                                         \
+      operator fmt::basic_string_view<char_type>() const {              \
+        return {s, sizeof(s) / sizeof(char_type) - 1};                  \
+      }                                                                 \
+    } result;                                                           \
+    /* Suppress Qt Creator warning about unused operator. */            \
+    (void)static_cast<fmt::basic_string_view<typename str::char_type>>( \
+        result);                                                        \
+    return result;                                                      \
+  }()
+
 /**
   \rst
   Constructs a compile-time format string.
@@ -3557,37 +3578,10 @@
     std::string s = format(FMT_STRING("{:d}"), "foo");
   \endrst
  */
-#define FMT_STRING(s)                                                    \
-  [] {                                                                   \
-    struct str : fmt::compile_string {                                   \
-      using char_type = typename std::remove_cv<std::remove_pointer<     \
-          typename std::decay<decltype(s)>::type>::type>::type;          \
-      FMT_CONSTEXPR operator fmt::basic_string_view<char_type>() const { \
-        return {s, sizeof(s) / sizeof(char_type) - 1};                   \
-      }                                                                  \
-    } result;                                                            \
-    /* Suppress Qt Creator warning about unused operator. */             \
-    (void)static_cast<fmt::basic_string_view<typename str::char_type>>(  \
-        result);                                                         \
-    return result;                                                       \
-  }()
+#define FMT_STRING(s) FMT_STRING_IMPL(s, )
 
 #if defined(FMT_STRING_ALIAS) && FMT_STRING_ALIAS
-/**
-  \rst
-  Constructs a compile-time format string. This macro is disabled by default to
-  prevent potential name collisions. To enable it define ``FMT_STRING_ALIAS`` to
-  1 before including ``fmt/format.h``.
-
-  **Example**::
-
-    #define FMT_STRING_ALIAS 1
-    #include <fmt/format.h>
-    // A compile-time error because 'd' is an invalid specifier for strings.
-    std::string s = format(fmt("{:d}"), "foo");
-  \endrst
- */
-#  define fmt(s) FMT_STRING(s)
+#  define fmt(s) FMT_STRING_IMPL(s, [[deprecated]])
 #endif
 
 #ifdef FMT_HEADER_ONLY
diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h
index 69bac0e..72d078b 100644
--- a/include/fmt/ostream.h
+++ b/include/fmt/ostream.h
@@ -46,9 +46,13 @@
 
 template <typename Char> struct test_stream : std::basic_ostream<Char> {
  private:
-  struct null;
   // Hide all operator<< from std::basic_ostream<Char>.
-  void operator<<(null);
+  void_t<> operator<<(null<>);
+  void_t<> operator<<(const Char*);
+
+  template <typename T, FMT_ENABLE_IF(std::is_convertible<T, int>::value &&
+                                      !std::is_enum<T>::value)>
+  void_t<> operator<<(T);
 };
 
 // Checks if T has a user-defined operator<< (e.g. not a member of
@@ -56,9 +60,9 @@
 template <typename T, typename Char> class is_streamable {
  private:
   template <typename U>
-  static decltype((void)(std::declval<test_stream<Char>&>()
-                         << std::declval<U>()),
-                  std::true_type())
+  static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
+                                              << std::declval<U>()),
+                                     void_t<>>::value>
   test(int);
 
   template <typename> static std::false_type test(...);
@@ -75,8 +79,7 @@
   const Char* buf_data = buf.data();
   using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
   unsigned_streamsize size = buf.size();
-  unsigned_streamsize max_size =
-      to_unsigned((std::numeric_limits<std::streamsize>::max)());
+  unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
   do {
     unsigned_streamsize n = size <= max_size ? size : max_size;
     os.write(buf_data, static_cast<std::streamsize>(n));
@@ -86,9 +89,11 @@
 }
 
 template <typename Char, typename T>
-void format_value(buffer<Char>& buf, const T& value) {
+void format_value(buffer<Char>& buf, const T& value,
+                  locale_ref loc = locale_ref()) {
   formatbuf<Char> format_buf(buf);
   std::basic_ostream<Char> output(&format_buf);
+  if (loc) output.imbue(loc.get<std::locale>());
   output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
   output << value;
   buf.resize(buf.size());
@@ -101,7 +106,7 @@
   template <typename Context>
   auto format(const T& value, Context& ctx) -> decltype(ctx.out()) {
     basic_memory_buffer<Char> buffer;
-    format_value(buffer, value);
+    format_value(buffer, value, ctx.locale());
     basic_string_view<Char> str(buffer.data(), buffer.size());
     return formatter<basic_string_view<Char>, Char>::format(str, ctx);
   }
diff --git a/include/fmt/posix.h b/include/fmt/posix.h
index 6b2d7f8..e68e226 100644
--- a/include/fmt/posix.h
+++ b/include/fmt/posix.h
@@ -13,11 +13,10 @@
 #  undef __STRICT_ANSI__
 #endif
 
-#include <errno.h>
-#include <fcntl.h>   // for O_RDONLY
-#include <locale.h>  // for locale_t
-#include <stdio.h>
-#include <stdlib.h>  // for strtod_l
+#include <cerrno>
+#include <clocale>  // for locale_t
+#include <cstdio>
+#include <cstdlib>  // for strtod_l
 
 #include <cstddef>
 
@@ -27,6 +26,18 @@
 
 #include "format.h"
 
+// UWP doesn't provide _pipe.
+#if FMT_HAS_INCLUDE("winapifamily.h")
+#  include <winapifamily.h>
+#endif
+#if FMT_HAS_INCLUDE("fcntl.h") && \
+    (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
+#  include <fcntl.h>  // for O_RDONLY
+#  define FMT_USE_FCNTL 1
+#else
+#  define FMT_USE_FCNTL 0
+#endif
+
 #ifndef FMT_POSIX
 #  if defined(_WIN32) && !defined(__MINGW32__)
 // Fix warnings about deprecated symbols.
@@ -54,8 +65,8 @@
 #ifndef _WIN32
 #  define FMT_RETRY_VAL(result, expression, error_result) \
     do {                                                  \
-      result = (expression);                              \
-    } while (result == error_result && errno == EINTR)
+      (result) = (expression);                            \
+    } while ((result) == (error_result) && errno == EINTR)
 #else
 #  define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
 #endif
@@ -132,16 +143,15 @@
   explicit buffered_file(FILE* f) : file_(f) {}
 
  public:
+  buffered_file(const buffered_file&) = delete;
+  void operator=(const buffered_file&) = delete;
+
   // Constructs a buffered_file object which doesn't represent any file.
   buffered_file() FMT_NOEXCEPT : file_(nullptr) {}
 
   // Destroys the object closing the file it represents if any.
   FMT_API ~buffered_file() FMT_NOEXCEPT;
 
- private:
-  buffered_file(const buffered_file&) = delete;
-  void operator=(const buffered_file&) = delete;
-
  public:
   buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) {
     other.file_ = nullptr;
@@ -177,6 +187,7 @@
   }
 };
 
+#if FMT_USE_FCNTL
 // A file. Closed file is represented by a file object with descriptor -1.
 // Methods that are not declared with FMT_NOEXCEPT may throw
 // fmt::system_error in case of failure. Note that some errors such as
@@ -204,14 +215,13 @@
   // Opens a file and constructs a file object representing this file.
   FMT_API file(cstring_view path, int oflag);
 
- private:
+ public:
   file(const file&) = delete;
   void operator=(const file&) = delete;
 
- public:
   file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
 
-  file& operator=(file&& other) {
+  file& operator=(file&& other) FMT_NOEXCEPT {
     close();
     fd_ = other.fd_;
     other.fd_ = -1;
@@ -260,6 +270,7 @@
 
 // Returns the memory page size.
 long getpagesize();
+#endif  // FMT_USE_FCNTL
 
 #ifdef FMT_LOCALE
 // A "C" numeric locale.
@@ -283,11 +294,10 @@
 
   locale_t locale_;
 
-  Locale(const Locale&) = delete;
-  void operator=(const Locale&) = delete;
-
  public:
   using type = locale_t;
+  Locale(const Locale&) = delete;
+  void operator=(const Locale&) = delete;
 
   Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", nullptr)) {
     if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
diff --git a/include/fmt/printf.h b/include/fmt/printf.h
index c803aa9..cdbb65e 100644
--- a/include/fmt/printf.h
+++ b/include/fmt/printf.h
@@ -1,4 +1,4 @@
-// Formatting library for C++
+// Formatting library for C++ - legacy printf implementation
 //
 // Copyright (c) 2012 - 2016, Victor Zverovich
 // All rights reserved.
@@ -8,7 +8,7 @@
 #ifndef FMT_PRINTF_H_
 #define FMT_PRINTF_H_
 
-#include <algorithm>  // std::fill_n
+#include <algorithm>  // std::max
 #include <limits>     // std::numeric_limits
 
 #include "ostream.h"
@@ -16,15 +16,11 @@
 FMT_BEGIN_NAMESPACE
 namespace internal {
 
-// A helper function to suppress bogus "conditional expression is constant"
-// warnings.
-template <typename T> inline T const_check(T value) { return value; }
-
 // Checks if a value fits in int - used to avoid warnings about comparing
 // signed and unsigned integers.
 template <bool IsSigned> struct int_checker {
   template <typename T> static bool fits_in_int(T value) {
-    unsigned max = std::numeric_limits<int>::max();
+    unsigned max = max_value<int>();
     return value <= max;
   }
   static bool fits_in_int(bool) { return true; }
@@ -33,7 +29,7 @@
 template <> struct int_checker<true> {
   template <typename T> static bool fits_in_int(T value) {
     return value >= std::numeric_limits<int>::min() &&
-           value <= std::numeric_limits<int>::max();
+           value <= max_value<int>();
   }
   static bool fits_in_int(int) { return true; }
 };
@@ -158,12 +154,12 @@
 
   template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
   unsigned operator()(T value) {
-    auto width = static_cast<uint32_or_64_t<T>>(value);
+    auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
     if (internal::is_negative(value)) {
       specs_.align = align::left;
       width = 0 - width;
     }
-    unsigned int_max = std::numeric_limits<int>::max();
+    unsigned int_max = max_value<int>();
     if (width > int_max) FMT_THROW(format_error("number is too big"));
     return static_cast<unsigned>(width);
   }
@@ -235,7 +231,7 @@
   printf_arg_formatter(iterator iter, format_specs& specs, context_type& ctx)
       : base(Range(iter), &specs, internal::locale_ref()), context_(ctx) {}
 
-  template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
+  template <typename T, FMT_ENABLE_IF(fmt::internal::is_integral<T>::value)>
   iterator operator()(T value) {
     // MSVC2013 fails to compile separate overloads for bool and char_type so
     // use std::is_same instead.
@@ -332,14 +328,14 @@
 
   OutputIt out_;
   basic_format_args<basic_printf_context> args_;
-  basic_parse_context<Char> parse_ctx_;
+  basic_format_parse_context<Char> parse_ctx_;
 
   static void parse_flags(format_specs& specs, const Char*& it,
                           const Char* end);
 
   // Returns the argument with specified index or, if arg_index is equal
   // to the maximum unsigned value, the next argument.
-  format_arg get_arg(unsigned arg_index = std::numeric_limits<unsigned>::max());
+  format_arg get_arg(unsigned arg_index = internal::max_value<unsigned>());
 
   // Parses argument index, flags and width and returns the argument index.
   unsigned parse_header(const Char*& it, const Char* end, format_specs& specs);
@@ -361,15 +357,14 @@
 
   format_arg arg(unsigned id) const { return args_.get(id); }
 
-  basic_parse_context<Char>& parse_context() { return parse_ctx_; }
+  basic_format_parse_context<Char>& parse_context() { return parse_ctx_; }
 
   FMT_CONSTEXPR void on_error(const char* message) {
     parse_ctx_.on_error(message);
   }
 
   /** Formats stored arguments and writes the output to the range. */
-  template <typename ArgFormatter =
-                printf_arg_formatter<internal::buffer_range<Char>>>
+  template <typename ArgFormatter = printf_arg_formatter<buffer_range<Char>>>
   OutputIt format();
 };
 
@@ -403,7 +398,7 @@
 template <typename OutputIt, typename Char>
 typename basic_printf_context<OutputIt, Char>::format_arg
 basic_printf_context<OutputIt, Char>::get_arg(unsigned arg_index) {
-  if (arg_index == std::numeric_limits<unsigned>::max())
+  if (arg_index == internal::max_value<unsigned>())
     arg_index = parse_ctx_.next_arg_id();
   else
     parse_ctx_.check_arg_id(--arg_index);
@@ -413,7 +408,7 @@
 template <typename OutputIt, typename Char>
 unsigned basic_printf_context<OutputIt, Char>::parse_header(
     const Char*& it, const Char* end, format_specs& specs) {
-  unsigned arg_index = std::numeric_limits<unsigned>::max();
+  unsigned arg_index = internal::max_value<unsigned>();
   char_type c = *it;
   if (c >= '0' && c <= '9') {
     // Parse an argument index (if followed by '$') or a width possibly
@@ -470,6 +465,7 @@
 
     // Parse argument index, flags and width.
     unsigned arg_index = parse_header(it, end, specs);
+    if (arg_index == 0) on_error("argument index out of range");
 
     // Parse precision.
     if (it != end && *it == '.') {
diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h
index cf0d41a..6110fda 100644
--- a/include/fmt/ranges.h
+++ b/include/fmt/ranges.h
@@ -246,7 +246,8 @@
   static FMT_CONSTEXPR_DECL const bool value =
       internal::is_range_<T>::value &&
       !internal::is_like_std_string<T>::value &&
-      !std::is_convertible<T, std::basic_string<Char>>::value;
+      !std::is_convertible<T, std::basic_string<Char>>::value &&
+      !std::is_constructible<internal::std_string_view<Char>, T>::value;
 };
 
 template <typename RangeT, typename Char>
@@ -283,6 +284,82 @@
   }
 };
 
+template <typename Char, typename... T> struct tuple_arg_join : internal::view {
+  const std::tuple<T...>& tuple;
+  basic_string_view<Char> sep;
+
+  tuple_arg_join(const std::tuple<T...>& t, basic_string_view<Char> s)
+      : tuple{t}, sep{s} {}
+};
+
+template <typename Char, typename... T>
+struct formatter<tuple_arg_join<Char, T...>, Char> {
+  template <typename ParseContext>
+  FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+    return ctx.begin();
+  }
+
+  template <typename FormatContext>
+  typename FormatContext::iterator format(
+      const tuple_arg_join<Char, T...>& value, FormatContext& ctx) {
+    return format(value, ctx, internal::make_index_sequence<sizeof...(T)>{});
+  }
+
+ private:
+  template <typename FormatContext, size_t... N>
+  typename FormatContext::iterator format(
+      const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
+      internal::index_sequence<N...>) {
+    return format_args(value, ctx, std::get<N>(value.tuple)...);
+  }
+
+  template <typename FormatContext>
+  typename FormatContext::iterator format_args(
+      const tuple_arg_join<Char, T...>&, FormatContext& ctx) {
+    // NOTE: for compilers that support C++17, this empty function instantiation
+    // can be replaced with a constexpr branch in the variadic overload.
+    return ctx.out();
+  }
+
+  template <typename FormatContext, typename Arg, typename... Args>
+  typename FormatContext::iterator format_args(
+      const tuple_arg_join<Char, T...>& value, FormatContext& ctx,
+      const Arg& arg, const Args&... args) {
+    using base = formatter<typename std::decay<Arg>::type, Char>;
+    auto out = ctx.out();
+    out = base{}.format(arg, ctx);
+    if (sizeof...(Args) > 0) {
+      out = std::copy(value.sep.begin(), value.sep.end(), out);
+      ctx.advance_to(out);
+      return format_args(value, ctx, args...);
+    }
+    return out;
+  }
+};
+
+/**
+  \rst
+  Returns an object that formats `tuple` with elements separated by `sep`.
+
+  **Example**::
+
+    std::tuple<int, char> t = {1, 'a'};
+    fmt::print("{}", fmt::join(t, ", "));
+    // Output: "1, a"
+  \endrst
+ */
+template <typename... T>
+FMT_CONSTEXPR tuple_arg_join<char, T...> join(const std::tuple<T...>& tuple,
+                                              string_view sep) {
+  return {tuple, sep};
+}
+
+template <typename... T>
+FMT_CONSTEXPR tuple_arg_join<wchar_t, T...> join(const std::tuple<T...>& tuple,
+                                                 wstring_view sep) {
+  return {tuple, sep};
+}
+
 FMT_END_NAMESPACE
 
 #endif  // FMT_RANGES_H_
diff --git a/include/fmt/safe-duration-cast.h b/include/fmt/safe-duration-cast.h
deleted file mode 100644
index aa03618..0000000
--- a/include/fmt/safe-duration-cast.h
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * For conversion between std::chrono::durations without undefined
- * behaviour or erroneous results.
- * This is a stripped down version of duration_cast, for inclusion in fmt.
- * See https://github.com/pauldreik/safe_duration_cast
- *
- * Copyright Paul Dreik 2019
- *
- * This file is licensed under the fmt license, see format.h
- */
-
-#include <chrono>
-#include <cmath>
-#include <limits>
-#include <type_traits>
-
-#include "format.h"
-
-FMT_BEGIN_NAMESPACE
-
-namespace safe_duration_cast {
-
-template <typename To, typename From,
-          FMT_ENABLE_IF(!std::is_same<From, To>::value &&
-                        std::numeric_limits<From>::is_signed ==
-                            std::numeric_limits<To>::is_signed)>
-FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
-  ec = 0;
-  using F = std::numeric_limits<From>;
-  using T = std::numeric_limits<To>;
-  static_assert(F::is_integer, "From must be integral");
-  static_assert(T::is_integer, "To must be integral");
-
-  // A and B are both signed, or both unsigned.
-  if (F::digits <= T::digits) {
-    // From fits in To without any problem.
-  } else {
-    // From does not always fit in To, resort to a dynamic check.
-    if (from < T::min() || from > T::max()) {
-      // outside range.
-      ec = 1;
-      return {};
-    }
-  }
-  return static_cast<To>(from);
-}
-
-/**
- * converts From to To, without loss. If the dynamic value of from
- * can't be converted to To without loss, ec is set.
- */
-template <typename To, typename From,
-          FMT_ENABLE_IF(!std::is_same<From, To>::value &&
-                        std::numeric_limits<From>::is_signed !=
-                            std::numeric_limits<To>::is_signed)>
-FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
-  ec = 0;
-  using F = std::numeric_limits<From>;
-  using T = std::numeric_limits<To>;
-  static_assert(F::is_integer, "From must be integral");
-  static_assert(T::is_integer, "To must be integral");
-
-  if (F::is_signed && !T::is_signed) {
-    // From may be negative, not allowed!
-    if (from < 0) {
-      ec = 1;
-      return {};
-    }
-
-    // From is positive. Can it always fit in To?
-    if (F::digits <= T::digits) {
-      // yes, From always fits in To.
-    } else {
-      // from may not fit in To, we have to do a dynamic check
-      if (from > static_cast<From>(T::max())) {
-        ec = 1;
-        return {};
-      }
-    }
-  }
-
-  if (!F::is_signed && T::is_signed) {
-    // can from be held in To?
-    if (F::digits < T::digits) {
-      // yes, From always fits in To.
-    } else {
-      // from may not fit in To, we have to do a dynamic check
-      if (from > static_cast<From>(T::max())) {
-        // outside range.
-        ec = 1;
-        return {};
-      }
-    }
-  }
-
-  // reaching here means all is ok for lossless conversion.
-  return static_cast<To>(from);
-
-}  // function
-
-template <typename To, typename From,
-          FMT_ENABLE_IF(std::is_same<From, To>::value)>
-FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) {
-  ec = 0;
-  return from;
-}  // function
-
-// clang-format off
-/**
- * converts From to To if possible, otherwise ec is set.
- *
- * input                            |    output
- * ---------------------------------|---------------
- * NaN                              | NaN
- * Inf                              | Inf
- * normal, fits in output           | converted (possibly lossy)
- * normal, does not fit in output   | ec is set
- * subnormal                        | best effort
- * -Inf                             | -Inf
- */
-// clang-format on
-template <typename To, typename From,
-          FMT_ENABLE_IF(!std::is_same<From, To>::value)>
-FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
-  ec = 0;
-  using T = std::numeric_limits<To>;
-  static_assert(std::is_floating_point<From>::value, "From must be floating");
-  static_assert(std::is_floating_point<To>::value, "To must be floating");
-
-  // catch the only happy case
-  if (std::isfinite(from)) {
-    if (from >= T::lowest() && from <= T::max()) {
-      return static_cast<To>(from);
-    }
-    // not within range.
-    ec = 1;
-    return {};
-  }
-
-  // nan and inf will be preserved
-  return static_cast<To>(from);
-}  // function
-
-template <typename To, typename From,
-          FMT_ENABLE_IF(std::is_same<From, To>::value)>
-FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) {
-  ec = 0;
-  static_assert(std::is_floating_point<From>::value, "From must be floating");
-  return from;
-}
-
-/**
- * safe duration cast between integral durations
- */
-template <typename To, typename FromRep, typename FromPeriod,
-          FMT_ENABLE_IF(std::is_integral<FromRep>::value),
-          FMT_ENABLE_IF(std::is_integral<typename To::rep>::value)>
-To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
-                      int& ec) {
-  using From = std::chrono::duration<FromRep, FromPeriod>;
-  ec = 0;
-  // the basic idea is that we need to convert from count() in the from type
-  // to count() in the To type, by multiplying it with this:
-  using Factor = std::ratio_divide<typename From::period, typename To::period>;
-
-  static_assert(Factor::num > 0, "num must be positive");
-  static_assert(Factor::den > 0, "den must be positive");
-
-  // the conversion is like this: multiply from.count() with Factor::num
-  // /Factor::den and convert it to To::rep, all this without
-  // overflow/underflow. let's start by finding a suitable type that can hold
-  // both To, From and Factor::num
-  using IntermediateRep =
-      typename std::common_type<typename From::rep, typename To::rep,
-                                decltype(Factor::num)>::type;
-
-  // safe conversion to IntermediateRep
-  IntermediateRep count =
-      lossless_integral_conversion<IntermediateRep>(from.count(), ec);
-  if (ec) {
-    return {};
-  }
-  // multiply with Factor::num without overflow or underflow
-  if (Factor::num != 1) {
-    constexpr auto max1 =
-        std::numeric_limits<IntermediateRep>::max() / Factor::num;
-    if (count > max1) {
-      ec = 1;
-      return {};
-    }
-    constexpr auto min1 =
-        std::numeric_limits<IntermediateRep>::min() / Factor::num;
-    if (count < min1) {
-      ec = 1;
-      return {};
-    }
-    count *= Factor::num;
-  }
-
-  // this can't go wrong, right? den>0 is checked earlier.
-  if (Factor::den != 1) {
-    count /= Factor::den;
-  }
-  // convert to the to type, safely
-  using ToRep = typename To::rep;
-  const ToRep tocount = lossless_integral_conversion<ToRep>(count, ec);
-  if (ec) {
-    return {};
-  }
-  return To{tocount};
-}
-
-/**
- * safe duration_cast between floating point durations
- */
-template <typename To, typename FromRep, typename FromPeriod,
-          FMT_ENABLE_IF(std::is_floating_point<FromRep>::value),
-          FMT_ENABLE_IF(std::is_floating_point<typename To::rep>::value)>
-To safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
-                      int& ec) {
-  using From = std::chrono::duration<FromRep, FromPeriod>;
-  ec = 0;
-  if (std::isnan(from.count())) {
-    // nan in, gives nan out. easy.
-    return To{std::numeric_limits<typename To::rep>::quiet_NaN()};
-  }
-  // maybe we should also check if from is denormal, and decide what to do about
-  // it.
-
-  // +-inf should be preserved.
-  if (std::isinf(from.count())) {
-    return To{from.count()};
-  }
-
-  // the basic idea is that we need to convert from count() in the from type
-  // to count() in the To type, by multiplying it with this:
-  using Factor = std::ratio_divide<typename From::period, typename To::period>;
-
-  static_assert(Factor::num > 0, "num must be positive");
-  static_assert(Factor::den > 0, "den must be positive");
-
-  // the conversion is like this: multiply from.count() with Factor::num
-  // /Factor::den and convert it to To::rep, all this without
-  // overflow/underflow. let's start by finding a suitable type that can hold
-  // both To, From and Factor::num
-  using IntermediateRep =
-      typename std::common_type<typename From::rep, typename To::rep,
-                                decltype(Factor::num)>::type;
-
-  // force conversion of From::rep -> IntermediateRep to be safe,
-  // even if it will never happen be narrowing in this context.
-  IntermediateRep count =
-      safe_float_conversion<IntermediateRep>(from.count(), ec);
-  if (ec) {
-    return {};
-  }
-
-  // multiply with Factor::num without overflow or underflow
-  if (Factor::num != 1) {
-    constexpr auto max1 = std::numeric_limits<IntermediateRep>::max() /
-                          static_cast<IntermediateRep>(Factor::num);
-    if (count > max1) {
-      ec = 1;
-      return {};
-    }
-    constexpr auto min1 = std::numeric_limits<IntermediateRep>::lowest() /
-                          static_cast<IntermediateRep>(Factor::num);
-    if (count < min1) {
-      ec = 1;
-      return {};
-    }
-    count *= static_cast<IntermediateRep>(Factor::num);
-  }
-
-  // this can't go wrong, right? den>0 is checked earlier.
-  if (Factor::den != 1) {
-    using common_t = typename std::common_type<IntermediateRep, intmax_t>::type;
-    count /= static_cast<common_t>(Factor::den);
-  }
-
-  // convert to the to type, safely
-  using ToRep = typename To::rep;
-
-  const ToRep tocount = safe_float_conversion<ToRep>(count, ec);
-  if (ec) {
-    return {};
-  }
-  return To{tocount};
-}
-
-}  // namespace safe_duration_cast
-
-FMT_END_NAMESPACE
diff --git a/src/format.cc b/src/format.cc
index 41076f1..c373db1 100644
--- a/src/format.cc
+++ b/src/format.cc
@@ -10,9 +10,10 @@
 FMT_BEGIN_NAMESPACE
 template struct FMT_API internal::basic_data<void>;
 
-// Workaround a bug in MSVC2013 that prevents instantiation of grisu_format.
-bool (*instantiate_grisu_format)(double, internal::buffer<char>&, int, unsigned,
-                                 int&) = internal::grisu_format;
+// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
+int (*instantiate_format_float)(double, int, internal::float_specs,
+                                internal::buffer<char>&) =
+    internal::format_float;
 
 #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
 template FMT_API internal::locale_ref::locale_ref(const std::locale& loc);
@@ -21,6 +22,7 @@
 
 // Explicit instantiations for char.
 
+template FMT_API std::string internal::grouping_impl<char>(locale_ref);
 template FMT_API char internal::thousands_sep_impl(locale_ref);
 template FMT_API char internal::decimal_point_impl(locale_ref);
 
@@ -35,23 +37,27 @@
 template FMT_API format_context::iterator internal::vformat_to(
     internal::buffer<char>&, string_view, basic_format_args<format_context>);
 
-template FMT_API char* internal::sprintf_format(double, internal::buffer<char>&,
-                                                sprintf_specs);
-template FMT_API char* internal::sprintf_format(long double,
-                                                internal::buffer<char>&,
-                                                sprintf_specs);
+template FMT_API int internal::snprintf_float(double, int,
+                                              internal::float_specs,
+                                              internal::buffer<char>&);
+template FMT_API int internal::snprintf_float(long double, int,
+                                              internal::float_specs,
+                                              internal::buffer<char>&);
+template FMT_API int internal::format_float(double, int, internal::float_specs,
+                                            internal::buffer<char>&);
+template FMT_API int internal::format_float(long double, int,
+                                            internal::float_specs,
+                                            internal::buffer<char>&);
 
 // Explicit instantiations for wchar_t.
 
+template FMT_API std::string internal::grouping_impl<wchar_t>(locale_ref);
 template FMT_API wchar_t internal::thousands_sep_impl(locale_ref);
 template FMT_API wchar_t internal::decimal_point_impl(locale_ref);
 
 template FMT_API void internal::buffer<wchar_t>::append(const wchar_t*,
                                                         const wchar_t*);
 
-template FMT_API void internal::arg_map<wformat_context>::init(
-    const basic_format_args<wformat_context>&);
-
 template FMT_API std::wstring internal::vformat<wchar_t>(
     wstring_view, basic_format_args<wformat_context>);
 FMT_END_NAMESPACE
diff --git a/src/posix.cc b/src/posix.cc
index 69c2781..3db72b7 100644
--- a/src/posix.cc
+++ b/src/posix.cc
@@ -12,7 +12,9 @@
 
 #include "fmt/posix.h"
 
-#include <limits.h>
+#include <climits>
+
+#if FMT_USE_FCNTL
 #include <sys/stat.h>
 #include <sys/types.h>
 
@@ -39,8 +41,8 @@
 #  ifdef __MINGW32__
 #    define _SH_DENYNO 0x40
 #  endif
-
 #endif  // _WIN32
+#endif  // FMT_USE_FCNTL
 
 #ifdef fileno
 #  undef fileno
@@ -49,7 +51,7 @@
 namespace {
 #ifdef _WIN32
 // Return type of read and write functions.
-typedef int RWResult;
+using RWResult = int;
 
 // On Windows the count argument to read and write is unsigned, so convert
 // it from size_t preventing integer overflow.
@@ -58,7 +60,7 @@
 }
 #else
 // Return type of read and write functions.
-typedef ssize_t RWResult;
+using RWResult = ssize_t;
 
 inline std::size_t convert_rwcount(std::size_t count) { return count; }
 #endif
@@ -94,6 +96,7 @@
   return fd;
 }
 
+#if FMT_USE_FCNTL
 file::file(cstring_view path, int oflag) {
   int mode = S_IRUSR | S_IWUSR;
 #if defined(_WIN32) && !defined(__MINGW32__)
@@ -138,7 +141,7 @@
   unsigned long long long_size = size_upper;
   return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
 #else
-  typedef struct stat Stat;
+  using Stat = struct stat;
   Stat file_stat = Stat();
   if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
     FMT_THROW(system_error(errno, "cannot get file attributes"));
@@ -230,4 +233,5 @@
   return size;
 #endif
 }
+#endif  // FMT_USE_FCNTL
 FMT_END_NAMESPACE
diff --git a/support/Vagrantfile b/support/Vagrantfile
index de0fcb9..24f166a 100644
--- a/support/Vagrantfile
+++ b/support/Vagrantfile
@@ -3,7 +3,7 @@
 
 # A vagrant config for testing against gcc-4.8.
 Vagrant.configure("2") do |config|
-  config.vm.box = "ubuntu/trusty64"
+  config.vm.box = "ubuntu/xenial64"
 
   config.vm.provider "virtualbox" do |vb|
     vb.memory = "4096"
diff --git a/support/cmake/fmt.pc.in b/support/cmake/fmt.pc.in
index 79de0b6..e935dc7 100644
--- a/support/cmake/fmt.pc.in
+++ b/support/cmake/fmt.pc.in
@@ -1,7 +1,7 @@
 prefix=@CMAKE_INSTALL_PREFIX@
 exec_prefix=@CMAKE_INSTALL_PREFIX@
-libdir=@CMAKE_INSTALL_FULL_LIBDIR@
-includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@
+libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@
+includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@
 
 Name: fmt
 Description: A modern formatting library
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index e399096..8aab2c9 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -47,8 +47,6 @@
 
 set(TEST_MAIN_SRC test-main.cc gtest-extra.cc gtest-extra.h util.cc)
 add_library(test-main STATIC ${TEST_MAIN_SRC})
-target_compile_definitions(test-main PUBLIC
-  FMT_USE_FILE_DESCRIPTORS=$<BOOL:${HAVE_OPEN}>)
 target_include_directories(test-main SYSTEM PUBLIC gtest gmock)
 target_link_libraries(test-main gmock fmt)
 
@@ -84,6 +82,9 @@
   if (FMT_PEDANTIC)
     target_compile_options(${name} PRIVATE ${PEDANTIC_COMPILE_FLAGS})
   endif ()
+  if (FMT_WERROR)
+    target_compile_options(${name} PRIVATE ${WERROR_FLAG})
+  endif ()
   target_include_directories(${name} SYSTEM PUBLIC gtest gmock)
   add_test(NAME ${name} COMMAND ${name})
 endfunction()
@@ -96,6 +97,9 @@
 target_compile_definitions(grisu-test PRIVATE FMT_USE_GRISU=1)
 add_fmt_test(gtest-extra-test)
 add_fmt_test(format-test mock-allocator.h)
+if (MSVC)
+  target_compile_options(format-test PRIVATE /bigobj)
+endif ()
 if (NOT (MSVC AND BUILD_SHARED_LIBS))
   add_fmt_test(format-impl-test)
 endif ()
@@ -107,12 +111,11 @@
 add_fmt_test(ranges-test)
 add_fmt_test(scan-test)
 
-if (HAVE_OPEN)
+if (NOT MSVC_BUILD_STATIC)
   add_fmt_executable(posix-mock-test
     posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC})
   target_include_directories(
     posix-mock-test PRIVATE ${PROJECT_SOURCE_DIR}/include)
-  target_compile_definitions(posix-mock-test PRIVATE FMT_USE_FILE_DESCRIPTORS=1)
   target_link_libraries(posix-mock-test gmock)
   target_include_directories(posix-mock-test SYSTEM PUBLIC gtest gmock)
   if (FMT_PEDANTIC)
@@ -225,3 +228,21 @@
     "-DPEDANTIC_COMPILE_FLAGS=${PEDANTIC_COMPILE_FLAGS}"
     "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}")
 endif ()
+
+# Activate optional CUDA tests if CUDA is found. For version selection, see
+# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
+if (${CMAKE_VERSION} VERSION_LESS 3.15)
+  find_package(CUDA 9.0)
+else ()
+  include(CheckLanguage)
+  check_language(CUDA)
+  if (CMAKE_CUDA_COMPILER)
+    enable_language(CUDA OPTIONAL)
+    set(CUDA_FOUND TRUE)
+  endif ()
+endif ()
+
+if (CUDA_FOUND)
+  add_subdirectory(cuda-test)
+  add_test(NAME cuda-test COMMAND fmt-in-cuda-test)
+endif ()
diff --git a/test/assert-test.cc b/test/assert-test.cc
index 0679cee..26a87a7 100644
--- a/test/assert-test.cc
+++ b/test/assert-test.cc
@@ -20,3 +20,14 @@
   EXPECT_DEBUG_DEATH_IF_SUPPORTED(FMT_ASSERT(false, "don't panic!"),
                                   "don't panic!");
 }
+
+bool test_condition = false;
+
+TEST(AssertTest, DanglingElse) {
+  bool executed_else = false;
+  if (test_condition)
+    FMT_ASSERT(true, "");
+  else
+    executed_else = true;
+  EXPECT_TRUE(executed_else);
+}
diff --git a/test/color-test.cc b/test/color-test.cc
index 62cfca0..fde3a0c 100644
--- a/test/color-test.cc
+++ b/test/color-test.cc
@@ -47,7 +47,7 @@
                "\x1b[105mtbmagenta\x1b[0m");
 }
 
-TEST(ColorsTest, ColorsFormat) {
+TEST(ColorsTest, Format) {
   EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
             "\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
   EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
@@ -78,4 +78,6 @@
             "\x1b[92mtbgreen\x1b[0m");
   EXPECT_EQ(fmt::format(bg(fmt::terminal_color::bright_magenta), "tbmagenta"),
             "\x1b[105mtbmagenta\x1b[0m");
+  EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"),
+            "\x1b[31mfoo\x1b[0m");
 }
diff --git a/test/compile-test.cc b/test/compile-test.cc
index 32a1da0..13bef48 100644
--- a/test/compile-test.cc
+++ b/test/compile-test.cc
@@ -34,205 +34,17 @@
 using testing::Return;
 using testing::StrictMock;
 
-FMT_BEGIN_NAMESPACE
-namespace internal {
-bool operator==(const internal::string_view_metadata& lhs,
-                const internal::string_view_metadata& rhs) {
-  return std::tie(lhs.offset_, lhs.size_) == std::tie(rhs.offset_, rhs.size_);
-}
-bool operator!=(const internal::string_view_metadata& lhs,
-                const internal::string_view_metadata& rhs) {
-  return !(lhs == rhs);
-}
-
-bool operator==(const format_part<char>::specification& lhs,
-                const format_part<char>::specification& rhs) {
-  if (lhs.arg_id.which != rhs.arg_id.which) {
-    return false;
-  }
-
-  typedef format_part<char>::argument_id::which_arg_id which_arg_id;
-  switch (lhs.arg_id.which) {
-  case which_arg_id::index: {
-    if (lhs.arg_id.val.index != rhs.arg_id.val.index) {
-      return false;
-    }
-  } break;
-  case which_arg_id::named_index: {
-    if (lhs.arg_id.val.named_index != rhs.arg_id.val.named_index) {
-      return false;
-    }
-  } break;
-  }
-
-  return std::tie(lhs.parsed_specs.width, lhs.parsed_specs.fill[0],
-                  lhs.parsed_specs.align, lhs.parsed_specs.precision,
-                  lhs.parsed_specs.sign, lhs.parsed_specs.type) ==
-         std::tie(rhs.parsed_specs.width, rhs.parsed_specs.fill[0],
-                  rhs.parsed_specs.align, rhs.parsed_specs.precision,
-                  rhs.parsed_specs.sign, rhs.parsed_specs.type);
-}
-
-bool operator!=(const format_part<char>::specification& lhs,
-                const format_part<char>::specification& rhs) {
-  return !(lhs == rhs);
-}
-
-bool operator==(const format_part<char>& lhs,
-                const fmt::internal::format_part<char>& rhs) {
-  typedef format_part<char>::kind kind;
-
-  if (lhs.which != rhs.which ||
-      lhs.end_of_argument_id != rhs.end_of_argument_id) {
-    return false;
-  }
-
-  switch (lhs.which) {
-  case kind::argument_id: {
-    return lhs.val.arg_id == rhs.val.arg_id;
-  }
-
-  case kind::named_argument_id: {
-    return lhs.val.named_arg_id == rhs.val.named_arg_id;
-  }
-
-  case kind::text: {
-    return lhs.val.text == rhs.val.text;
-  }
-
-  case kind::specification: {
-    return lhs.val.spec == rhs.val.spec;
-  }
-  }
-
-  return false;
-}
-
-bool operator!=(const fmt::internal::format_part<char>& lhs,
-                const fmt::internal::format_part<char>& rhs) {
-  return !(lhs == rhs);
-}
-}
-FMT_END_NAMESPACE
-
-TEST(CompileTest, FormatPart_ComparisonOperators) {
-  typedef fmt::internal::format_part<char> format_part;
-  typedef fmt::internal::dynamic_format_specs<char> prepared_specs;
-
-  {
-    const auto part = format_part(0u);
-    const auto other = format_part(0u);
-    EXPECT_EQ(part, other);
-  }
-  {
-    const auto lhs = format_part(0u);
-    const auto rhs = format_part(1u);
-    EXPECT_NE(lhs, rhs);
-  }
-  {
-    const auto lhs = format_part(fmt::internal::string_view_metadata(0, 42));
-    const auto rhs = format_part(fmt::internal::string_view_metadata(0, 42));
-    EXPECT_EQ(lhs, rhs);
-  }
-  {
-    const auto lhs = format_part(fmt::internal::string_view_metadata(0, 42));
-    const auto rhs = format_part(fmt::internal::string_view_metadata(0, 4422));
-    EXPECT_NE(lhs, rhs);
-  }
-  {
-    auto lhs = format_part(0u);
-    auto rhs = format_part(fmt::internal::string_view_metadata(0, 42));
-    EXPECT_NE(lhs, rhs);
-    rhs = format_part(fmt::internal::string_view_metadata(0, 0));
-    EXPECT_NE(lhs, rhs);
-  }
-  {
-    auto lhs = format_part(0u);
-    lhs.end_of_argument_id = 42;
-    auto rhs = format_part(0u);
-    rhs.end_of_argument_id = 42;
-    EXPECT_EQ(lhs, rhs);
-    rhs.end_of_argument_id = 13;
-    EXPECT_NE(lhs, rhs);
-  }
-  {
-    const auto specs_argument_id = 0u;
-    const auto specs_named_argument_id =
-        fmt::internal::string_view_metadata(0, 42);
-    auto specs = format_part::specification(0u);
-    auto lhs = format_part(specs);
-    auto rhs = format_part(specs);
-    EXPECT_EQ(lhs, rhs);
-
-    specs.parsed_specs = prepared_specs();
-    lhs = format_part(specs);
-    rhs = format_part(specs);
-    EXPECT_EQ(lhs, rhs);
-
-    specs = format_part::specification(specs_named_argument_id);
-    lhs = format_part(specs);
-    rhs = format_part(specs);
-    EXPECT_EQ(lhs, rhs);
-
-    specs.parsed_specs = prepared_specs();
-    lhs = format_part(specs);
-    rhs = format_part(specs);
-    EXPECT_EQ(lhs, rhs);
-
-    auto lhs_spec = format_part::specification(specs_argument_id);
-    auto rhs_spec = format_part::specification(specs_named_argument_id);
-    lhs = format_part(lhs_spec);
-    rhs = format_part(rhs_spec);
-    EXPECT_NE(lhs, rhs);
-
-    lhs_spec = format_part::specification(specs_argument_id);
-    rhs_spec = format_part::specification(specs_argument_id);
-    lhs_spec.parsed_specs.precision = 1;
-    rhs_spec.parsed_specs.precision = 2;
-    lhs = format_part(lhs_spec);
-    rhs = format_part(rhs_spec);
-    EXPECT_NE(lhs, rhs);
-  }
-  {
-    const auto specs_argument_id = 0u;
-    const auto specs_named_argument_id =
-        fmt::internal::string_view_metadata(0, 42);
-    auto specs = format_part::specification(specs_argument_id);
-    auto lhs = format_part(specs);
-    auto rhs = format_part(0u);
-    auto rhs2 = format_part(fmt::internal::string_view_metadata(0, 42));
-    EXPECT_NE(lhs, rhs);
-    EXPECT_NE(lhs, rhs2);
-
-    specs.parsed_specs = prepared_specs();
-    lhs = format_part{specs};
-    EXPECT_NE(lhs, rhs);
-    EXPECT_NE(lhs, rhs2);
-
-    specs = format_part::specification(specs_named_argument_id);
-    EXPECT_NE(lhs, rhs);
-    EXPECT_NE(lhs, rhs2);
-
-    specs.parsed_specs = prepared_specs();
-    lhs = format_part(specs);
-    EXPECT_NE(lhs, rhs);
-    EXPECT_NE(lhs, rhs2);
-  }
-}
-
 // compiletime_prepared_parts_type_provider is useful only with relaxed
 // constexpr.
 #if FMT_USE_CONSTEXPR
 template <unsigned EXPECTED_PARTS_COUNT, typename Format>
 void check_prepared_parts_type(Format format) {
-  typedef fmt::internal::compiletime_prepared_parts_type_provider<decltype(
-      format)>
-      provider;
-  typedef typename provider::template format_parts_array<EXPECTED_PARTS_COUNT>
-      expected_parts_type;
-  static_assert(
-      std::is_same<typename provider::type, expected_parts_type>::value,
-      "CompileTimePreparedPartsTypeProvider test failed");
+  typedef fmt::internal::compiled_format_base<decltype(format)> provider;
+  typedef fmt::internal::format_part<char>
+      expected_parts_type[EXPECTED_PARTS_COUNT];
+  static_assert(std::is_same<typename provider::parts_container,
+                             expected_parts_type>::value,
+                "CompileTimePreparedPartsTypeProvider test failed");
 }
 
 TEST(CompileTest, CompileTimePreparedPartsTypeProvider) {
@@ -253,40 +65,6 @@
 }
 #endif
 
-class custom_parts_container {
- public:
-  typedef fmt::internal::format_part<char> format_part_type;
-
- private:
-  typedef std::deque<format_part_type> parts;
-
- public:
-  void add(format_part_type part) { parts_.push_back(std::move(part)); }
-
-  void substitute_last(format_part_type part) {
-    parts_.back() = std::move(part);
-  }
-
-  format_part_type last() { return parts_.back(); }
-
-  auto begin() -> decltype(std::declval<parts>().begin()) {
-    return parts_.begin();
-  }
-
-  auto begin() const -> decltype(std::declval<const parts>().begin()) {
-    return parts_.begin();
-  }
-
-  auto end() -> decltype(std::declval<parts>().begin()) { return parts_.end(); }
-
-  auto end() const -> decltype(std::declval<const parts>().begin()) {
-    return parts_.end();
-  }
-
- private:
-  parts parts_;
-};
-
 TEST(CompileTest, PassStringLiteralFormat) {
   const auto prepared = fmt::compile<int>("test {}");
   EXPECT_EQ("test 42", fmt::format(prepared, 42));
@@ -338,3 +116,28 @@
   auto f = fmt::compile<int>("{:10}");
   EXPECT_EQ(fmt::formatted_size(f, 42), 10);
 }
+
+TEST(CompileTest, MultipleTypes) {
+  auto f = fmt::compile<int, int>("{} {}");
+  EXPECT_EQ(fmt::format(f, 42, 42), "42 42");
+}
+
+struct formattable {};
+
+FMT_BEGIN_NAMESPACE
+template <> struct formatter<formattable> : formatter<const char*> {
+  auto format(formattable, format_context& ctx) -> decltype(ctx.out()) {
+    return formatter<const char*>::format("foo", ctx);
+  }
+};
+FMT_END_NAMESPACE
+
+TEST(CompileTest, FormatUserDefinedType) {
+  auto f = fmt::compile<formattable>("{}");
+  EXPECT_EQ(fmt::format(f, formattable()), "foo");
+}
+
+TEST(CompileTest, EmptyFormatString) {
+  auto f = fmt::compile<>("");
+  EXPECT_EQ(fmt::format(f), "");
+}
diff --git a/test/core-test.cc b/test/core-test.cc
index 0de26db..e2eec3e 100644
--- a/test/core-test.cc
+++ b/test/core-test.cc
@@ -103,7 +103,7 @@
 TEST(BufferTest, Ctor) {
   {
     mock_buffer<int> buffer;
-    EXPECT_EQ(nullptr, &buffer[0]);
+    EXPECT_EQ(nullptr, buffer.data());
     EXPECT_EQ(static_cast<size_t>(0), buffer.size());
     EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
   }
@@ -127,8 +127,13 @@
 struct dying_buffer : test_buffer<int> {
   MOCK_METHOD0(die, void());
   ~dying_buffer() { die(); }
+
+ private:
+  virtual void avoid_weak_vtable();
 };
 
+void dying_buffer::avoid_weak_vtable() {}
+
 TEST(BufferTest, VirtualDtor) {
   typedef StrictMock<dying_buffer> stict_mock_buffer;
   stict_mock_buffer* mock_buffer = new stict_mock_buffer();
@@ -285,8 +290,6 @@
 VISIT_TYPE(unsigned long, unsigned long long);
 #endif
 
-VISIT_TYPE(float, double);
-
 #define CHECK_ARG_(Char, expected, value)                                     \
   {                                                                           \
     testing::StrictMock<mock_visitor<decltype(expected)>> visitor;            \
@@ -450,11 +453,11 @@
 FMT_END_NAMESPACE
 
 TEST(CoreTest, HasFormatter) {
-  using fmt::internal::has_formatter;
+  using fmt::has_formatter;
   using context = fmt::format_context;
-  EXPECT_TRUE((has_formatter<enabled_formatter, context>::value));
-  EXPECT_FALSE((has_formatter<disabled_formatter, context>::value));
-  EXPECT_FALSE((has_formatter<disabled_formatter_convertible, context>::value));
+  static_assert(has_formatter<enabled_formatter, context>::value, "");
+  static_assert(!has_formatter<disabled_formatter, context>::value, "");
+  static_assert(!has_formatter<disabled_formatter_convertible, context>::value, "");
 }
 
 struct convertible_to_int {
@@ -613,6 +616,17 @@
   EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_view()));
 }
 
+#ifdef FMT_USE_STRING_VIEW
+struct explicitly_convertible_to_std_string_view {
+  explicit operator std::string_view() const { return "foo"; }
+};
+
+TEST(FormatterTest, FormatExplicitlyConvertibleToStdStringView) {
+  EXPECT_EQ("foo",
+            fmt::format("{}", explicitly_convertible_to_std_string_view()));
+}
+#endif
+
 struct explicitly_convertible_to_wstring_view {
   explicit operator fmt::wstring_view() const { return L"foo"; }
 };
@@ -621,17 +635,15 @@
   EXPECT_EQ(L"foo",
             fmt::format(L"{}", explicitly_convertible_to_wstring_view()));
 }
+#endif
 
-struct explicitly_convertible_to_string_like {
-  template <typename String,
-            typename = typename std::enable_if<std::is_constructible<
-                String, const char*, std::size_t>::value>::type>
-  explicit operator String() const {
-    return String("foo", 3u);
-  }
+struct disabled_rvalue_conversion {
+  operator const char*() const& { return "foo"; }
+  operator const char*()& { return "foo"; }
+  operator const char*() const&& = delete;
+  operator const char*()&& = delete;
 };
 
-TEST(FormatterTest, FormatExplicitlyConvertibleToStringLike) {
-  EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_like()));
+TEST(FormatterTest, DisabledRValueConversion) {
+  EXPECT_EQ("foo", fmt::format("{}", disabled_rvalue_conversion()));
 }
-#endif
diff --git a/test/cuda-test/CMakeLists.txt b/test/cuda-test/CMakeLists.txt
new file mode 100644
index 0000000..be8685b
--- /dev/null
+++ b/test/cuda-test/CMakeLists.txt
@@ -0,0 +1,73 @@
+# We can find some usecases which follow the guide of CMake which uses
+# `enable_language(CUDA)` instead of `find_package(CUDA)` and let the CMake
+# built-in functions use NVCC.
+
+# See: https://cmake.org/cmake/help/latest/module/FindCUDA.html#replacement
+#
+# However, this requires CMake version 3.10 or higher and we can't be sure most
+# of the CUDA projects are using those.
+#
+# This test relies on `find_package(CUDA)` in the parent CMake config.
+
+# These can be updated when NVCC becomes ready for C++ 17 features
+# https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#cpp14-language-features
+set(CMAKE_CUDA_STANDARD 14)
+set(CMAKE_CUDA_STANDARD_REQUIRED 14)
+
+# In this test, we assume that the user is going to compile CUDA source code
+# with some libraries (fmt in this case).
+#
+# In addition to that, this test invokes both the C++ host compiler and NVCC
+# by providing another (non-CUDA) C++ source code.
+if (${CMAKE_VERSION} VERSION_LESS 3.15)
+  # https://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html
+  list(APPEND CUDA_NVCC_FLAGS "-std=c++14")
+  if (MSVC)
+    # This is the solution of pytorch:
+    # https://github.com/pytorch/pytorch/pull/7118
+    list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/std:c++14")
+    list(APPEND CUDA_NVCC_FLAGS "-Xcompiler" "/Zc:__cplusplus")
+    # for the reason of this -Xcompiler options, see below.
+  endif ()
+  cuda_add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc)
+  target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14)
+  if (MSVC)
+    # This part is for (non-CUDA) C++ code. MSVC can define incorrect
+    # `__cplusplus` macro. Fix for the issue is to use additional compiler flag. 
+    #
+    # See Also:
+    # https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/
+    # https://github.com/Microsoft/vscode-cpptools/issues/2595
+    target_compile_options(fmt-in-cuda-test PRIVATE /Zc:__cplusplus /permissive-)
+  endif ()
+else()
+  # now using a "new" way of handling CUDA
+  add_executable(fmt-in-cuda-test cuda-cpp14.cu cpp14.cc)
+  set_target_properties(fmt-in-cuda-test PROPERTIES CUDA_SEPARABLE_COMPILATION ON)
+  target_compile_features(fmt-in-cuda-test PRIVATE cxx_std_14)
+  if (MSVC)
+    # with MSVC, 'cxx_std_14' will only propagate to the host code (MSVC), but will
+    # not set __cplusplus correctly anyway, while nvcc will ignore it.
+    # If specified for nvcc on the command line as '-std=c++14' nvcc will emit this
+    # message instead:
+    # nvcc warning : The -std=c++14 flag is not supported with the configured host
+    #                compiler. Flag will be ignored.
+    set_property(SOURCE cuda-cpp14.cu APPEND PROPERTY
+      COMPILE_OPTIONS -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus)
+    set_property(SOURCE cpp14.cc APPEND PROPERTY
+      COMPILE_OPTIONS /std:c++14 /Zc:__cplusplus)
+  endif()
+endif()
+
+get_target_property(IN_USE_CUDA_STANDARD fmt-in-cuda-test CUDA_STANDARD)
+message(STATUS "cuda_standard:          ${IN_USE_CUDA_STANDARD}")
+
+get_target_property(IN_USE_CUDA_STANDARD_REQUIRED
+    fmt-in-cuda-test CUDA_STANDARD_REQUIRED)
+message(STATUS "cuda_standard_required: ${IN_USE_CUDA_STANDARD_REQUIRED}")
+
+# We don't use PUBLIC or other keyword for reasons explained in the
+# CUDA_LINK_LIBRARIES_KEYWORD section in
+# https://cmake.org/cmake/help/latest/module/FindCUDA.html
+target_link_libraries(fmt-in-cuda-test fmt::fmt)
+
diff --git a/test/cuda-test/cpp14.cc b/test/cuda-test/cpp14.cc
new file mode 100644
index 0000000..ad84639
--- /dev/null
+++ b/test/cuda-test/cpp14.cc
@@ -0,0 +1,11 @@
+#include <fmt/core.h>
+
+// The purpose of this part is to ensure NVCC's host compiler also supports
+// the standard version. See 'cuda-cpp14.cu'.
+//
+// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros
+static_assert(__cplusplus >= 201402L, "expect C++ 2014 for host compiler");
+
+auto make_message_cpp() -> std::string {
+  return fmt::format("host compiler \t: __cplusplus == {}", __cplusplus);
+}
diff --git a/test/cuda-test/cuda-cpp14.cu b/test/cuda-test/cuda-cpp14.cu
new file mode 100644
index 0000000..76a9d08
--- /dev/null
+++ b/test/cuda-test/cuda-cpp14.cu
@@ -0,0 +1,28 @@
+//  Direct NVCC command line example:
+//
+//  nvcc ./cuda-cpp14.cu -x cu -I"../include" -l"fmtd" -L"../build/Debug" \
+//       -std=c++14 -Xcompiler /std:c++14 -Xcompiler /Zc:__cplusplus
+
+// Ensure that we are using the latest C++ standard for NVCC
+// The version is C++14
+//
+// https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#c-cplusplus-language-support
+// https://en.cppreference.com/w/cpp/preprocessor/replace#Predefined_macros
+static_assert(__cplusplus >= 201402L, "expect C++ 2014 for nvcc");
+
+#include <fmt/core.h>
+
+#include <cuda.h>
+#include <iostream>
+
+extern auto make_message_cpp() -> std::string;
+extern auto make_message_cuda() -> std::string;
+
+int main() {
+  std::cout << make_message_cuda() << std::endl;
+  std::cout << make_message_cpp() << std::endl;
+}
+
+auto make_message_cuda() -> std::string {
+  return fmt::format("nvcc compiler \t: __cplusplus == {}", __cplusplus);
+}
diff --git a/test/custom-formatter-test.cc b/test/custom-formatter-test.cc
index db1e199..36007ca 100644
--- a/test/custom-formatter-test.cc
+++ b/test/custom-formatter-test.cc
@@ -18,9 +18,9 @@
 // A custom argument formatter that doesn't print `-` for floating-point values
 // rounded to 0.
 class custom_arg_formatter
-    : public fmt::arg_formatter<fmt::internal::buffer_range<char>> {
+    : public fmt::arg_formatter<fmt::buffer_range<char>> {
  public:
-  using range = fmt::internal::buffer_range<char>;
+  using range = fmt::buffer_range<char>;
   typedef fmt::arg_formatter<range> base;
 
   custom_arg_formatter(fmt::format_context& ctx,
diff --git a/test/format b/test/format
index d9472f9..db27614 100644
--- a/test/format
+++ b/test/format
@@ -8,6 +8,7 @@
 #ifndef FMT_FORMAT_
 #define FMT_FORMAT_
 
+#include <cassert>
 #include <variant>
 #include "fmt/format.h"
 
@@ -666,8 +667,7 @@
       break;
     case internal::double_type:
     case internal::long_double_type:
-      handle_float_type_spec(type_spec,
-                             internal::float_type_checker<decltype(eh)>(eh));
+      internal::parse_float_type_spec(specs_, eh);
       break;
     case internal::cstring_type:
       internal::handle_cstring_type_spec(
@@ -690,9 +690,9 @@
   template <typename FormatContext>
   auto format(const T& val, FormatContext& ctx) -> decltype(ctx.out()) {
     fmt::internal::handle_dynamic_spec<fmt::internal::width_checker>(
-        specs_.width, specs_.width_ref, ctx, nullptr);
+        specs_.width, specs_.width_ref, ctx);
     fmt::internal::handle_dynamic_spec<fmt::internal::precision_checker>(
-        specs_.precision, specs_.precision_ref, ctx, nullptr);
+        specs_.precision, specs_.precision_ref, ctx);
     using range_type = fmt::internal::output_range<typename FormatContext::iterator,
                          typename FormatContext::char_type>;
     return visit_format_arg(arg_formatter<range_type>(ctx, nullptr, &specs_),
@@ -718,7 +718,7 @@
 string vformat(string_view fmt, format_args args) {
   fmt::memory_buffer mbuf;
   fmt::internal::buffer<char>& buf = mbuf;
-  using range = fmt::internal::buffer_range<char>;
+  using range = fmt::buffer_range<char>;
   detail::format_handler<detail::arg_formatter<range>, char, format_context>
     h(range(std::back_inserter(buf)), fmt, args, {});
   fmt::internal::parse_format_string<false>(fmt::to_string_view(fmt), h);
diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc
index 2017283..3c94438 100644
--- a/test/format-impl-test.cc
+++ b/test/format-impl-test.cc
@@ -22,53 +22,232 @@
 
 #undef max
 
+using fmt::internal::bigint;
 using fmt::internal::fp;
+using fmt::internal::max_value;
 
-template <bool is_iec559> void test_construct_from_double() {
+static_assert(!std::is_copy_constructible<bigint>::value, "");
+static_assert(!std::is_copy_assignable<bigint>::value, "");
+
+TEST(BigIntTest, Construct) {
+  EXPECT_EQ("", fmt::format("{}", bigint()));
+  EXPECT_EQ("42", fmt::format("{}", bigint(0x42)));
+  EXPECT_EQ("123456789abcedf0", fmt::format("{}", bigint(0x123456789abcedf0)));
+}
+
+TEST(BigIntTest, Compare) {
+  bigint n1(42);
+  bigint n2(42);
+  EXPECT_EQ(compare(n1, n2), 0);
+  n2 <<= 32;
+  EXPECT_LT(compare(n1, n2), 0);
+  bigint n3(43);
+  EXPECT_LT(compare(n1, n3), 0);
+  EXPECT_GT(compare(n3, n1), 0);
+  bigint n4(42 * 0x100000001);
+  EXPECT_LT(compare(n2, n4), 0);
+  EXPECT_GT(compare(n4, n2), 0);
+}
+
+TEST(BigIntTest, AddCompare) {
+  EXPECT_LT(
+      add_compare(bigint(0xffffffff), bigint(0xffffffff), bigint(1) <<= 64), 0);
+  EXPECT_LT(add_compare(bigint(1) <<= 32, bigint(1), bigint(1) <<= 96), 0);
+  EXPECT_GT(add_compare(bigint(1) <<= 32, bigint(0), bigint(0xffffffff)), 0);
+  EXPECT_GT(add_compare(bigint(0), bigint(1) <<= 32, bigint(0xffffffff)), 0);
+  EXPECT_GT(add_compare(bigint(42), bigint(1), bigint(42)), 0);
+  EXPECT_GT(add_compare(bigint(0xffffffff), bigint(1), bigint(0xffffffff)), 0);
+  EXPECT_LT(add_compare(bigint(10), bigint(10), bigint(22)), 0);
+  EXPECT_LT(add_compare(bigint(0x100000010), bigint(0x100000010),
+                        bigint(0x300000010)),
+            0);
+  EXPECT_GT(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
+                        bigint(0x300000000)),
+            0);
+  EXPECT_EQ(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
+                        bigint(0x300000001)),
+            0);
+  EXPECT_LT(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
+                        bigint(0x300000002)),
+            0);
+  EXPECT_LT(add_compare(bigint(0x1ffffffff), bigint(0x100000002),
+                        bigint(0x300000003)),
+            0);
+}
+
+TEST(BigIntTest, ShiftLeft) {
+  bigint n(0x42);
+  n <<= 0;
+  EXPECT_EQ("42", fmt::format("{}", n));
+  n <<= 1;
+  EXPECT_EQ("84", fmt::format("{}", n));
+  n <<= 25;
+  EXPECT_EQ("108000000", fmt::format("{}", n));
+}
+
+TEST(BigIntTest, Multiply) {
+  bigint n(0x42);
+  EXPECT_THROW(n *= 0, assertion_failure);
+  n *= 1;
+  EXPECT_EQ("42", fmt::format("{}", n));
+  n *= 2;
+  EXPECT_EQ("84", fmt::format("{}", n));
+  n *= 0x12345678;
+  EXPECT_EQ("962fc95e0", fmt::format("{}", n));
+  bigint bigmax(max_value<uint32_t>());
+  bigmax *= max_value<uint32_t>();
+  EXPECT_EQ("fffffffe00000001", fmt::format("{}", bigmax));
+  bigmax.assign(max_value<uint64_t>());
+  bigmax *= max_value<uint64_t>();
+  EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", bigmax));
+}
+
+TEST(BigIntTest, Accumulator) {
+  fmt::internal::accumulator acc;
+  EXPECT_EQ(acc.lower, 0);
+  EXPECT_EQ(acc.upper, 0);
+  acc.upper = 12;
+  acc.lower = 34;
+  EXPECT_EQ(static_cast<uint32_t>(acc), 34);
+  acc += 56;
+  EXPECT_EQ(acc.lower, 90);
+  acc += fmt::internal::max_value<uint64_t>();
+  EXPECT_EQ(acc.upper, 13);
+  EXPECT_EQ(acc.lower, 89);
+  acc >>= 32;
+  EXPECT_EQ(acc.upper, 0);
+  EXPECT_EQ(acc.lower, 13 * 0x100000000);
+}
+
+TEST(BigIntTest, Square) {
+  bigint n0(0);
+  n0.square();
+  EXPECT_EQ("0", fmt::format("{}", n0));
+  bigint n1(0x100);
+  n1.square();
+  EXPECT_EQ("10000", fmt::format("{}", n1));
+  bigint n2(0xfffffffff);
+  n2.square();
+  EXPECT_EQ("ffffffffe000000001", fmt::format("{}", n2));
+  bigint n3(max_value<uint64_t>());
+  n3.square();
+  EXPECT_EQ("fffffffffffffffe0000000000000001", fmt::format("{}", n3));
+  bigint n4;
+  n4.assign_pow10(10);
+  EXPECT_EQ("2540be400", fmt::format("{}", n4));
+}
+
+TEST(BigIntTest, DivModAssignZeroDivisor) {
+  bigint zero(0);
+  EXPECT_THROW(bigint(0).divmod_assign(zero), assertion_failure);
+  EXPECT_THROW(bigint(42).divmod_assign(zero), assertion_failure);
+}
+
+TEST(BigIntTest, DivModAssignSelf) {
+  bigint n(100);
+  EXPECT_THROW(n.divmod_assign(n), assertion_failure);
+}
+
+TEST(BigIntTest, DivModAssignUnaligned) {
+  // (42 << 340) / pow(10, 100):
+  bigint n1(42);
+  n1 <<= 340;
+  bigint n2;
+  n2.assign_pow10(100);
+  int result = n1.divmod_assign(n2);
+  EXPECT_EQ(result, 9406);
+  EXPECT_EQ("10f8353019583bfc29ffc8f564e1b9f9d819dbb4cf783e4507eca1539220p96",
+            fmt::format("{}", n1));
+}
+
+TEST(BigIntTest, DivModAssign) {
+  // 100 / 10:
+  bigint n1(100);
+  int result = n1.divmod_assign(bigint(10));
+  EXPECT_EQ(result, 10);
+  EXPECT_EQ("0", fmt::format("{}", n1));
+  // pow(10, 100) / (42 << 320):
+  n1.assign_pow10(100);
+  result = n1.divmod_assign(bigint(42) <<= 320);
+  EXPECT_EQ(result, 111);
+  EXPECT_EQ("13ad2594c37ceb0b2784c4ce0bf38ace408e211a7caab24308a82e8f10p96",
+            fmt::format("{}", n1));
+  // 42 / 100:
+  bigint n2(42);
+  n1.assign_pow10(2);
+  result = n2.divmod_assign(n1);
+  EXPECT_EQ(result, 0);
+  EXPECT_EQ("2a", fmt::format("{}", n2));
+}
+
+template <bool is_iec559> void run_double_tests() {
   fmt::print("warning: double is not IEC559, skipping FP tests\n");
 }
 
-template <> void test_construct_from_double<true>() {
-  auto v = fp(1.23);
-  EXPECT_EQ(v.f, 0x13ae147ae147aeu);
-  EXPECT_EQ(v.e, -52);
+template <> void run_double_tests<true>() {
+  // Construct from double.
+  EXPECT_EQ(fp(1.23), fp(0x13ae147ae147aeu, -52));
+
+  // Compute boundaries:
+  fp value;
+  // Normalized & not power of 2 - equidistant boundaries:
+  auto b = value.assign_with_boundaries(1.23);
+  EXPECT_EQ(value, fp(0x0013ae147ae147ae, -52));
+  EXPECT_EQ(b.lower, 0x9d70a3d70a3d6c00);
+  EXPECT_EQ(b.upper, 0x9d70a3d70a3d7400);
+  // Normalized power of 2 - lower boundary is closer:
+  b = value.assign_with_boundaries(1.9807040628566084e+28);  // 2**94
+  EXPECT_EQ(value, fp(0x0010000000000000, 42));
+  EXPECT_EQ(b.lower, 0x7ffffffffffffe00);
+  EXPECT_EQ(b.upper, 0x8000000000000400);
+  // Smallest normalized double - equidistant boundaries:
+  b = value.assign_with_boundaries(2.2250738585072014e-308);
+  EXPECT_EQ(value, fp(0x0010000000000000, -1074));
+  EXPECT_EQ(b.lower, 0x7ffffffffffffc00);
+  EXPECT_EQ(b.upper, 0x8000000000000400);
+  // Subnormal - equidistant boundaries:
+  b = value.assign_with_boundaries(4.9406564584124654e-324);
+  EXPECT_EQ(value, fp(0x0000000000000001, -1074));
+  EXPECT_EQ(b.lower, 0x4000000000000000);
+  EXPECT_EQ(b.upper, 0xc000000000000000);
 }
 
-TEST(FPTest, ConstructFromDouble) {
-  test_construct_from_double<std::numeric_limits<double>::is_iec559>();
+TEST(FPTest, DoubleTests) {
+  run_double_tests<std::numeric_limits<double>::is_iec559>();
 }
 
 TEST(FPTest, Normalize) {
-  auto v = fp(0xbeef, 42);
-  v.normalize();
-  EXPECT_EQ(0xbeef000000000000, v.f);
-  EXPECT_EQ(-6, v.e);
+  const auto v = fp(0xbeef, 42);
+  auto normalized = normalize(v);
+  EXPECT_EQ(0xbeef000000000000, normalized.f);
+  EXPECT_EQ(-6, normalized.e);
 }
 
-TEST(FPTest, ComputeBoundariesSubnormal) {
-  auto v = fp(0xbeef, 42);
-  fp lower, upper;
-  v.compute_boundaries(lower, upper);
-  EXPECT_EQ(0xbeee800000000000, lower.f);
-  EXPECT_EQ(-6, lower.e);
-  EXPECT_EQ(0xbeef800000000000, upper.f);
-  EXPECT_EQ(-6, upper.e);
-}
-
-TEST(FPTest, ComputeBoundaries) {
-  auto v = fp(0x10000000000000, 42);
-  fp lower, upper;
-  v.compute_boundaries(lower, upper);
-  EXPECT_EQ(0x7ffffffffffffe00, lower.f);
-  EXPECT_EQ(31, lower.e);
-  EXPECT_EQ(0x8000000000000400, upper.f);
-  EXPECT_EQ(31, upper.e);
-}
-
-TEST(FPTest, Subtract) {
-  auto v = fp(123, 1) - fp(102, 1);
-  EXPECT_EQ(v.f, 21u);
-  EXPECT_EQ(v.e, 1);
+TEST(FPTest, ComputeFloatBoundaries) {
+  struct {
+    double x, lower, upper;
+  } tests[] = {
+      // regular
+      {1.5f, 1.4999999403953552, 1.5000000596046448},
+      // boundary
+      {1.0f, 0.9999999701976776, 1.0000000596046448},
+      // min normal
+      {1.1754944e-38f, 1.1754942807573643e-38, 1.1754944208872107e-38},
+      // max subnormal
+      {1.1754942e-38f, 1.1754941406275179e-38, 1.1754942807573643e-38},
+      // min subnormal
+      {1e-45f, 7.006492321624085e-46, 2.1019476964872256e-45},
+  };
+  for (auto test : tests) {
+    fp vlower = normalize(fp(test.lower));
+    fp vupper = normalize(fp(test.upper));
+    vlower.f >>= vupper.e - vlower.e;
+    vlower.e = vupper.e;
+    fp value;
+    auto b = value.assign_float_with_boundaries(test.x);
+    EXPECT_EQ(vlower.f, b.lower);
+    EXPECT_EQ(vupper.f, b.upper);
+  }
 }
 
 TEST(FPTest, Multiply) {
@@ -100,7 +279,7 @@
   EXPECT_EQ(fmt::internal::up, get_round_direction(100, 60, 10));
   for (int i = 41; i < 60; ++i)
     EXPECT_EQ(fmt::internal::unknown, get_round_direction(100, i, 10));
-  uint64_t max = std::numeric_limits<uint64_t>::max();
+  uint64_t max = max_value<uint64_t>();
   EXPECT_THROW(get_round_direction(100, 100, 0), assertion_failure);
   EXPECT_THROW(get_round_direction(100, 0, 100), assertion_failure);
   EXPECT_THROW(get_round_direction(100, 0, 50), assertion_failure);
@@ -132,15 +311,14 @@
   // Check that divisor - error doesn't overflow.
   EXPECT_EQ(handler(1).on_digit('0', 100, 10, 101, exp, false), digits::error);
   // Check that 2 * error doesn't overflow.
-  uint64_t max = std::numeric_limits<uint64_t>::max();
+  uint64_t max = max_value<uint64_t>();
   EXPECT_EQ(handler(1).on_digit('0', max, 10, max - 1, exp, false),
             digits::error);
 }
 
 TEST(FPTest, GrisuFormatCompilesWithNonIEEEDouble) {
   fmt::memory_buffer buf;
-  int exp = 0;
-  grisu_format(4.2f, buf, -1, false, exp);
+  format_float(0.42, -1, fmt::internal::float_specs(), buf);
 }
 
 template <typename T> struct value_extractor {
@@ -149,10 +327,21 @@
   template <typename U> FMT_NORETURN T operator()(U) {
     throw std::runtime_error(fmt::format("invalid type {}", typeid(U).name()));
   }
+
+#ifdef __apple_build_version__
+  // Apple Clang does not define typeid for __int128_t and __uint128_t.
+  FMT_NORETURN T operator()(__int128_t) {
+    throw std::runtime_error(fmt::format("invalid type {}", "__int128_t"));
+  }
+
+  FMT_NORETURN T operator()(__uint128_t) {
+    throw std::runtime_error(fmt::format("invalid type {}", "__uint128_t"));
+  }
+#endif
 };
 
 TEST(FormatTest, ArgConverter) {
-  long long value = std::numeric_limits<long long>::max();
+  long long value = max_value<long long>();
   auto arg = fmt::internal::make_arg<fmt::format_context>(value);
   fmt::visit_format_arg(
       fmt::internal::arg_converter<long long, fmt::format_context>(arg, 'd'),
@@ -243,8 +432,7 @@
 // Tests fmt::internal::count_digits for integer type Int.
 template <typename Int> void test_count_digits() {
   for (Int i = 0; i < 10; ++i) EXPECT_EQ(1u, fmt::internal::count_digits(i));
-  for (Int i = 1, n = 1, end = std::numeric_limits<Int>::max() / 10; n <= end;
-       ++i) {
+  for (Int i = 1, n = 1, end = max_value<Int>() / 10; n <= end; ++i) {
     n *= 10;
     EXPECT_EQ(i, fmt::internal::count_digits(n - 1));
     EXPECT_EQ(i + 1, fmt::internal::count_digits(n));
@@ -259,7 +447,7 @@
 TEST(UtilTest, WriteUIntPtr) {
   fmt::memory_buffer buf;
   fmt::internal::writer writer(buf);
-  writer.write_pointer(fmt::internal::bit_cast<fmt::internal::fallback_uintptr>(
+  writer.write_pointer(fmt::internal::fallback_uintptr(
                            reinterpret_cast<void*>(0xface)),
                        nullptr);
   EXPECT_EQ("0xface", to_string(buf));
diff --git a/test/format-test.cc b/test/format-test.cc
index dfc265f..f52e127 100644
--- a/test/format-test.cc
+++ b/test/format-test.cc
@@ -20,8 +20,14 @@
 #  include <windows.h>
 #endif
 
+// Check if fmt/format.h compiles with the X11 index macro defined.
+#define index(x, y) no nice things
+
 #include "fmt/color.h"
 #include "fmt/format.h"
+
+#undef index
+
 #include "gmock.h"
 #include "gtest-extra.h"
 #include "mock-allocator.h"
@@ -34,12 +40,13 @@
 using std::size_t;
 
 using fmt::basic_memory_buffer;
-using fmt::internal::basic_writer;
 using fmt::format;
 using fmt::format_error;
 using fmt::memory_buffer;
 using fmt::string_view;
 using fmt::wmemory_buffer;
+using fmt::internal::basic_writer;
+using fmt::internal::max_value;
 
 using testing::Return;
 using testing::StrictMock;
@@ -99,7 +106,7 @@
 template <typename Char, typename T>
 ::testing::AssertionResult check_write(const T& value, const char* type) {
   fmt::basic_memory_buffer<Char> buffer;
-  using range = fmt::internal::buffer_range<Char>;
+  using range = fmt::buffer_range<Char>;
   basic_writer<range> writer(buffer);
   writer.write(value);
   std::basic_string<Char> actual = to_string(buffer);
@@ -162,8 +169,7 @@
 }
 
 TEST(UtilTest, ParseNonnegativeInt) {
-  if (std::numeric_limits<int>::max() !=
-      static_cast<int>(static_cast<unsigned>(1) << 31)) {
+  if (max_value<int>() != static_cast<int>(static_cast<unsigned>(1) << 31)) {
     fmt::print("Skipping parse_nonnegative_int test\n");
     return;
   }
@@ -181,7 +187,7 @@
 }
 
 TEST(IteratorTest, CountingIterator) {
-  fmt::internal::counting_iterator<char> it;
+  fmt::internal::counting_iterator it;
   auto prev = it++;
   EXPECT_EQ(prev.count(), 0);
   EXPECT_EQ(it.count(), 1);
@@ -270,7 +276,7 @@
   EXPECT_EQ(alloc, buffer2.get_allocator().get());
 }
 
-TEST(MemoryBufferTest, MoveCtor) {
+TEST(MemoryBufferTest, MoveCtorInlineBuffer) {
   std::allocator<char> alloc;
   basic_memory_buffer<char, 5, TestAllocator> buffer((TestAllocator(&alloc)));
   const char test[] = "test";
@@ -280,15 +286,22 @@
   // dynamic allocation.
   buffer.push_back('a');
   check_move_buffer("testa", buffer);
+}
+
+TEST(MemoryBufferTest, MoveCtorDynamicBuffer) {
+  std::allocator<char> alloc;
+  basic_memory_buffer<char, 4, TestAllocator> buffer((TestAllocator(&alloc)));
+  const char test[] = "test";
+  buffer.append(test, test + 4);
   const char* inline_buffer_ptr = &buffer[0];
   // Adding one more character causes the content to move from the inline to
   // a dynamically allocated buffer.
-  buffer.push_back('b');
-  basic_memory_buffer<char, 5, TestAllocator> buffer2(std::move(buffer));
+  buffer.push_back('a');
+  basic_memory_buffer<char, 4, TestAllocator> buffer2(std::move(buffer));
   // Move should rip the guts of the first buffer.
   EXPECT_EQ(inline_buffer_ptr, &buffer[0]);
-  EXPECT_EQ("testab", std::string(&buffer2[0], buffer2.size()));
-  EXPECT_GT(buffer2.capacity(), 5u);
+  EXPECT_EQ("testa", std::string(&buffer2[0], buffer2.size()));
+  EXPECT_GT(buffer2.capacity(), 4u);
 }
 
 static void check_move_assign_buffer(const char* str,
@@ -477,7 +490,7 @@
   message = fmt::memory_buffer();
 
   // Check if std::allocator throws on allocating max size_t / 2 chars.
-  size_t max_size = std::numeric_limits<size_t>::max() / 2;
+  size_t max_size = max_value<size_t>() / 2;
   bool throws_on_alloc = false;
   try {
     std::allocator<char> alloc;
@@ -525,9 +538,8 @@
   EXPECT_EQ(fmt::format("test: {}", utf8_message.str()),
             fmt::to_string(actual_message));
   actual_message.resize(0);
-  fmt::internal::format_windows_error(
-      actual_message, ERROR_FILE_EXISTS,
-      fmt::string_view(0, std::numeric_limits<size_t>::max()));
+  fmt::internal::format_windows_error(actual_message, ERROR_FILE_EXISTS,
+                                      fmt::string_view(0, max_value<size_t>()));
   EXPECT_EQ(fmt::format("error {}", ERROR_FILE_EXISTS),
             fmt::to_string(actual_message));
 }
@@ -593,31 +605,31 @@
   CHECK_WRITE(static_cast<short>(12));
   CHECK_WRITE(34u);
   CHECK_WRITE(std::numeric_limits<int>::min());
-  CHECK_WRITE(std::numeric_limits<int>::max());
-  CHECK_WRITE(std::numeric_limits<unsigned>::max());
+  CHECK_WRITE(max_value<int>());
+  CHECK_WRITE(max_value<unsigned>());
 }
 
 TEST(WriterTest, WriteLong) {
   CHECK_WRITE(56l);
   CHECK_WRITE(78ul);
   CHECK_WRITE(std::numeric_limits<long>::min());
-  CHECK_WRITE(std::numeric_limits<long>::max());
-  CHECK_WRITE(std::numeric_limits<unsigned long>::max());
+  CHECK_WRITE(max_value<long>());
+  CHECK_WRITE(max_value<unsigned long>());
 }
 
 TEST(WriterTest, WriteLongLong) {
   CHECK_WRITE(56ll);
   CHECK_WRITE(78ull);
   CHECK_WRITE(std::numeric_limits<long long>::min());
-  CHECK_WRITE(std::numeric_limits<long long>::max());
-  CHECK_WRITE(std::numeric_limits<unsigned long long>::max());
+  CHECK_WRITE(max_value<long long>());
+  CHECK_WRITE(max_value<unsigned long long>());
 }
 
 TEST(WriterTest, WriteDouble) {
   CHECK_WRITE(4.2);
   CHECK_WRITE(-4.2);
   auto min = std::numeric_limits<double>::min();
-  auto max = std::numeric_limits<double>::max();
+  auto max = max_value<double>();
   if (fmt::internal::use_grisu<double>()) {
     EXPECT_EQ("2.2250738585072014e-308", fmt::format("{}", min));
     EXPECT_EQ("1.7976931348623157e+308", fmt::format("{}", max));
@@ -637,7 +649,7 @@
   else
     fmt::print("warning: long double formatting with std::swprintf is broken");
   auto min = std::numeric_limits<long double>::min();
-  auto max = std::numeric_limits<long double>::max();
+  auto max = max_value<long double>();
   if (fmt::internal::use_grisu<long double>()) {
     EXPECT_EQ("2.2250738585072014e-308", fmt::format("{}", min));
     EXPECT_EQ("1.7976931348623157e+308", fmt::format("{}", max));
@@ -673,9 +685,7 @@
   CHECK_WRITE_WCHAR("abc");
 }
 
-TEST(WriterTest, WriteWideString) {
-  CHECK_WRITE_WCHAR(L"abc");
-}
+TEST(WriterTest, WriteWideString) { CHECK_WRITE_WCHAR(L"abc"); }
 
 TEST(FormatToTest, FormatWithoutArgs) {
   std::string s;
@@ -697,12 +707,6 @@
   EXPECT_STREQ(buf.data(), L"42");
 }
 
-TEST(FormatToTest, FormatToNonbackInsertIteratorWithSignAndNumericAlignment) {
-  char buffer[16] = {};
-  fmt::format_to(fmt::internal::make_checked(buffer, 16), "{: =+}", 42.0);
-  EXPECT_STREQ("+42.0", buffer);
-}
-
 TEST(FormatToTest, FormatToMemoryBuffer) {
   fmt::basic_memory_buffer<char, 100> buffer;
   fmt::format_to(buffer, "{}", "foo");
@@ -856,6 +860,7 @@
   EXPECT_EQ("  0xface", format("{0:>8}", reinterpret_cast<void*>(0xface)));
 }
 
+#if FMT_NUMERIC_ALIGN
 TEST(FormatterTest, NumericAlign) {
   EXPECT_EQ("  42", format("{0:=4}", 42));
   EXPECT_EQ("+ 42", format("{0:=+4}", 42));
@@ -882,6 +887,13 @@
   EXPECT_EQ(" 1.0", fmt::format("{:= }", 1.0));
 }
 
+TEST(FormatToTest, FormatToNonbackInsertIteratorWithSignAndNumericAlignment) {
+  char buffer[16] = {};
+  fmt::format_to(fmt::internal::make_checked(buffer, 16), "{: =+}", 42.0);
+  EXPECT_STREQ("+42.0", buffer);
+}
+#endif
+
 TEST(FormatterTest, CenterAlign) {
   EXPECT_EQ(" 42  ", format("{0:^5}", 42));
   EXPECT_EQ(" 42  ", format("{0:^5o}", 042));
@@ -1001,6 +1013,7 @@
   EXPECT_EQ("0x42", format("{0:#x}", 0x42));
   EXPECT_EQ("0X42", format("{0:#X}", 0x42));
   EXPECT_EQ("-0x42", format("{0:#x}", -0x42));
+  EXPECT_EQ("0", format("{0:#o}", 0));
   EXPECT_EQ("042", format("{0:#o}", 042));
   EXPECT_EQ("-042", format("{0:#o}", -042));
   EXPECT_EQ("42", format("{0:#}", 42u));
@@ -1185,12 +1198,37 @@
                    "precision not allowed for this argument type");
   EXPECT_THROW_MSG(format("{0:.2f}", 42ull), format_error,
                    "precision not allowed for this argument type");
-  EXPECT_THROW_MSG(format("{0:.2%}", 42), format_error,
-                   "precision not allowed for this argument type");
   EXPECT_THROW_MSG(format("{0:3.0}", 'x'), format_error,
                    "precision not allowed for this argument type");
   EXPECT_EQ("1.2", format("{0:.2}", 1.2345));
   EXPECT_EQ("1.2", format("{0:.2}", 1.2345l));
+  EXPECT_EQ("1.2e+56", format("{:.2}", 1.234e56));
+  EXPECT_EQ("1e+00", format("{:.0e}", 1.0L));
+  EXPECT_EQ("  0.0e+00", format("{:9.1e}", 0.0));
+  EXPECT_EQ(
+      "4.9406564584124654417656879286822137236505980261432476442558568250067550"
+      "727020875186529983636163599237979656469544571773092665671035593979639877"
+      "479601078187812630071319031140452784581716784898210368871863605699873072"
+      "305000638740915356498438731247339727316961514003171538539807412623856559"
+      "117102665855668676818703956031062493194527159149245532930545654440112748"
+      "012970999954193198940908041656332452475714786901472678015935523861155013"
+      "480352649347201937902681071074917033322268447533357208324319361e-324",
+      format("{:.494}", 4.9406564584124654E-324));
+  EXPECT_EQ(
+      "-0X1.41FE3FFE71C9E000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000000000000000000000000"
+      "000000000000000000000000000000000000000000000000000P+127",
+      format("{:.838A}", -2.14001164E+38));
+  EXPECT_EQ("123.", format("{:#.0f}", 123.0));
 
   EXPECT_THROW_MSG(format("{0:.2}", reinterpret_cast<void*>(0xcafe)),
                    format_error,
@@ -1330,9 +1368,17 @@
   EXPECT_EQ("10010001101000101011001111000", format("{0:b}", 0x12345678));
   EXPECT_EQ("10010000101010111100110111101111", format("{0:b}", 0x90ABCDEF));
   EXPECT_EQ("11111111111111111111111111111111",
-            format("{0:b}", std::numeric_limits<uint32_t>::max()));
+            format("{0:b}", max_value<uint32_t>()));
 }
 
+#if FMT_USE_INT128
+constexpr auto int128_max = static_cast<__int128_t>(
+    (static_cast<__uint128_t>(1) << ((__SIZEOF_INT128__ * CHAR_BIT) - 1)) - 1);
+constexpr auto int128_min = -int128_max - 1;
+
+constexpr auto uint128_max = ~static_cast<__uint128_t>(0);
+#endif
+
 TEST(FormatterTest, FormatDec) {
   EXPECT_EQ("0", format("{0}", 0));
   EXPECT_EQ("42", format("{0}", 42));
@@ -1341,6 +1387,23 @@
   EXPECT_EQ("-42", format("{0}", -42));
   EXPECT_EQ("12345", format("{0}", 12345));
   EXPECT_EQ("67890", format("{0}", 67890));
+#if FMT_USE_INT128
+  EXPECT_EQ("0", format("{0}", static_cast<__int128_t>(0)));
+  EXPECT_EQ("0", format("{0}", static_cast<__uint128_t>(0)));
+  EXPECT_EQ("9223372036854775808",
+            format("{0}", static_cast<__int128_t>(INT64_MAX) + 1));
+  EXPECT_EQ("-9223372036854775809",
+            format("{0}", static_cast<__int128_t>(INT64_MIN) - 1));
+  EXPECT_EQ("18446744073709551616",
+            format("{0}", static_cast<__int128_t>(UINT64_MAX) + 1));
+  EXPECT_EQ("170141183460469231731687303715884105727",
+            format("{0}", int128_max));
+  EXPECT_EQ("-170141183460469231731687303715884105728",
+            format("{0}", int128_min));
+  EXPECT_EQ("340282366920938463463374607431768211455",
+            format("{0}", uint128_max));
+#endif
+
   char buffer[BUFFER_SIZE];
   safe_sprintf(buffer, "%d", INT_MIN);
   EXPECT_EQ(buffer, format("{0}", INT_MIN));
@@ -1365,6 +1428,19 @@
   EXPECT_EQ("90abcdef", format("{0:x}", 0x90abcdef));
   EXPECT_EQ("12345678", format("{0:X}", 0x12345678));
   EXPECT_EQ("90ABCDEF", format("{0:X}", 0x90ABCDEF));
+#if FMT_USE_INT128
+  EXPECT_EQ("0", format("{0:x}", static_cast<__int128_t>(0)));
+  EXPECT_EQ("0", format("{0:x}", static_cast<__uint128_t>(0)));
+  EXPECT_EQ("8000000000000000",
+            format("{0:x}", static_cast<__int128_t>(INT64_MAX) + 1));
+  EXPECT_EQ("-8000000000000001",
+            format("{0:x}", static_cast<__int128_t>(INT64_MIN) - 1));
+  EXPECT_EQ("10000000000000000",
+            format("{0:x}", static_cast<__int128_t>(UINT64_MAX) + 1));
+  EXPECT_EQ("7fffffffffffffffffffffffffffffff", format("{0:x}", int128_max));
+  EXPECT_EQ("-80000000000000000000000000000000", format("{0:x}", int128_min));
+  EXPECT_EQ("ffffffffffffffffffffffffffffffff", format("{0:x}", uint128_max));
+#endif
 
   char buffer[BUFFER_SIZE];
   safe_sprintf(buffer, "-%x", 0 - static_cast<unsigned>(INT_MIN));
@@ -1387,6 +1463,23 @@
   EXPECT_EQ("42", format("{0:o}", 042u));
   EXPECT_EQ("-42", format("{0:o}", -042));
   EXPECT_EQ("12345670", format("{0:o}", 012345670));
+#if FMT_USE_INT128
+  EXPECT_EQ("0", format("{0:o}", static_cast<__int128_t>(0)));
+  EXPECT_EQ("0", format("{0:o}", static_cast<__uint128_t>(0)));
+  EXPECT_EQ("1000000000000000000000",
+            format("{0:o}", static_cast<__int128_t>(INT64_MAX) + 1));
+  EXPECT_EQ("-1000000000000000000001",
+            format("{0:o}", static_cast<__int128_t>(INT64_MIN) - 1));
+  EXPECT_EQ("2000000000000000000000",
+            format("{0:o}", static_cast<__int128_t>(UINT64_MAX) + 1));
+  EXPECT_EQ("1777777777777777777777777777777777777777777",
+            format("{0:o}", int128_max));
+  EXPECT_EQ("-2000000000000000000000000000000000000000000",
+            format("{0:o}", int128_min));
+  EXPECT_EQ("3777777777777777777777777777777777777777777",
+            format("{0:o}", uint128_max));
+#endif
+
   char buffer[BUFFER_SIZE];
   safe_sprintf(buffer, "-%o", 0 - static_cast<unsigned>(INT_MIN));
   EXPECT_EQ(buffer, format("{0:o}", INT_MIN));
@@ -1403,11 +1496,7 @@
 }
 
 TEST(FormatterTest, FormatIntLocale) {
-  EXPECT_EQ("123", format("{:n}", 123));
-  EXPECT_EQ("1,234", format("{:n}", 1234));
-  EXPECT_EQ("1,234,567", format("{:n}", 1234567));
-  EXPECT_EQ("4,294,967,295",
-            format("{:n}", std::numeric_limits<uint32_t>::max()));
+  EXPECT_EQ("1234", format("{:n}", 1234));
 }
 
 struct ConvertibleToLongLong {
@@ -1420,7 +1509,6 @@
 
 TEST(FormatterTest, FormatFloat) {
   EXPECT_EQ("392.500000", format("{0:f}", 392.5f));
-  EXPECT_EQ("12.500000%", format("{0:%}", 0.125f));
 }
 
 TEST(FormatterTest, FormatDouble) {
@@ -1433,8 +1521,6 @@
   EXPECT_EQ("392.65", format("{:G}", 392.65));
   EXPECT_EQ("392.650000", format("{:f}", 392.65));
   EXPECT_EQ("392.650000", format("{:F}", 392.65));
-  EXPECT_EQ("12.500000%", format("{:%}", 0.125));
-  EXPECT_EQ("12.34%", format("{:.2%}", 0.1234432));
   char buffer[BUFFER_SIZE];
   safe_sprintf(buffer, "%e", 392.65);
   EXPECT_EQ(buffer, format("{0:e}", 392.65));
@@ -1474,7 +1560,6 @@
   EXPECT_EQ("nan    ", format("{:<7}", nan));
   EXPECT_EQ("  nan  ", format("{:^7}", nan));
   EXPECT_EQ("    nan", format("{:>7}", nan));
-  EXPECT_EQ("nan%", format("{:%}", nan));
 }
 
 TEST(FormatterTest, FormatInfinity) {
@@ -1487,7 +1572,6 @@
   EXPECT_EQ("inf    ", format("{:<7}", inf));
   EXPECT_EQ("  inf  ", format("{:^7}", inf));
   EXPECT_EQ("    inf", format("{:>7}", inf));
-  EXPECT_EQ("inf%", format("{:%}", inf));
 }
 
 TEST(FormatterTest, FormatLongDouble) {
@@ -1498,8 +1582,6 @@
   EXPECT_EQ("392.65", format("{0:G}", 392.65l));
   EXPECT_EQ("392.650000", format("{0:f}", 392.65l));
   EXPECT_EQ("392.650000", format("{0:F}", 392.65l));
-  EXPECT_EQ("12.500000%", format("{:%}", 0.125l));
-  EXPECT_EQ("12.34%", format("{:.2%}", 0.1234432l));
   char buffer[BUFFER_SIZE];
   safe_sprintf(buffer, "%Le", 392.65l);
   EXPECT_EQ(buffer, format("{0:e}", 392.65l));
@@ -1745,13 +1827,12 @@
   EXPECT_EQ("42", fmt::format_int(42ull).str());
   EXPECT_EQ("-42", fmt::format_int(-42ll).str());
   std::ostringstream os;
-  os << std::numeric_limits<int64_t>::max();
-  EXPECT_EQ(os.str(),
-            fmt::format_int(std::numeric_limits<int64_t>::max()).str());
+  os << max_value<int64_t>();
+  EXPECT_EQ(os.str(), fmt::format_int(max_value<int64_t>()).str());
 }
 
 TEST(FormatTest, Print) {
-#if FMT_USE_FILE_DESCRIPTORS
+#if FMT_USE_FCNTL
   EXPECT_WRITE(stdout, fmt::print("Don't {}!", "panic"), "Don't panic!");
   EXPECT_WRITE(stderr, fmt::print(stderr, "Don't {}!", "panic"),
                "Don't panic!");
@@ -1858,7 +1939,7 @@
   EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), Answer()));
   Answer answer;
   EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), answer));
-  const Answer const_answer;
+  const Answer const_answer = Answer();
   EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), const_answer));
 }
 
@@ -1894,6 +1975,11 @@
   EXPECT_EQ("foo", "foo"_format());
   EXPECT_EQ("        42", "{0:10}"_format(42));
 }
+
+TEST(FormatTest, UdlPassUserDefinedObjectAsLvalue) {
+  Date date(2015, 10, 21);
+  EXPECT_EQ("2015-10-21", "{}"_format(date));
+}
 #endif  // FMT_USE_USER_DEFINED_LITERALS
 
 enum TestEnum { A };
@@ -1901,22 +1987,29 @@
 TEST(FormatTest, Enum) { EXPECT_EQ("0", fmt::format("{}", A)); }
 
 TEST(FormatTest, FormatterNotSpecialized) {
-  EXPECT_FALSE((fmt::internal::has_formatter<fmt::formatter<TestEnum>,
-                                             fmt::format_context>::value));
+  static_assert(
+      !fmt::has_formatter<fmt::formatter<TestEnum>, fmt::format_context>::value,
+      "");
 }
 
 #if FMT_HAS_FEATURE(cxx_strong_enums)
-enum TestFixedEnum : short { B };
+enum big_enum : unsigned long long { big_enum_value = 5000000000ULL };
 
-TEST(FormatTest, FixedEnum) { EXPECT_EQ("0", fmt::format("{}", B)); }
+TEST(FormatTest, StrongEnum) {
+  EXPECT_EQ("5000000000", fmt::format("{}", big_enum_value));
+}
 #endif
 
-using buffer_range = fmt::internal::buffer_range<char>;
+using buffer_range = fmt::buffer_range<char>;
 
 class mock_arg_formatter
     : public fmt::internal::arg_formatter_base<buffer_range> {
  private:
+#if FMT_USE_INT128
+  MOCK_METHOD1(call, void(__int128_t value));
+#else
   MOCK_METHOD1(call, void(long long value));
+#endif
 
  public:
   typedef fmt::internal::arg_formatter_base<buffer_range> base;
@@ -1929,14 +2022,14 @@
   }
 
   template <typename T>
-  typename std::enable_if<std::is_integral<T>::value, iterator>::type
+  typename std::enable_if<fmt::internal::is_integral<T>::value, iterator>::type
   operator()(T value) {
     call(value);
     return base::operator()(value);
   }
 
   template <typename T>
-  typename std::enable_if<!std::is_integral<T>::value, iterator>::type
+  typename std::enable_if<!fmt::internal::is_integral<T>::value, iterator>::type
   operator()(T value) {
     return base::operator()(value);
   }
@@ -1988,8 +2081,10 @@
                    "cannot switch from manual to automatic argument indexing");
   EXPECT_THROW_MSG(format("{:{0}}", num), format_error,
                    "cannot switch from automatic to manual argument indexing");
+#if FMT_NUMERIC_ALIGN
   EXPECT_THROW_MSG(format("{:=}", str), format_error,
                    "format specifier requires numeric argument");
+#endif
   EXPECT_THROW_MSG(format("{:+}", str), format_error,
                    "format specifier requires numeric argument");
   EXPECT_THROW_MSG(format("{:-}", str), format_error,
@@ -2214,9 +2309,9 @@
 
 template <size_t N>
 FMT_CONSTEXPR fmt::format_specs parse_specs(const char (&s)[N]) {
-  fmt::format_specs specs;
-  test_parse_context parse_ctx;
-  test_context ctx{};
+  auto specs = fmt::format_specs();
+  auto parse_ctx = test_parse_context();
+  auto ctx = test_context();
   fmt::internal::specs_handler<test_parse_context, test_context> h(
       specs, parse_ctx, ctx);
   parse_format_specs(s, s + N, h);
@@ -2372,13 +2467,15 @@
   EXPECT_ERROR("{0:s", "unknown format specifier", Date);
 #  if FMT_MSC_VER >= 1916
   // This causes an internal compiler error in MSVC2017.
-  EXPECT_ERROR("{0:=5", "unknown format specifier", int);
   EXPECT_ERROR("{:{<}", "invalid fill character '{'", int);
   EXPECT_ERROR("{:10000000000}", "number is too big", int);
   EXPECT_ERROR("{:.10000000000}", "number is too big", int);
   EXPECT_ERROR_NOARGS("{:x}", "argument index out of range");
+#    if FMT_NUMERIC_ALIGN
+  EXPECT_ERROR("{0:=5", "unknown format specifier", int);
   EXPECT_ERROR("{:=}", "format specifier requires numeric argument",
                const char*);
+#    endif
   EXPECT_ERROR("{:+}", "format specifier requires numeric argument",
                const char*);
   EXPECT_ERROR("{:-}", "format specifier requires numeric argument",
@@ -2534,3 +2631,13 @@
   EXPECT_EQ(result.size(), 1);
   EXPECT_EQ(result[0], mychar('x'));
 }
+
+TEST(FormatTest, FormatUTF8Precision) {
+  using str_type = std::basic_string<char8_t>;
+  str_type format(reinterpret_cast<const char8_t*>(u8"{:.4}"));
+  str_type str(reinterpret_cast<const char8_t*>(u8"caf\u00e9s"));  // cafés
+  auto result = fmt::format(format, str);
+  EXPECT_EQ(fmt::internal::count_code_points(result), 4);
+  EXPECT_EQ(result.size(), 5);
+  EXPECT_EQ(result, str.substr(0, 5));
+}
diff --git a/test/grisu-test.cc b/test/grisu-test.cc
index 79a1319..3fc670c 100644
--- a/test/grisu-test.cc
+++ b/test/grisu-test.cc
@@ -48,10 +48,28 @@
   EXPECT_EQ("1e-05", fmt::format("{}", 1e-5));
   EXPECT_EQ("9.999e-05", fmt::format("{}", 9.999e-5));
   EXPECT_EQ("10000000000.0", fmt::format("{}", 1e10));
-  EXPECT_EQ("1e+11", fmt::format("{}", 1e11));
+  EXPECT_EQ("100000000000.0", fmt::format("{}", 1e11));
   EXPECT_EQ("12340000000.0", fmt::format("{}", 1234e7));
   EXPECT_EQ("12.34", fmt::format("{}", 1234e-2));
   EXPECT_EQ("0.001234", fmt::format("{}", 1234e-6));
+  EXPECT_EQ("0.1", fmt::format("{}", 0.1f));
+  EXPECT_EQ("0.10000000149011612", fmt::format("{}", double(0.1f)));
 }
 
 TEST(GrisuTest, ZeroPrecision) { EXPECT_EQ("1", fmt::format("{:.0}", 1.0)); }
+
+TEST(GrisuTest, Fallback) {
+  EXPECT_EQ("1e+23", fmt::format("{}", 1e23));
+  EXPECT_EQ("9e-265", fmt::format("{}", 9e-265));
+  EXPECT_EQ("5.423717798060526e+125",
+            fmt::format("{}", 5.423717798060526e+125));
+  EXPECT_EQ("1.372371880954233e-288",
+            fmt::format("{}", 1.372371880954233e-288));
+  EXPECT_EQ("55388492.622190244", fmt::format("{}", 55388492.622190244));
+  EXPECT_EQ("2.2506787569811123e-253",
+            fmt::format("{}", 2.2506787569811123e-253));
+  EXPECT_EQ("1103618912042992.8", fmt::format("{}", 1103618912042992.8));
+  // pow(2, -25) - assymetric boundaries:
+  EXPECT_EQ("2.9802322387695312e-08",
+            fmt::format("{}", 2.9802322387695312e-08));
+}
diff --git a/test/gtest-extra-test.cc b/test/gtest-extra-test.cc
index 3f8fd5e..99d1d03 100644
--- a/test/gtest-extra-test.cc
+++ b/test/gtest-extra-test.cc
@@ -74,14 +74,6 @@
   EXPECT_EQ(s_ + 1, p_);
 }
 
-// Tests that when EXPECT_WRITE fails, it evaluates its message argument
-// exactly once.
-TEST_F(SingleEvaluationTest, FailedEXPECT_WRITE) {
-  EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), p_++),
-                          "01234");
-  EXPECT_EQ(s_ + 1, p_);
-}
-
 // Tests that assertion arguments are evaluated exactly once.
 TEST_F(SingleEvaluationTest, ExceptionTests) {
   // successful EXPECT_THROW_MSG
@@ -163,6 +155,15 @@
   EXPECT_EQ(4, b_);
 }
 
+#if FMT_USE_FCNTL
+// Tests that when EXPECT_WRITE fails, it evaluates its message argument
+// exactly once.
+TEST_F(SingleEvaluationTest, FailedEXPECT_WRITE) {
+  EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), p_++),
+                          "01234");
+  EXPECT_EQ(s_ + 1, p_);
+}
+
 // Tests that assertion arguments are evaluated exactly once.
 TEST_F(SingleEvaluationTest, WriteTests) {
   // successful EXPECT_WRITE
@@ -187,6 +188,24 @@
   EXPECT_EQ(2, b_);
 }
 
+// Tests EXPECT_WRITE.
+TEST(ExpectTest, EXPECT_WRITE) {
+  EXPECT_WRITE(stdout, do_nothing(), "");
+  EXPECT_WRITE(stdout, std::printf("test"), "test");
+  EXPECT_WRITE(stderr, std::fprintf(stderr, "test"), "test");
+  EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("that"), "this"),
+                          "Expected: this\n"
+                          "  Actual: that");
+}
+
+TEST(StreamingAssertionsTest, EXPECT_WRITE) {
+  EXPECT_WRITE(stdout, std::printf("test"), "test") << "unexpected failure";
+  EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), "other")
+                              << "expected failure",
+                          "expected failure");
+}
+#endif  // FMT_USE_FCNTL
+
 // Tests that the compiler will not complain about unreachable code in the
 // EXPECT_THROW_MSG macro.
 TEST(ExpectThrowTest, DoesNotGenerateUnreachableCodeWarning) {
@@ -280,16 +299,6 @@
           format_system_error(EDOM, "test")));
 }
 
-// Tests EXPECT_WRITE.
-TEST(ExpectTest, EXPECT_WRITE) {
-  EXPECT_WRITE(stdout, do_nothing(), "");
-  EXPECT_WRITE(stdout, std::printf("test"), "test");
-  EXPECT_WRITE(stderr, std::fprintf(stderr, "test"), "test");
-  EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("that"), "this"),
-                          "Expected: this\n"
-                          "  Actual: that");
-}
-
 TEST(StreamingAssertionsTest, EXPECT_THROW_MSG) {
   EXPECT_THROW_MSG(throw_exception(), std::exception, "test")
       << "unexpected failure";
@@ -308,20 +317,13 @@
       "expected failure");
 }
 
-TEST(StreamingAssertionsTest, EXPECT_WRITE) {
-  EXPECT_WRITE(stdout, std::printf("test"), "test") << "unexpected failure";
-  EXPECT_NONFATAL_FAILURE(EXPECT_WRITE(stdout, std::printf("test"), "other")
-                              << "expected failure",
-                          "expected failure");
-}
-
 TEST(UtilTest, FormatSystemError) {
   fmt::memory_buffer out;
   fmt::format_system_error(out, EDOM, "test message");
   EXPECT_EQ(to_string(out), format_system_error(EDOM, "test message"));
 }
 
-#if FMT_USE_FILE_DESCRIPTORS
+#if FMT_USE_FCNTL
 
 using fmt::buffered_file;
 using fmt::error_code;
diff --git a/test/gtest-extra.cc b/test/gtest-extra.cc
index ae89e71..cf92852 100644
--- a/test/gtest-extra.cc
+++ b/test/gtest-extra.cc
@@ -7,7 +7,7 @@
 
 #include "gtest-extra.h"
 
-#if FMT_USE_FILE_DESCRIPTORS
+#if FMT_USE_FCNTL
 
 using fmt::file;
 
@@ -78,7 +78,7 @@
   return buffer;
 }
 
-#endif  // FMT_USE_FILE_DESCRIPTORS
+#endif  // FMT_USE_FCNTL
 
 std::string format_system_error(int error_code, fmt::string_view message) {
   fmt::memory_buffer out;
diff --git a/test/gtest-extra.h b/test/gtest-extra.h
index 4a69336..ee33561 100644
--- a/test/gtest-extra.h
+++ b/test/gtest-extra.h
@@ -10,16 +10,7 @@
 
 #include <string>
 #include "gmock.h"
-
-#include "fmt/core.h"
-
-#ifndef FMT_USE_FILE_DESCRIPTORS
-#  define FMT_USE_FILE_DESCRIPTORS 0
-#endif
-
-#if FMT_USE_FILE_DESCRIPTORS
-#  include "fmt/posix.h"
-#endif
+#include "fmt/posix.h"
 
 #define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
   GTEST_AMBIGUOUS_ELSE_BLOCKER_                                                \
@@ -65,7 +56,7 @@
   EXPECT_THROW_MSG(statement, fmt::system_error,            \
                    format_system_error(error_code, message))
 
-#if FMT_USE_FILE_DESCRIPTORS
+#if FMT_USE_FCNTL
 
 // Captures file output by redirecting it to a pipe.
 // The output it can handle is limited by the pipe capacity.
@@ -151,7 +142,9 @@
 #  define EXPECT_READ(file, expected_content) \
     EXPECT_EQ(expected_content, read(file, std::strlen(expected_content)))
 
-#endif  // FMT_USE_FILE_DESCRIPTORS
+#else
+#  define EXPECT_WRITE(file, statement, expected_output) SUCCEED()
+#endif  // FMT_USE_FCNTL
 
 template <typename Mock> struct ScopedMock : testing::StrictMock<Mock> {
   ScopedMock() { Mock::instance = this; }
diff --git a/test/locale-test.cc b/test/locale-test.cc
index 37d4687..a064ecd 100644
--- a/test/locale-test.cc
+++ b/test/locale-test.cc
@@ -8,45 +8,82 @@
 #include "fmt/locale.h"
 #include "gmock.h"
 
+using fmt::internal::max_value;
+
 #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
 template <typename Char> struct numpunct : std::numpunct<Char> {
  protected:
   Char do_decimal_point() const FMT_OVERRIDE { return '?'; }
+  std::string do_grouping() const FMT_OVERRIDE { return "\03"; }
   Char do_thousands_sep() const FMT_OVERRIDE { return '~'; }
 };
 
+template <typename Char> struct no_grouping : std::numpunct<Char> {
+ protected:
+  Char do_decimal_point() const FMT_OVERRIDE { return '.'; }
+  std::string do_grouping() const FMT_OVERRIDE { return ""; }
+  Char do_thousands_sep() const FMT_OVERRIDE { return ','; }
+};
+
+template <typename Char> struct special_grouping : std::numpunct<Char> {
+ protected:
+  Char do_decimal_point() const FMT_OVERRIDE { return '.'; }
+  std::string do_grouping() const FMT_OVERRIDE { return "\03\02"; }
+  Char do_thousands_sep() const FMT_OVERRIDE { return ','; }
+};
+
+template <typename Char> struct small_grouping : std::numpunct<Char> {
+ protected:
+  Char do_decimal_point() const FMT_OVERRIDE { return '.'; }
+  std::string do_grouping() const FMT_OVERRIDE { return "\01"; }
+  Char do_thousands_sep() const FMT_OVERRIDE { return ','; }
+};
+
 TEST(LocaleTest, DoubleDecimalPoint) {
   std::locale loc(std::locale(), new numpunct<char>());
   EXPECT_EQ("1?23", fmt::format(loc, "{:n}", 1.23));
-  // Test with Grisu disabled.
-  fmt::memory_buffer buf;
-  fmt::internal::writer w(buf, fmt::internal::locale_ref(loc));
-  auto specs = fmt::format_specs();
-  specs.type = 'n';
-  w.write_double<double, false>(1.23, specs);
-  EXPECT_EQ(fmt::to_string(buf), "1?23");
 }
 
 TEST(LocaleTest, Format) {
   std::locale loc(std::locale(), new numpunct<char>());
-  EXPECT_EQ("1,234,567", fmt::format(std::locale(), "{:n}", 1234567));
+  EXPECT_EQ("1234567", fmt::format(std::locale(), "{:n}", 1234567));
   EXPECT_EQ("1~234~567", fmt::format(loc, "{:n}", 1234567));
   fmt::format_arg_store<fmt::format_context, int> as{1234567};
   EXPECT_EQ("1~234~567", fmt::vformat(loc, "{:n}", fmt::format_args(as)));
   std::string s;
   fmt::format_to(std::back_inserter(s), loc, "{:n}", 1234567);
   EXPECT_EQ("1~234~567", s);
+
+  std::locale no_grouping_loc(std::locale(), new no_grouping<char>());
+  EXPECT_EQ("1234567", fmt::format(no_grouping_loc, "{:n}", 1234567));
+
+  std::locale special_grouping_loc(std::locale(), new special_grouping<char>());
+  EXPECT_EQ("1,23,45,678", fmt::format(special_grouping_loc, "{:n}", 12345678));
+
+  std::locale small_grouping_loc(std::locale(), new small_grouping<char>());
+  EXPECT_EQ("4,2,9,4,9,6,7,2,9,5",
+            fmt::format(small_grouping_loc, "{:n}", max_value<uint32_t>()));
 }
 
 TEST(LocaleTest, WFormat) {
   std::locale loc(std::locale(), new numpunct<wchar_t>());
-  EXPECT_EQ(L"1,234,567", fmt::format(std::locale(), L"{:n}", 1234567));
+  EXPECT_EQ(L"1234567", fmt::format(std::locale(), L"{:n}", 1234567));
   EXPECT_EQ(L"1~234~567", fmt::format(loc, L"{:n}", 1234567));
   fmt::format_arg_store<fmt::wformat_context, int> as{1234567};
   EXPECT_EQ(L"1~234~567", fmt::vformat(loc, L"{:n}", fmt::wformat_args(as)));
-  auto sep =
-      std::use_facet<std::numpunct<wchar_t>>(std::locale("C")).thousands_sep();
-  auto result = sep == ',' ? L"1,234,567" : L"1234567";
-  EXPECT_EQ(result, fmt::format(std::locale("C"), L"{:n}", 1234567));
+  EXPECT_EQ(L"1234567", fmt::format(std::locale("C"), L"{:n}", 1234567));
+
+  std::locale no_grouping_loc(std::locale(), new no_grouping<wchar_t>());
+  EXPECT_EQ(L"1234567", fmt::format(no_grouping_loc, L"{:n}", 1234567));
+
+  std::locale special_grouping_loc(std::locale(),
+                                   new special_grouping<wchar_t>());
+  EXPECT_EQ(L"1,23,45,678",
+            fmt::format(special_grouping_loc, L"{:n}", 12345678));
+
+  std::locale small_grouping_loc(std::locale(), new small_grouping<wchar_t>());
+  EXPECT_EQ(L"4,2,9,4,9,6,7,2,9,5",
+            fmt::format(small_grouping_loc, L"{:n}", max_value<uint32_t>()));
 }
+
 #endif  // FMT_STATIC_THOUSANDS_SEPARATOR
diff --git a/test/ostream-test.cc b/test/ostream-test.cc
index fb88ad4..8a55d89 100644
--- a/test/ostream-test.cc
+++ b/test/ostream-test.cc
@@ -64,7 +64,7 @@
   EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum()));
 }
 
-using range = fmt::internal::buffer_range<char>;
+using range = fmt::buffer_range<char>;
 
 struct test_arg_formatter : fmt::arg_formatter<range> {
   fmt::format_parse_context parse_ctx;
@@ -95,8 +95,10 @@
 TEST(OStreamTest, FormatSpecs) {
   EXPECT_EQ("def  ", format("{0:<5}", TestString("def")));
   EXPECT_EQ("  def", format("{0:>5}", TestString("def")));
+#if FMT_NUMERIC_ALIGN
   EXPECT_THROW_MSG(format("{0:=5}", TestString("def")), format_error,
                    "format specifier requires numeric argument");
+#endif
   EXPECT_EQ(" def ", format("{0:^5}", TestString("def")));
   EXPECT_EQ("def**", format("{0:*<5}", TestString("def")));
   EXPECT_THROW_MSG(format("{0:+}", TestString()), format_error,
@@ -143,8 +145,8 @@
 }
 
 TEST(OStreamTest, WriteToOStreamMaxSize) {
-  std::size_t max_size = std::numeric_limits<std::size_t>::max();
-  std::streamsize max_streamsize = std::numeric_limits<std::streamsize>::max();
+  std::size_t max_size = fmt::internal::max_value<std::size_t>();
+  std::streamsize max_streamsize = fmt::internal::max_value<std::streamsize>();
   if (max_size <= fmt::internal::to_unsigned(max_streamsize)) return;
 
   struct test_buffer : fmt::internal::buffer<char> {
@@ -185,8 +187,8 @@
 
 #if FMT_USE_CONSTEXPR
 TEST(OStreamTest, ConstexprString) {
-  EXPECT_EQ("42", format(fmt("{}"), std::string("42")));
-  EXPECT_EQ("a string", format(fmt("{0}"), TestString("a string")));
+  EXPECT_EQ("42", format(FMT_STRING("{}"), std::string("42")));
+  EXPECT_EQ("a string", format(FMT_STRING("{0}"), TestString("a string")));
 }
 #endif
 
@@ -240,3 +242,37 @@
   EXPECT_EQ("{}"_format("test"), "test");
 }
 #endif
+
+template <typename T> struct convertible {
+  T value;
+  explicit convertible(const T& val) : value(val) {}
+  operator T() const { return value; }
+};
+
+TEST(OStreamTest, DisableBuiltinOStreamOperators) {
+  EXPECT_EQ("42", fmt::format("{:d}", convertible<unsigned short>(42)));
+  EXPECT_EQ(L"42", fmt::format(L"{:d}", convertible<unsigned short>(42)));
+  EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo")));
+}
+
+struct explicitly_convertible_to_string_like {
+  template <typename String,
+            typename = typename std::enable_if<std::is_constructible<
+                String, const char*, std::size_t>::value>::type>
+  explicit operator String() const {
+    return String("foo", 3u);
+  }
+};
+
+TEST(FormatterTest, FormatExplicitlyConvertibleToStringLike) {
+  EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_like()));
+}
+
+std::ostream& operator<<(std::ostream& os,
+                         explicitly_convertible_to_string_like) {
+  return os << "bar";
+}
+
+TEST(FormatterTest, FormatExplicitlyConvertibleToStringLikeIgnoreInserter) {
+  EXPECT_EQ("foo", fmt::format("{}", explicitly_convertible_to_string_like()));
+}
diff --git a/test/posix-mock-test.cc b/test/posix-mock-test.cc
index 58e015a..fbd4a68 100644
--- a/test/posix-mock-test.cc
+++ b/test/posix-mock-test.cc
@@ -30,7 +30,6 @@
 
 using fmt::buffered_file;
 using fmt::error_code;
-using fmt::file;
 
 using testing::_;
 using testing::Return;
@@ -198,6 +197,9 @@
   f.print("{}", content);
 }
 
+#if FMT_USE_FCNTL
+using fmt::file;
+
 TEST(UtilTest, GetPageSize) {
 #ifdef _WIN32
   SYSTEM_INFO si = {};
@@ -429,6 +431,7 @@
   EXPECT_EQ(2, fileno_count);
   fileno_count = 0;
 }
+#endif  // FMT_USE_FCNTL
 
 struct TestMock {
   static TestMock* instance;
diff --git a/test/posix-test.cc b/test/posix-test.cc
index 133a922..24d211d 100644
--- a/test/posix-test.cc
+++ b/test/posix-test.cc
@@ -19,6 +19,9 @@
 
 using fmt::buffered_file;
 using fmt::error_code;
+
+#if FMT_USE_FCNTL
+
 using fmt::file;
 
 // Checks if the file is open by reading one character from it.
@@ -368,12 +371,6 @@
   EXPECT_EQ(read_fd, FMT_POSIX(fileno(read_end.fdopen("r").get())));
 }
 
-TEST(FileTest, FdopenError) {
-  file f;
-  EXPECT_SYSTEM_ERROR_NOASSERT(f.fdopen("r"), EBADF,
-                               "cannot associate stream with file descriptor");
-}
-
 #ifdef FMT_LOCALE
 TEST(LocaleTest, Strtod) {
   fmt::Locale locale;
@@ -382,3 +379,4 @@
   EXPECT_EQ(start + 3, ptr);
 }
 #endif
+#endif  // FMT_USE_FCNTL
\ No newline at end of file
diff --git a/test/printf-test.cc b/test/printf-test.cc
index aa25a59..2d68fab 100644
--- a/test/printf-test.cc
+++ b/test/printf-test.cc
@@ -16,6 +16,7 @@
 
 using fmt::format;
 using fmt::format_error;
+using fmt::internal::max_value;
 
 const unsigned BIG_NUM = INT_MAX + 1u;
 
@@ -295,13 +296,12 @@
   long long signed_value = 0;
   unsigned long long unsigned_value = 0;
   // Apply integer promotion to the argument.
-  using std::numeric_limits;
-  unsigned long long max = numeric_limits<U>::max();
+  unsigned long long max = max_value<U>();
   using fmt::internal::const_check;
-  if (const_check(max <= static_cast<unsigned>(numeric_limits<int>::max()))) {
+  if (const_check(max <= static_cast<unsigned>(max_value<int>()))) {
     signed_value = static_cast<int>(value);
     unsigned_value = static_cast<unsigned>(value);
-  } else if (const_check(max <= numeric_limits<unsigned>::max())) {
+  } else if (const_check(max <= max_value<unsigned>())) {
     signed_value = static_cast<unsigned>(value);
     unsigned_value = static_cast<unsigned>(value);
   }
@@ -332,25 +332,27 @@
 }
 
 template <typename T> void TestLength(const char* length_spec) {
-  T min = std::numeric_limits<T>::min(), max = std::numeric_limits<T>::max();
+  T min = std::numeric_limits<T>::min(), max = max_value<T>();
   TestLength<T>(length_spec, 42);
   TestLength<T>(length_spec, -42);
   TestLength<T>(length_spec, min);
   TestLength<T>(length_spec, max);
-  TestLength<T>(length_spec, static_cast<long long>(min) - 1);
-  unsigned long long long_long_max = std::numeric_limits<long long>::max();
+  long long long_long_min = std::numeric_limits<long long>::min();
+  if (static_cast<long long>(min) > long_long_min)
+    TestLength<T>(length_spec, static_cast<long long>(min) - 1);
+  unsigned long long long_long_max = max_value<long long>();
   if (static_cast<unsigned long long>(max) < long_long_max)
     TestLength<T>(length_spec, static_cast<long long>(max) + 1);
   TestLength<T>(length_spec, std::numeric_limits<short>::min());
-  TestLength<T>(length_spec, std::numeric_limits<unsigned short>::max());
+  TestLength<T>(length_spec, max_value<unsigned short>());
   TestLength<T>(length_spec, std::numeric_limits<int>::min());
-  TestLength<T>(length_spec, std::numeric_limits<int>::max());
+  TestLength<T>(length_spec, max_value<int>());
   TestLength<T>(length_spec, std::numeric_limits<unsigned>::min());
-  TestLength<T>(length_spec, std::numeric_limits<unsigned>::max());
+  TestLength<T>(length_spec, max_value<unsigned>());
   TestLength<T>(length_spec, std::numeric_limits<long long>::min());
-  TestLength<T>(length_spec, std::numeric_limits<long long>::max());
+  TestLength<T>(length_spec, max_value<long long>());
   TestLength<T>(length_spec, std::numeric_limits<unsigned long long>::min());
-  TestLength<T>(length_spec, std::numeric_limits<unsigned long long>::max());
+  TestLength<T>(length_spec, max_value<unsigned long long>());
 }
 
 TEST(PrintfTest, Length) {
@@ -366,7 +368,7 @@
   TestLength<intmax_t>("j");
   TestLength<std::size_t>("z");
   TestLength<std::ptrdiff_t>("t");
-  long double max = std::numeric_limits<long double>::max();
+  long double max = max_value<long double>();
   EXPECT_PRINTF(fmt::format("{:.6}", max), "%g", max);
   EXPECT_PRINTF(fmt::format("{:.6}", max), "%Lg", max);
 }
@@ -389,7 +391,7 @@
 TEST(PrintfTest, long_long) {
   // fmt::printf allows passing long long arguments to %d without length
   // specifiers.
-  long long max = std::numeric_limits<long long>::max();
+  long long max = max_value<long long>();
   EXPECT_PRINTF(fmt::format("{}", max), "%d", max);
 }
 
@@ -408,6 +410,7 @@
   EXPECT_PRINTF("392.65", "%G", 392.65);
   EXPECT_PRINTF("392", "%g", 392.0);
   EXPECT_PRINTF("392", "%G", 392.0);
+  EXPECT_PRINTF("4.56e-07", "%g", 0.000000456);
   safe_sprintf(buffer, "%a", -392.65);
   EXPECT_EQ(buffer, format("{:a}", -392.65));
   safe_sprintf(buffer, "%A", -392.65);
@@ -425,7 +428,7 @@
 
 TEST(PrintfTest, Char) {
   EXPECT_PRINTF("x", "%c", 'x');
-  int max = std::numeric_limits<int>::max();
+  int max = max_value<int>();
   EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
   // EXPECT_PRINTF("x", "%lc", L'x');
   EXPECT_PRINTF(L"x", L"%c", L'x');
@@ -474,7 +477,7 @@
 
 TEST(PrintfTest, Enum) { EXPECT_PRINTF("42", "%d", A); }
 
-#if FMT_USE_FILE_DESCRIPTORS
+#if FMT_USE_FCNTL
 TEST(PrintfTest, Examples) {
   const char* weekday = "Thursday";
   const char* month = "August";
@@ -561,8 +564,7 @@
 #endif
 }
 
-typedef fmt::printf_arg_formatter<fmt::internal::buffer_range<char>>
-    formatter_t;
+typedef fmt::printf_arg_formatter<fmt::buffer_range<char>> formatter_t;
 typedef fmt::basic_printf_context<formatter_t::iterator, char> context_t;
 
 // A custom printf argument formatter that doesn't print `-` for floating-point
@@ -587,8 +589,7 @@
       if (round(value * pow(10, specs()->precision)) == 0.0) value = 0;
   return formatter_t::operator()(value);
 }
-}
-;
+};
 
 typedef fmt::basic_format_args<context_t> format_args_t;
 
diff --git a/test/ranges-test.cc b/test/ranges-test.cc
index 7d4b6d2..265f9ac 100644
--- a/test/ranges-test.cc
+++ b/test/ranges-test.cc
@@ -9,13 +9,13 @@
 // All Rights Reserved
 // {fmt} support for ranges, containers and types tuple interface.
 
-/// Check if  'if constexpr' is supported.
+#include "fmt/ranges.h"
+#include "gtest.h"
+
+// Check if  'if constexpr' is supported.
 #if (__cplusplus > 201402L) || \
     (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910)
 
-#  include "fmt/ranges.h"
-#  include "gtest.h"
-
 #  include <array>
 #  include <map>
 #  include <string>
@@ -49,6 +49,25 @@
   EXPECT_EQ("(42, 1.5, \"this is tuple\", 'i')", fmt::format("{}", tu1));
 }
 
+TEST(RangesTest, JoinTuple) {
+  // Value tuple args
+  std::tuple<char, int, float> t1 = std::make_tuple('a', 1, 2.0f);
+  EXPECT_EQ("(a, 1, 2.0)", fmt::format("({})", fmt::join(t1, ", ")));
+
+  // Testing lvalue tuple args
+  int x = 4;
+  std::tuple<char, int&> t2{'b', x};
+  EXPECT_EQ("b + 4", fmt::format("{}", fmt::join(t2, " + ")));
+
+  // Empty tuple
+  std::tuple<> t3;
+  EXPECT_EQ("", fmt::format("{}", fmt::join(t3, "|")));
+
+  // Single element tuple
+  std::tuple<float> t4{4.0f};
+  EXPECT_EQ("4.0", fmt::format("{}", fmt::join(t4, "/")));
+}
+
 struct my_struct {
   int32_t i;
   std::string str;  // can throw
@@ -100,3 +119,16 @@
 
 #endif  // (__cplusplus > 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >
         // 201402L && _MSC_VER >= 1910)
+
+#ifdef FMT_USE_STRING_VIEW
+struct string_like {
+  const char* begin();
+  const char* end();
+  explicit operator fmt::string_view() const { return "foo"; }
+  explicit operator std::string_view() const { return "foo"; }
+};
+
+TEST(RangesTest, FormatStringLike) {
+  EXPECT_EQ("foo", fmt::format("{}", string_like()));
+}
+#endif  // FMT_USE_STRING_VIEW
diff --git a/test/scan.h b/test/scan.h
index ace84f7..50ac094 100644
--- a/test/scan.h
+++ b/test/scan.h
@@ -201,7 +201,8 @@
       scan_ctx_.advance_to(it);
       break;
     }
-    default:
+    case scan_type::none_type:
+    case scan_type::custom_type:
       assert(false);
     }
   }
diff --git a/test/std-format-test.cc b/test/std-format-test.cc
index 2fd60d4..a95f244 100644
--- a/test/std-format-test.cc
+++ b/test/std-format-test.cc
@@ -30,18 +30,14 @@
   // Error: '=' with charT and no integer presentation type
   EXPECT_THROW(string s5 = format("{:=6}", 'x'), std::format_error);
   string s6 = format("{:6d}", c);       // s6 == "   120"
-  string s7 = format("{:=+06d}", c);    // s7 == "+00120"
-  string s8 = format("{:0=#6x}", 0xa);  // s8 == "0x000a"
-  string s9 = format("{:6}", true);     // s9 == "true  "
+  string s7 = format("{:6}", true);     // s9 == "true  "
   EXPECT_EQ(s0, "    42");
   EXPECT_EQ(s1, "x     ");
   EXPECT_EQ(s2, "x*****");
   EXPECT_EQ(s3, "*****x");
   EXPECT_EQ(s4, "**x***");
   EXPECT_EQ(s6, "   120");
-  EXPECT_EQ(s7, "+00120");
-  EXPECT_EQ(s8, "0x000a");
-  EXPECT_EQ(s9, "true  ");
+  EXPECT_EQ(s7, "true  ");
 }
 
 TEST(StdFormatTest, Float) {
@@ -65,11 +61,11 @@
   string s0 = format("{}", 42);                       // s0 == "42"
   string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42);  // s1 == "101010 42 52 2a"
   string s2 = format("{0:#x} {0:#X}", 42);            // s2 == "0x2a 0X2A"
-  string s3 = format("{:n}", 1234);  // s3 == "1,234" (depends on the locale)
+  string s3 = format("{:n}", 1234);  // s3 == "1234" (depends on the locale)
   EXPECT_EQ(s0, "42");
   EXPECT_EQ(s1, "101010 42 52 2a");
   EXPECT_EQ(s2, "0x2a 0X2A");
-  EXPECT_EQ(s3, "1,234");
+  EXPECT_EQ(s3, "1234");
 }
 
 #include <format>
diff --git a/test/test-assert.h b/test/test-assert.h
index 034a4ce..c02dc0f 100644
--- a/test/test-assert.h
+++ b/test/test-assert.h
@@ -14,8 +14,13 @@
 class assertion_failure : public std::logic_error {
  public:
   explicit assertion_failure(const char* message) : std::logic_error(message) {}
+
+ private:
+  virtual void avoid_weak_vtable();
 };
 
+void assertion_failure::avoid_weak_vtable() {}
+
 #define FMT_ASSERT(condition, message) \
   if (!(condition)) throw assertion_failure(message);
 
diff --git a/test/util.cc b/test/util.cc
index 329f654..e75bd45 100644
--- a/test/util.cc
+++ b/test/util.cc
@@ -33,11 +33,17 @@
 const char* const FILE_CONTENT = "Don't panic!";
 
 fmt::buffered_file open_buffered_file(FILE** fp) {
+#if FMT_USE_FCNTL
   fmt::file read_end, write_end;
   fmt::file::pipe(read_end, write_end);
   write_end.write(FILE_CONTENT, std::strlen(FILE_CONTENT));
   write_end.close();
   fmt::buffered_file f = read_end.fdopen("r");
   if (fp) *fp = f.get();
+#else
+  fmt::buffered_file f("test-file", "w");
+  fputs(FILE_CONTENT, f.get());
+  if (fp) *fp = f.get();
+#endif
   return f;
 }