You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2026/04/21 04:09:11 UTC

(couchdb-jiffy) branch main updated (e0fa42e -> a81f9b7)

This is an automated email from the ASF dual-hosted git repository.

nickva pushed a change to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git


    from e0fa42e  Remove macos-13 CI workers
     new 3282a1a  Add a missing else in utf8 validation
     new 66a6d99  Fix -Wextra warnings
     new 229c05b  Add specific gcc and clang versions to CI
     new 7fdf276  Run prop tests in CI
     new 936e0f8  Fix OTP 27 prop tests
     new 0435946  Add C coverage checks
     new 5ba8495  Clean up unused code paths. Make OTP 20 the official minimum version
     new 0243863  Make most of decode functions static
     new 8f50162  Add extra number tests
     new 953a171  Add a deep pretty printing test
     new 0693a9a  Increase coverage for bad options
     new e1fa8a4  Add yielding tests
     new 7606c3f  Add coverage for random unicode edge cases
     new dbd7bcc  Plain ASCII scan ahead optimization in string decoding
     new b699032  Gate the restrict macro with a C version check
     new efaaae1  Use the restrict pointer qualifier when decoding numbers
     new b2c4ae2  Use assert for dec_number unreacheable state
     new e48ccda  Improve error handling in dec_number
     new 67571e1  Use quick lookahead parsing for numbers
     new f8a7c77  Plain ASCII scan ahead optimization in string encoding
     new 0e5c71c  Bump yielding reduction count to 4000
     new 32062a2  Whitespace scan ahead optimization in decoder
     new 13686ba  Add bench folder to gitignore
     new 7d37dd1  Ignore .cache
     new 09a75e9  Optimize dedupe_keys option
     new 6362a56  Replace chocolatey with erlef/setup-beam
     new b42c9a4  Switch to Ryu for float point encoding
     new f84f784  Faster map encoding with enif_make_map_from_arrays
     new e1f4d4d  Faster array creation
     new 3ab87a6  No more FLTO
     new 57682e4  Add extra tests and cleanup to increase coverage
     new a529615  Add some NIF lifecyle coverage
     new 38dffa9  Produce a unity build
     new 2649ba2  Prevent forward slashes from breaking scan-ahead optimization
     new 56117fa  Test with the Big List of Naughty Strings (BLNS)
     new a03cbf4  RFC 8259 Tests
     new 5681d76  Cleanup jiffy erl module
     new 34b00c1  Another l2b/b2l cleanup
     new 6b04965  Header cleanups
     new 34d4b1e  100% compliance with RFC 8259
     new c3dce8a  Skip setting buffer to 0 for strtod and strtol
     new ac3c936  Fix potential segfault in termstack expansion
     new 4c4cf90  Optimize termstack_restore allocation
     new f6f49d1  Use ffc.h library to parse numbers
     new c77f342  Use branch predictors for encoding
     new 0b795ab  Implement SIMD vectorization
     new c93691c  Improve coverage even more
     new b8a2299  Reduce bytes per reduction a bit
     new a81f9b7  Speed up UTF8 validation by about 25%

The 49 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .github/workflows/ci.yml                           |   87 +-
 .gitignore                                         |   10 +
 LICENSE                                            |  533 +++-
 Makefile                                           |   24 +-
 README.ffc.md                                      |   11 +
 README.ryu.md                                      |   28 +
 c_src/decoder.c                                    |  280 +-
 c_src/double-conversion/AUTHORS                    |   14 -
 c_src/double-conversion/COPYING                    |   26 -
 c_src/double-conversion/LICENSE                    |   26 -
 c_src/double-conversion/README.md                  |   54 -
 c_src/double-conversion/VERSION                    |    3 -
 c_src/double-conversion/bignum-dtoa.cc             |  641 ----
 c_src/double-conversion/bignum-dtoa.h              |   84 -
 c_src/double-conversion/bignum.cc                  |  767 -----
 c_src/double-conversion/bignum.h                   |  144 -
 c_src/double-conversion/cached-powers.cc           |  175 --
 c_src/double-conversion/cached-powers.h            |   64 -
 c_src/double-conversion/diy-fp.cc                  |   57 -
 c_src/double-conversion/diy-fp.h                   |  118 -
 c_src/double-conversion/double-conversion.cc       | 1144 -------
 c_src/double-conversion/double-conversion.h        |  576 ----
 c_src/double-conversion/fast-dtoa.cc               |  665 ----
 c_src/double-conversion/fast-dtoa.h                |   88 -
 c_src/double-conversion/fixed-dtoa.cc              |  405 ---
 c_src/double-conversion/fixed-dtoa.h               |   56 -
 c_src/double-conversion/ieee.h                     |  402 ---
 c_src/double-conversion/strtod.cc                  |  580 ----
 c_src/double-conversion/strtod.h                   |   45 -
 c_src/double-conversion/utils.h                    |  367 ---
 c_src/doubles.cc                                   |   33 -
 c_src/encoder.c                                    |  237 +-
 c_src/ffc.h                                        | 3242 ++++++++++++++++++++
 c_src/jiffy.c                                      |   33 +-
 c_src/jiffy.h                                      |   62 +-
 c_src/jiffy_simd.h                                 |   64 +
 c_src/{utf8.c => jiffy_utf8.h}                     |  178 +-
 c_src/objects.c                                    |  251 ++
 c_src/objects.cc                                   |   70 -
 c_src/ryu/LICENSE-Apache2                          |  201 ++
 c_src/ryu/LICENSE-Boost                            |   23 +
 c_src/ryu/common.h                                 |  116 +
 c_src/ryu/d2s.c                                    |  391 +++
 c_src/ryu/d2s_full_table.h                         |  367 +++
 c_src/ryu/d2s_intrinsics.h                         |  359 +++
 c_src/ryu/digit_table.h                            |   35 +
 c_src/ryu/ryu.h                                    |   37 +
 c_src/ryu/to_chars.h                               |  342 +++
 c_src/termstack.c                                  |   87 -
 c_src/termstack.h                                  |   34 -
 c_src/util.c                                       |   60 +-
 rebar.config                                       |   18 +-
 rebar.config.script                                |   39 +-
 src/jiffy.erl                                      |   19 +-
 test/cases/blns.txt                                |  742 +++++
 .../i_number_double_huge_neg_exp.json              |    1 +
 test/cases/json_test_suite/i_number_huge_exp.json  |    1 +
 .../json_test_suite/i_number_neg_int_huge_exp.json |    1 +
 .../i_number_pos_double_huge_exp.json              |    1 +
 .../i_number_real_neg_overflow.json                |    1 +
 .../i_number_real_pos_overflow.json                |    1 +
 .../json_test_suite/i_number_real_underflow.json   |    1 +
 .../json_test_suite/i_number_too_big_neg_int.json  |    1 +
 .../json_test_suite/i_number_too_big_pos_int.json  |    1 +
 .../i_number_very_big_negative_int.json            |    1 +
 .../i_object_key_lone_2nd_surrogate.json           |    1 +
 .../i_string_1st_surrogate_but_2nd_missing.json    |    1 +
 .../i_string_1st_valid_surrogate_2nd_invalid.json  |    1 +
 .../i_string_UTF-16LE_with_BOM.json                |  Bin 0 -> 12 bytes
 .../i_string_UTF-8_invalid_sequence.json           |    1 +
 .../i_string_UTF8_surrogate_U+D800.json            |    1 +
 ...ring_incomplete_surrogate_and_escape_valid.json |    1 +
 .../i_string_incomplete_surrogate_pair.json        |    1 +
 ..._string_incomplete_surrogates_escape_valid.json |    1 +
 .../i_string_invalid_lonely_surrogate.json         |    1 +
 .../i_string_invalid_surrogate.json                |    1 +
 .../json_test_suite/i_string_invalid_utf-8.json    |    1 +
 .../i_string_inverted_surrogates_U+1D11E.json      |    1 +
 .../json_test_suite/i_string_iso_latin_1.json      |    1 +
 .../i_string_lone_second_surrogate.json            |    1 +
 .../i_string_lone_utf8_continuation_byte.json      |    1 +
 .../i_string_not_in_unicode_range.json             |    1 +
 .../i_string_overlong_sequence_2_bytes.json        |    1 +
 .../i_string_overlong_sequence_6_bytes.json        |    1 +
 .../i_string_overlong_sequence_6_bytes_null.json   |    1 +
 .../json_test_suite/i_string_truncated-utf-8.json  |    1 +
 .../json_test_suite/i_string_utf16BE_no_BOM.json   |  Bin 0 -> 10 bytes
 .../json_test_suite/i_string_utf16LE_no_BOM.json   |  Bin 0 -> 10 bytes
 .../i_structure_500_nested_arrays.json             |    1 +
 .../i_structure_UTF-8_BOM_empty_object.json        |    1 +
 .../n_array_1_true_without_comma.json              |    1 +
 .../json_test_suite/n_array_a_invalid_utf8.json    |    1 +
 .../n_array_colon_instead_of_comma.json            |    1 +
 .../json_test_suite/n_array_comma_after_close.json |    1 +
 .../json_test_suite/n_array_comma_and_number.json  |    1 +
 .../json_test_suite/n_array_double_comma.json      |    1 +
 .../n_array_double_extra_comma.json                |    1 +
 .../cases/json_test_suite/n_array_extra_close.json |    1 +
 .../cases/json_test_suite/n_array_extra_comma.json |    1 +
 test/cases/json_test_suite/n_array_incomplete.json |    1 +
 .../n_array_incomplete_invalid_value.json          |    1 +
 .../n_array_inner_array_no_comma.json              |    1 +
 .../json_test_suite/n_array_invalid_utf8.json      |    1 +
 .../n_array_items_separated_by_semicolon.json      |    1 +
 test/cases/json_test_suite/n_array_just_comma.json |    1 +
 test/cases/json_test_suite/n_array_just_minus.json |    1 +
 .../json_test_suite/n_array_missing_value.json     |    1 +
 .../json_test_suite/n_array_newlines_unclosed.json |    3 +
 .../json_test_suite/n_array_number_and_comma.json  |    1 +
 .../n_array_number_and_several_commas.json         |    1 +
 .../n_array_spaces_vertical_tab_formfeed.json      |    1 +
 .../cases/json_test_suite/n_array_star_inside.json |    1 +
 test/cases/json_test_suite/n_array_unclosed.json   |    1 +
 .../n_array_unclosed_trailing_comma.json           |    1 +
 .../n_array_unclosed_with_new_lines.json           |    3 +
 .../n_array_unclosed_with_object_inside.json       |    1 +
 test/cases/json_test_suite/n_incomplete_false.json |    1 +
 test/cases/json_test_suite/n_incomplete_null.json  |    1 +
 test/cases/json_test_suite/n_incomplete_true.json  |    1 +
 .../n_multidigit_number_then_00.json               |  Bin 0 -> 4 bytes
 test/cases/json_test_suite/n_number_++.json        |    1 +
 test/cases/json_test_suite/n_number_+1.json        |    1 +
 test/cases/json_test_suite/n_number_+Inf.json      |    1 +
 test/cases/json_test_suite/n_number_-01.json       |    1 +
 test/cases/json_test_suite/n_number_-1.0..json     |    1 +
 test/cases/json_test_suite/n_number_-2..json       |    1 +
 test/cases/json_test_suite/n_number_-NaN.json      |    1 +
 test/cases/json_test_suite/n_number_.-1.json       |    1 +
 test/cases/json_test_suite/n_number_.2e-3.json     |    1 +
 test/cases/json_test_suite/n_number_0.1.2.json     |    1 +
 test/cases/json_test_suite/n_number_0.3e+.json     |    1 +
 test/cases/json_test_suite/n_number_0.3e.json      |    1 +
 test/cases/json_test_suite/n_number_0.e1.json      |    1 +
 .../json_test_suite/n_number_0_capital_E+.json     |    1 +
 .../json_test_suite/n_number_0_capital_E.json      |    1 +
 test/cases/json_test_suite/n_number_0e+.json       |    1 +
 test/cases/json_test_suite/n_number_0e.json        |    1 +
 test/cases/json_test_suite/n_number_1.0e+.json     |    1 +
 test/cases/json_test_suite/n_number_1.0e-.json     |    1 +
 test/cases/json_test_suite/n_number_1.0e.json      |    1 +
 test/cases/json_test_suite/n_number_1_000.json     |    1 +
 test/cases/json_test_suite/n_number_1eE2.json      |    1 +
 test/cases/json_test_suite/n_number_2.e+3.json     |    1 +
 test/cases/json_test_suite/n_number_2.e-3.json     |    1 +
 test/cases/json_test_suite/n_number_2.e3.json      |    1 +
 test/cases/json_test_suite/n_number_9.e+.json      |    1 +
 test/cases/json_test_suite/n_number_Inf.json       |    1 +
 test/cases/json_test_suite/n_number_NaN.json       |    1 +
 .../n_number_U+FF11_fullwidth_digit_one.json       |    1 +
 .../cases/json_test_suite/n_number_expression.json |    1 +
 .../json_test_suite/n_number_hex_1_digit.json      |    1 +
 .../json_test_suite/n_number_hex_2_digits.json     |    1 +
 test/cases/json_test_suite/n_number_infinity.json  |    1 +
 test/cases/json_test_suite/n_number_invalid+-.json |    1 +
 .../n_number_invalid-negative-real.json            |    1 +
 .../n_number_invalid-utf-8-in-bigger-int.json      |    1 +
 .../n_number_invalid-utf-8-in-exponent.json        |    1 +
 .../n_number_invalid-utf-8-in-int.json             |    1 +
 .../json_test_suite/n_number_minus_infinity.json   |    1 +
 .../n_number_minus_sign_with_trailing_garbage.json |    1 +
 .../json_test_suite/n_number_minus_space_1.json    |    1 +
 .../n_number_neg_int_starting_with_zero.json       |    1 +
 .../n_number_neg_real_without_int_part.json        |    1 +
 .../n_number_neg_with_garbage_at_end.json          |    1 +
 .../n_number_real_garbage_after_e.json             |    1 +
 .../n_number_real_with_invalid_utf8_after_e.json   |    1 +
 .../n_number_real_without_fractional_part.json     |    1 +
 .../n_number_starting_with_dot.json                |    1 +
 .../cases/json_test_suite/n_number_with_alpha.json |    1 +
 .../json_test_suite/n_number_with_alpha_char.json  |    1 +
 .../n_number_with_leading_zero.json                |    1 +
 test/cases/json_test_suite/n_object_bad_value.json |    1 +
 .../json_test_suite/n_object_bracket_key.json      |    1 +
 .../n_object_comma_instead_of_colon.json           |    1 +
 .../json_test_suite/n_object_double_colon.json     |    1 +
 test/cases/json_test_suite/n_object_emoji.json     |    1 +
 .../json_test_suite/n_object_garbage_at_end.json   |    1 +
 .../n_object_key_with_single_quotes.json           |    1 +
 ...ontinuation_byte_in_key_and_trailing_comma.json |    1 +
 .../json_test_suite/n_object_missing_colon.json    |    1 +
 .../json_test_suite/n_object_missing_key.json      |    1 +
 .../n_object_missing_semicolon.json                |    1 +
 .../json_test_suite/n_object_missing_value.json    |    1 +
 test/cases/json_test_suite/n_object_no-colon.json  |    1 +
 .../json_test_suite/n_object_non_string_key.json   |    1 +
 ...ect_non_string_key_but_huge_number_instead.json |    1 +
 .../n_object_repeated_null_null.json               |    1 +
 .../n_object_several_trailing_commas.json          |    1 +
 .../json_test_suite/n_object_single_quote.json     |    1 +
 .../json_test_suite/n_object_trailing_comma.json   |    1 +
 .../json_test_suite/n_object_trailing_comment.json |    1 +
 .../n_object_trailing_comment_open.json            |    1 +
 .../n_object_trailing_comment_slash_open.json      |    1 +
 ...ect_trailing_comment_slash_open_incomplete.json |    1 +
 .../n_object_two_commas_in_a_row.json              |    1 +
 .../json_test_suite/n_object_unquoted_key.json     |    1 +
 .../n_object_unterminated-value.json               |    1 +
 .../n_object_with_single_string.json               |    1 +
 .../n_object_with_trailing_garbage.json            |    1 +
 test/cases/json_test_suite/n_single_space.json     |    1 +
 .../n_string_1_surrogate_then_escape.json          |    1 +
 .../n_string_1_surrogate_then_escape_u.json        |    1 +
 .../n_string_1_surrogate_then_escape_u1.json       |    1 +
 .../n_string_1_surrogate_then_escape_u1x.json      |    1 +
 .../n_string_accentuated_char_no_quotes.json       |    1 +
 .../json_test_suite/n_string_backslash_00.json     |  Bin 0 -> 6 bytes
 test/cases/json_test_suite/n_string_escape_x.json  |    1 +
 .../n_string_escaped_backslash_bad.json            |    1 +
 .../n_string_escaped_ctrl_char_tab.json            |    1 +
 .../json_test_suite/n_string_escaped_emoji.json    |    1 +
 .../n_string_incomplete_escape.json                |    1 +
 .../n_string_incomplete_escaped_character.json     |    1 +
 .../n_string_incomplete_surrogate.json             |    1 +
 ...string_incomplete_surrogate_escape_invalid.json |    1 +
 .../n_string_invalid-utf-8-in-escape.json          |    1 +
 .../n_string_invalid_backslash_esc.json            |    1 +
 .../n_string_invalid_unicode_escape.json           |    1 +
 .../n_string_invalid_utf8_after_escape.json        |    1 +
 .../n_string_leading_uescaped_thinspace.json       |    1 +
 .../n_string_no_quotes_with_bad_escape.json        |    1 +
 .../n_string_single_doublequote.json               |    1 +
 .../json_test_suite/n_string_single_quote.json     |    1 +
 .../n_string_single_string_no_double_quotes.json   |    1 +
 .../n_string_start_escape_unclosed.json            |    1 +
 .../n_string_unescaped_ctrl_char.json              |  Bin 0 -> 7 bytes
 .../n_string_unescaped_newline.json                |    2 +
 .../json_test_suite/n_string_unescaped_tab.json    |    1 +
 .../json_test_suite/n_string_unicode_CapitalU.json |    1 +
 .../n_string_with_trailing_garbage.json            |    1 +
 .../n_structure_100000_opening_arrays.json         |    1 +
 .../n_structure_U+2060_word_joined.json            |    1 +
 .../n_structure_UTF8_BOM_no_data.json              |    1 +
 .../n_structure_angle_bracket_..json               |    1 +
 .../n_structure_angle_bracket_null.json            |    1 +
 .../n_structure_array_trailing_garbage.json        |    1 +
 .../n_structure_array_with_extra_array_close.json  |    1 +
 .../n_structure_array_with_unclosed_string.json    |    1 +
 .../n_structure_ascii-unicode-identifier.json      |    1 +
 .../n_structure_capitalized_True.json              |    1 +
 .../n_structure_close_unopened_array.json          |    1 +
 ...n_structure_comma_instead_of_closing_brace.json |    1 +
 .../json_test_suite/n_structure_double_array.json  |    1 +
 .../json_test_suite/n_structure_end_array.json     |    1 +
 .../n_structure_incomplete_UTF8_BOM.json           |    1 +
 .../n_structure_lone-invalid-utf-8.json            |    1 +
 .../n_structure_lone-open-bracket.json             |    1 +
 .../cases/json_test_suite/n_structure_no_data.json |    0
 .../n_structure_null-byte-outside-string.json      |  Bin 0 -> 3 bytes
 .../n_structure_number_with_trailing_garbage.json  |    1 +
 ...tructure_object_followed_by_closing_object.json |    1 +
 .../n_structure_object_unclosed_no_value.json      |    1 +
 .../n_structure_object_with_comment.json           |    1 +
 .../n_structure_object_with_trailing_garbage.json  |    1 +
 .../n_structure_open_array_apostrophe.json         |    1 +
 .../n_structure_open_array_comma.json              |    1 +
 .../n_structure_open_array_object.json             |    1 +
 .../n_structure_open_array_open_object.json        |    1 +
 .../n_structure_open_array_open_string.json        |    1 +
 .../n_structure_open_array_string.json             |    1 +
 .../json_test_suite/n_structure_open_object.json   |    1 +
 .../n_structure_open_object_close_array.json       |    1 +
 .../n_structure_open_object_comma.json             |    1 +
 .../n_structure_open_object_open_array.json        |    1 +
 .../n_structure_open_object_open_string.json       |    1 +
 ...ucture_open_object_string_with_apostrophes.json |    1 +
 .../json_test_suite/n_structure_open_open.json     |    1 +
 .../json_test_suite/n_structure_single_eacute.json |    1 +
 .../json_test_suite/n_structure_single_star.json   |    1 +
 .../json_test_suite/n_structure_trailing_#.json    |    1 +
 .../n_structure_uescaped_LF_before_string.json     |    1 +
 .../n_structure_unclosed_array.json                |    1 +
 .../n_structure_unclosed_array_partial_null.json   |    1 +
 ..._structure_unclosed_array_unfinished_false.json |    1 +
 ...n_structure_unclosed_array_unfinished_true.json |    1 +
 .../n_structure_unclosed_object.json               |    1 +
 .../n_structure_unicode-identifier.json            |    1 +
 .../n_structure_whitespace_U+2060_word_joiner.json |    1 +
 .../n_structure_whitespace_formfeed.json           |    1 +
 .../json_test_suite/y_array_arraysWithSpaces.json  |    1 +
 .../json_test_suite/y_array_empty-string.json      |    1 +
 .../y_array_empty.json}                            |    0
 .../y_array_ending_with_newline.json               |    1 +
 test/cases/json_test_suite/y_array_false.json      |    1 +
 .../json_test_suite/y_array_heterogeneous.json     |    1 +
 test/cases/json_test_suite/y_array_null.json       |    1 +
 .../y_array_with_1_and_newline.json                |    2 +
 .../y_array_with_leading_space.json                |    1 +
 .../json_test_suite/y_array_with_several_null.json |    1 +
 .../y_array_with_trailing_space.json               |    1 +
 test/cases/json_test_suite/y_number.json           |    1 +
 test/cases/json_test_suite/y_number_0e+1.json      |    1 +
 test/cases/json_test_suite/y_number_0e1.json       |    1 +
 .../json_test_suite/y_number_after_space.json      |    1 +
 .../y_number_double_close_to_zero.json             |    1 +
 .../json_test_suite/y_number_int_with_exp.json     |    1 +
 .../cases/json_test_suite/y_number_minus_zero.json |    1 +
 .../json_test_suite/y_number_negative_int.json     |    1 +
 .../json_test_suite/y_number_negative_one.json     |    1 +
 .../json_test_suite/y_number_negative_zero.json    |    1 +
 .../json_test_suite/y_number_real_capital_e.json   |    1 +
 .../y_number_real_capital_e_neg_exp.json           |    1 +
 .../y_number_real_capital_e_pos_exp.json           |    1 +
 .../json_test_suite/y_number_real_exponent.json    |    1 +
 .../y_number_real_fraction_exponent.json           |    1 +
 .../json_test_suite/y_number_real_neg_exp.json     |    1 +
 .../y_number_real_pos_exponent.json                |    1 +
 .../cases/json_test_suite/y_number_simple_int.json |    1 +
 .../json_test_suite/y_number_simple_real.json      |    1 +
 test/cases/json_test_suite/y_object.json           |    1 +
 test/cases/json_test_suite/y_object_basic.json     |    1 +
 .../json_test_suite/y_object_duplicated_key.json   |    1 +
 .../y_object_duplicated_key_and_value.json         |    1 +
 test/cases/json_test_suite/y_object_empty.json     |    1 +
 test/cases/json_test_suite/y_object_empty_key.json |    1 +
 .../y_object_escaped_null_in_key.json              |    1 +
 .../json_test_suite/y_object_extreme_numbers.json  |    1 +
 .../json_test_suite/y_object_long_strings.json     |    1 +
 test/cases/json_test_suite/y_object_simple.json    |    1 +
 .../json_test_suite/y_object_string_unicode.json   |    1 +
 .../json_test_suite/y_object_with_newlines.json    |    3 +
 .../y_string_1_2_3_bytes_UTF-8_sequences.json      |    1 +
 .../y_string_accepted_surrogate_pair.json          |    1 +
 .../y_string_accepted_surrogate_pairs.json         |    1 +
 .../json_test_suite/y_string_allowed_escapes.json  |    1 +
 .../y_string_backslash_and_u_escaped_zero.json     |    1 +
 .../y_string_backslash_doublequotes.json           |    1 +
 test/cases/json_test_suite/y_string_comments.json  |    1 +
 .../json_test_suite/y_string_double_escape_a.json  |    1 +
 .../json_test_suite/y_string_double_escape_n.json  |    1 +
 .../y_string_escaped_control_character.json        |    1 +
 .../y_string_escaped_noncharacter.json             |    1 +
 test/cases/json_test_suite/y_string_in_array.json  |    1 +
 .../y_string_in_array_with_leading_space.json      |    1 +
 .../y_string_last_surrogates_1_and_2.json          |    1 +
 .../json_test_suite/y_string_nbsp_uescaped.json    |    1 +
 .../y_string_nonCharacterInUTF-8_U+10FFFF.json     |    1 +
 .../y_string_nonCharacterInUTF-8_U+FFFF.json       |    1 +
 .../json_test_suite/y_string_null_escape.json      |    1 +
 .../json_test_suite/y_string_one-byte-utf-8.json   |    1 +
 test/cases/json_test_suite/y_string_pi.json        |    1 +
 .../y_string_reservedCharacterInUTF-8_U+1BFFF.json |    1 +
 .../json_test_suite/y_string_simple_ascii.json     |    1 +
 test/cases/json_test_suite/y_string_space.json     |    1 +
 ...g_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json |    1 +
 .../json_test_suite/y_string_three-byte-utf-8.json |    1 +
 .../json_test_suite/y_string_two-byte-utf-8.json   |    1 +
 .../json_test_suite/y_string_u+2028_line_sep.json  |    1 +
 .../json_test_suite/y_string_u+2029_par_sep.json   |    1 +
 test/cases/json_test_suite/y_string_uEscape.json   |    1 +
 .../json_test_suite/y_string_uescaped_newline.json |    1 +
 .../y_string_unescaped_char_delete.json            |    1 +
 test/cases/json_test_suite/y_string_unicode.json   |    1 +
 .../y_string_unicodeEscapedBackslash.json          |    1 +
 test/cases/json_test_suite/y_string_unicode_2.json |    1 +
 .../y_string_unicode_U+10FFFE_nonchar.json         |    1 +
 .../y_string_unicode_U+1FFFE_nonchar.json          |    1 +
 .../y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json  |    1 +
 .../y_string_unicode_U+2064_invisible_plus.json    |    1 +
 .../y_string_unicode_U+FDD0_nonchar.json           |    1 +
 .../y_string_unicode_U+FFFE_nonchar.json           |    1 +
 .../y_string_unicode_escaped_double_quote.json     |    1 +
 test/cases/json_test_suite/y_string_utf8.json      |    1 +
 .../y_string_with_del_character.json               |    1 +
 .../json_test_suite/y_structure_lonely_false.json  |    1 +
 .../json_test_suite/y_structure_lonely_int.json    |    1 +
 .../y_structure_lonely_negative_real.json          |    1 +
 .../json_test_suite/y_structure_lonely_null.json   |    1 +
 .../json_test_suite/y_structure_lonely_string.json |    1 +
 .../json_test_suite/y_structure_lonely_true.json   |    1 +
 .../json_test_suite/y_structure_string_empty.json  |    1 +
 .../y_structure_trailing_newline.json              |    1 +
 .../json_test_suite/y_structure_true_in_array.json |    1 +
 .../y_structure_whitespace_array.json              |    1 +
 test/install_eqc_mini.sh                           |   40 +
 test/jiffy_02_literal_tests.erl                    |   10 +
 test/jiffy_03_number_tests.erl                     |  100 +-
 test/jiffy_04_string_tests.erl                     |   39 +-
 test/jiffy_05_array_tests.erl                      |    8 +
 test/jiffy_06_object_tests.erl                     |   62 +
 test/jiffy_11_property_tests.erl                   |   10 +-
 test/jiffy_12_error_tests.erl                      |   21 +
 test/jiffy_16_dedupe_keys_tests.erl                |   63 +
 test/jiffy_19_yielding_tests.erl                   |  105 +
 test/jiffy_20_lifecycle_tests.erl                  |   29 +
 test/jiffy_21_naughty_strings_tests.erl            |   25 +
 test/jiffy_22_json_test_suite_tests.erl            |   86 +
 test/jiffy_util.hrl                                |   13 +-
 387 files changed, 8301 insertions(+), 7141 deletions(-)
 create mode 100644 README.ffc.md
 create mode 100644 README.ryu.md
 delete mode 100644 c_src/double-conversion/AUTHORS
 delete mode 100644 c_src/double-conversion/COPYING
 delete mode 100644 c_src/double-conversion/LICENSE
 delete mode 100644 c_src/double-conversion/README.md
 delete mode 100644 c_src/double-conversion/VERSION
 delete mode 100644 c_src/double-conversion/bignum-dtoa.cc
 delete mode 100644 c_src/double-conversion/bignum-dtoa.h
 delete mode 100644 c_src/double-conversion/bignum.cc
 delete mode 100644 c_src/double-conversion/bignum.h
 delete mode 100644 c_src/double-conversion/cached-powers.cc
 delete mode 100644 c_src/double-conversion/cached-powers.h
 delete mode 100644 c_src/double-conversion/diy-fp.cc
 delete mode 100644 c_src/double-conversion/diy-fp.h
 delete mode 100644 c_src/double-conversion/double-conversion.cc
 delete mode 100644 c_src/double-conversion/double-conversion.h
 delete mode 100644 c_src/double-conversion/fast-dtoa.cc
 delete mode 100644 c_src/double-conversion/fast-dtoa.h
 delete mode 100644 c_src/double-conversion/fixed-dtoa.cc
 delete mode 100644 c_src/double-conversion/fixed-dtoa.h
 delete mode 100644 c_src/double-conversion/ieee.h
 delete mode 100644 c_src/double-conversion/strtod.cc
 delete mode 100644 c_src/double-conversion/strtod.h
 delete mode 100644 c_src/double-conversion/utils.h
 delete mode 100644 c_src/doubles.cc
 create mode 100644 c_src/ffc.h
 create mode 100644 c_src/jiffy_simd.h
 rename c_src/{utf8.c => jiffy_utf8.h} (60%)
 create mode 100644 c_src/objects.c
 delete mode 100644 c_src/objects.cc
 create mode 100644 c_src/ryu/LICENSE-Apache2
 create mode 100644 c_src/ryu/LICENSE-Boost
 create mode 100644 c_src/ryu/common.h
 create mode 100644 c_src/ryu/d2s.c
 create mode 100644 c_src/ryu/d2s_full_table.h
 create mode 100644 c_src/ryu/d2s_intrinsics.h
 create mode 100644 c_src/ryu/digit_table.h
 create mode 100644 c_src/ryu/ryu.h
 create mode 100644 c_src/ryu/to_chars.h
 delete mode 100644 c_src/termstack.c
 delete mode 100644 c_src/termstack.h
 create mode 100644 test/cases/blns.txt
 create mode 100644 test/cases/json_test_suite/i_number_double_huge_neg_exp.json
 create mode 100644 test/cases/json_test_suite/i_number_huge_exp.json
 create mode 100644 test/cases/json_test_suite/i_number_neg_int_huge_exp.json
 create mode 100644 test/cases/json_test_suite/i_number_pos_double_huge_exp.json
 create mode 100644 test/cases/json_test_suite/i_number_real_neg_overflow.json
 create mode 100644 test/cases/json_test_suite/i_number_real_pos_overflow.json
 create mode 100644 test/cases/json_test_suite/i_number_real_underflow.json
 create mode 100644 test/cases/json_test_suite/i_number_too_big_neg_int.json
 create mode 100644 test/cases/json_test_suite/i_number_too_big_pos_int.json
 create mode 100644 test/cases/json_test_suite/i_number_very_big_negative_int.json
 create mode 100644 test/cases/json_test_suite/i_object_key_lone_2nd_surrogate.json
 create mode 100644 test/cases/json_test_suite/i_string_1st_surrogate_but_2nd_missing.json
 create mode 100644 test/cases/json_test_suite/i_string_1st_valid_surrogate_2nd_invalid.json
 create mode 100644 test/cases/json_test_suite/i_string_UTF-16LE_with_BOM.json
 create mode 100644 test/cases/json_test_suite/i_string_UTF-8_invalid_sequence.json
 create mode 100644 test/cases/json_test_suite/i_string_UTF8_surrogate_U+D800.json
 create mode 100644 test/cases/json_test_suite/i_string_incomplete_surrogate_and_escape_valid.json
 create mode 100644 test/cases/json_test_suite/i_string_incomplete_surrogate_pair.json
 create mode 100644 test/cases/json_test_suite/i_string_incomplete_surrogates_escape_valid.json
 create mode 100644 test/cases/json_test_suite/i_string_invalid_lonely_surrogate.json
 create mode 100644 test/cases/json_test_suite/i_string_invalid_surrogate.json
 create mode 100644 test/cases/json_test_suite/i_string_invalid_utf-8.json
 create mode 100644 test/cases/json_test_suite/i_string_inverted_surrogates_U+1D11E.json
 create mode 100644 test/cases/json_test_suite/i_string_iso_latin_1.json
 create mode 100644 test/cases/json_test_suite/i_string_lone_second_surrogate.json
 create mode 100644 test/cases/json_test_suite/i_string_lone_utf8_continuation_byte.json
 create mode 100644 test/cases/json_test_suite/i_string_not_in_unicode_range.json
 create mode 100644 test/cases/json_test_suite/i_string_overlong_sequence_2_bytes.json
 create mode 100644 test/cases/json_test_suite/i_string_overlong_sequence_6_bytes.json
 create mode 100644 test/cases/json_test_suite/i_string_overlong_sequence_6_bytes_null.json
 create mode 100644 test/cases/json_test_suite/i_string_truncated-utf-8.json
 create mode 100644 test/cases/json_test_suite/i_string_utf16BE_no_BOM.json
 create mode 100644 test/cases/json_test_suite/i_string_utf16LE_no_BOM.json
 create mode 100644 test/cases/json_test_suite/i_structure_500_nested_arrays.json
 create mode 100644 test/cases/json_test_suite/i_structure_UTF-8_BOM_empty_object.json
 create mode 100644 test/cases/json_test_suite/n_array_1_true_without_comma.json
 create mode 100644 test/cases/json_test_suite/n_array_a_invalid_utf8.json
 create mode 100644 test/cases/json_test_suite/n_array_colon_instead_of_comma.json
 create mode 100644 test/cases/json_test_suite/n_array_comma_after_close.json
 create mode 100644 test/cases/json_test_suite/n_array_comma_and_number.json
 create mode 100644 test/cases/json_test_suite/n_array_double_comma.json
 create mode 100644 test/cases/json_test_suite/n_array_double_extra_comma.json
 create mode 100644 test/cases/json_test_suite/n_array_extra_close.json
 create mode 100644 test/cases/json_test_suite/n_array_extra_comma.json
 create mode 100644 test/cases/json_test_suite/n_array_incomplete.json
 create mode 100644 test/cases/json_test_suite/n_array_incomplete_invalid_value.json
 create mode 100644 test/cases/json_test_suite/n_array_inner_array_no_comma.json
 create mode 100644 test/cases/json_test_suite/n_array_invalid_utf8.json
 create mode 100644 test/cases/json_test_suite/n_array_items_separated_by_semicolon.json
 create mode 100644 test/cases/json_test_suite/n_array_just_comma.json
 create mode 100644 test/cases/json_test_suite/n_array_just_minus.json
 create mode 100644 test/cases/json_test_suite/n_array_missing_value.json
 create mode 100644 test/cases/json_test_suite/n_array_newlines_unclosed.json
 create mode 100644 test/cases/json_test_suite/n_array_number_and_comma.json
 create mode 100644 test/cases/json_test_suite/n_array_number_and_several_commas.json
 create mode 100644 test/cases/json_test_suite/n_array_spaces_vertical_tab_formfeed.json
 create mode 100644 test/cases/json_test_suite/n_array_star_inside.json
 create mode 100644 test/cases/json_test_suite/n_array_unclosed.json
 create mode 100644 test/cases/json_test_suite/n_array_unclosed_trailing_comma.json
 create mode 100644 test/cases/json_test_suite/n_array_unclosed_with_new_lines.json
 create mode 100644 test/cases/json_test_suite/n_array_unclosed_with_object_inside.json
 create mode 100644 test/cases/json_test_suite/n_incomplete_false.json
 create mode 100644 test/cases/json_test_suite/n_incomplete_null.json
 create mode 100644 test/cases/json_test_suite/n_incomplete_true.json
 create mode 100644 test/cases/json_test_suite/n_multidigit_number_then_00.json
 create mode 100644 test/cases/json_test_suite/n_number_++.json
 create mode 100644 test/cases/json_test_suite/n_number_+1.json
 create mode 100644 test/cases/json_test_suite/n_number_+Inf.json
 create mode 100644 test/cases/json_test_suite/n_number_-01.json
 create mode 100644 test/cases/json_test_suite/n_number_-1.0..json
 create mode 100644 test/cases/json_test_suite/n_number_-2..json
 create mode 100644 test/cases/json_test_suite/n_number_-NaN.json
 create mode 100644 test/cases/json_test_suite/n_number_.-1.json
 create mode 100644 test/cases/json_test_suite/n_number_.2e-3.json
 create mode 100644 test/cases/json_test_suite/n_number_0.1.2.json
 create mode 100644 test/cases/json_test_suite/n_number_0.3e+.json
 create mode 100644 test/cases/json_test_suite/n_number_0.3e.json
 create mode 100644 test/cases/json_test_suite/n_number_0.e1.json
 create mode 100644 test/cases/json_test_suite/n_number_0_capital_E+.json
 create mode 100644 test/cases/json_test_suite/n_number_0_capital_E.json
 create mode 100644 test/cases/json_test_suite/n_number_0e+.json
 create mode 100644 test/cases/json_test_suite/n_number_0e.json
 create mode 100644 test/cases/json_test_suite/n_number_1.0e+.json
 create mode 100644 test/cases/json_test_suite/n_number_1.0e-.json
 create mode 100644 test/cases/json_test_suite/n_number_1.0e.json
 create mode 100644 test/cases/json_test_suite/n_number_1_000.json
 create mode 100644 test/cases/json_test_suite/n_number_1eE2.json
 create mode 100644 test/cases/json_test_suite/n_number_2.e+3.json
 create mode 100644 test/cases/json_test_suite/n_number_2.e-3.json
 create mode 100644 test/cases/json_test_suite/n_number_2.e3.json
 create mode 100644 test/cases/json_test_suite/n_number_9.e+.json
 create mode 100644 test/cases/json_test_suite/n_number_Inf.json
 create mode 100644 test/cases/json_test_suite/n_number_NaN.json
 create mode 100644 test/cases/json_test_suite/n_number_U+FF11_fullwidth_digit_one.json
 create mode 100644 test/cases/json_test_suite/n_number_expression.json
 create mode 100644 test/cases/json_test_suite/n_number_hex_1_digit.json
 create mode 100644 test/cases/json_test_suite/n_number_hex_2_digits.json
 create mode 100644 test/cases/json_test_suite/n_number_infinity.json
 create mode 100644 test/cases/json_test_suite/n_number_invalid+-.json
 create mode 100644 test/cases/json_test_suite/n_number_invalid-negative-real.json
 create mode 100644 test/cases/json_test_suite/n_number_invalid-utf-8-in-bigger-int.json
 create mode 100644 test/cases/json_test_suite/n_number_invalid-utf-8-in-exponent.json
 create mode 100644 test/cases/json_test_suite/n_number_invalid-utf-8-in-int.json
 create mode 100644 test/cases/json_test_suite/n_number_minus_infinity.json
 create mode 100644 test/cases/json_test_suite/n_number_minus_sign_with_trailing_garbage.json
 create mode 100644 test/cases/json_test_suite/n_number_minus_space_1.json
 create mode 100644 test/cases/json_test_suite/n_number_neg_int_starting_with_zero.json
 create mode 100644 test/cases/json_test_suite/n_number_neg_real_without_int_part.json
 create mode 100644 test/cases/json_test_suite/n_number_neg_with_garbage_at_end.json
 create mode 100644 test/cases/json_test_suite/n_number_real_garbage_after_e.json
 create mode 100644 test/cases/json_test_suite/n_number_real_with_invalid_utf8_after_e.json
 create mode 100644 test/cases/json_test_suite/n_number_real_without_fractional_part.json
 create mode 100644 test/cases/json_test_suite/n_number_starting_with_dot.json
 create mode 100644 test/cases/json_test_suite/n_number_with_alpha.json
 create mode 100644 test/cases/json_test_suite/n_number_with_alpha_char.json
 create mode 100644 test/cases/json_test_suite/n_number_with_leading_zero.json
 create mode 100644 test/cases/json_test_suite/n_object_bad_value.json
 create mode 100644 test/cases/json_test_suite/n_object_bracket_key.json
 create mode 100644 test/cases/json_test_suite/n_object_comma_instead_of_colon.json
 create mode 100644 test/cases/json_test_suite/n_object_double_colon.json
 create mode 100644 test/cases/json_test_suite/n_object_emoji.json
 create mode 100644 test/cases/json_test_suite/n_object_garbage_at_end.json
 create mode 100644 test/cases/json_test_suite/n_object_key_with_single_quotes.json
 create mode 100644 test/cases/json_test_suite/n_object_lone_continuation_byte_in_key_and_trailing_comma.json
 create mode 100644 test/cases/json_test_suite/n_object_missing_colon.json
 create mode 100644 test/cases/json_test_suite/n_object_missing_key.json
 create mode 100644 test/cases/json_test_suite/n_object_missing_semicolon.json
 create mode 100644 test/cases/json_test_suite/n_object_missing_value.json
 create mode 100644 test/cases/json_test_suite/n_object_no-colon.json
 create mode 100644 test/cases/json_test_suite/n_object_non_string_key.json
 create mode 100644 test/cases/json_test_suite/n_object_non_string_key_but_huge_number_instead.json
 create mode 100644 test/cases/json_test_suite/n_object_repeated_null_null.json
 create mode 100644 test/cases/json_test_suite/n_object_several_trailing_commas.json
 create mode 100644 test/cases/json_test_suite/n_object_single_quote.json
 create mode 100644 test/cases/json_test_suite/n_object_trailing_comma.json
 create mode 100644 test/cases/json_test_suite/n_object_trailing_comment.json
 create mode 100644 test/cases/json_test_suite/n_object_trailing_comment_open.json
 create mode 100644 test/cases/json_test_suite/n_object_trailing_comment_slash_open.json
 create mode 100644 test/cases/json_test_suite/n_object_trailing_comment_slash_open_incomplete.json
 create mode 100644 test/cases/json_test_suite/n_object_two_commas_in_a_row.json
 create mode 100644 test/cases/json_test_suite/n_object_unquoted_key.json
 create mode 100644 test/cases/json_test_suite/n_object_unterminated-value.json
 create mode 100644 test/cases/json_test_suite/n_object_with_single_string.json
 create mode 100644 test/cases/json_test_suite/n_object_with_trailing_garbage.json
 create mode 100644 test/cases/json_test_suite/n_single_space.json
 create mode 100644 test/cases/json_test_suite/n_string_1_surrogate_then_escape.json
 create mode 100644 test/cases/json_test_suite/n_string_1_surrogate_then_escape_u.json
 create mode 100644 test/cases/json_test_suite/n_string_1_surrogate_then_escape_u1.json
 create mode 100644 test/cases/json_test_suite/n_string_1_surrogate_then_escape_u1x.json
 create mode 100644 test/cases/json_test_suite/n_string_accentuated_char_no_quotes.json
 create mode 100644 test/cases/json_test_suite/n_string_backslash_00.json
 create mode 100644 test/cases/json_test_suite/n_string_escape_x.json
 create mode 100644 test/cases/json_test_suite/n_string_escaped_backslash_bad.json
 create mode 100644 test/cases/json_test_suite/n_string_escaped_ctrl_char_tab.json
 create mode 100644 test/cases/json_test_suite/n_string_escaped_emoji.json
 create mode 100644 test/cases/json_test_suite/n_string_incomplete_escape.json
 create mode 100644 test/cases/json_test_suite/n_string_incomplete_escaped_character.json
 create mode 100644 test/cases/json_test_suite/n_string_incomplete_surrogate.json
 create mode 100644 test/cases/json_test_suite/n_string_incomplete_surrogate_escape_invalid.json
 create mode 100644 test/cases/json_test_suite/n_string_invalid-utf-8-in-escape.json
 create mode 100644 test/cases/json_test_suite/n_string_invalid_backslash_esc.json
 create mode 100644 test/cases/json_test_suite/n_string_invalid_unicode_escape.json
 create mode 100644 test/cases/json_test_suite/n_string_invalid_utf8_after_escape.json
 create mode 100644 test/cases/json_test_suite/n_string_leading_uescaped_thinspace.json
 create mode 100644 test/cases/json_test_suite/n_string_no_quotes_with_bad_escape.json
 create mode 100644 test/cases/json_test_suite/n_string_single_doublequote.json
 create mode 100644 test/cases/json_test_suite/n_string_single_quote.json
 create mode 100644 test/cases/json_test_suite/n_string_single_string_no_double_quotes.json
 create mode 100644 test/cases/json_test_suite/n_string_start_escape_unclosed.json
 create mode 100644 test/cases/json_test_suite/n_string_unescaped_ctrl_char.json
 create mode 100644 test/cases/json_test_suite/n_string_unescaped_newline.json
 create mode 100644 test/cases/json_test_suite/n_string_unescaped_tab.json
 create mode 100644 test/cases/json_test_suite/n_string_unicode_CapitalU.json
 create mode 100644 test/cases/json_test_suite/n_string_with_trailing_garbage.json
 create mode 100644 test/cases/json_test_suite/n_structure_100000_opening_arrays.json
 create mode 100644 test/cases/json_test_suite/n_structure_U+2060_word_joined.json
 create mode 100644 test/cases/json_test_suite/n_structure_UTF8_BOM_no_data.json
 create mode 100644 test/cases/json_test_suite/n_structure_angle_bracket_..json
 create mode 100644 test/cases/json_test_suite/n_structure_angle_bracket_null.json
 create mode 100644 test/cases/json_test_suite/n_structure_array_trailing_garbage.json
 create mode 100644 test/cases/json_test_suite/n_structure_array_with_extra_array_close.json
 create mode 100644 test/cases/json_test_suite/n_structure_array_with_unclosed_string.json
 create mode 100644 test/cases/json_test_suite/n_structure_ascii-unicode-identifier.json
 create mode 100644 test/cases/json_test_suite/n_structure_capitalized_True.json
 create mode 100644 test/cases/json_test_suite/n_structure_close_unopened_array.json
 create mode 100644 test/cases/json_test_suite/n_structure_comma_instead_of_closing_brace.json
 create mode 100644 test/cases/json_test_suite/n_structure_double_array.json
 create mode 100644 test/cases/json_test_suite/n_structure_end_array.json
 create mode 100644 test/cases/json_test_suite/n_structure_incomplete_UTF8_BOM.json
 create mode 100644 test/cases/json_test_suite/n_structure_lone-invalid-utf-8.json
 create mode 100644 test/cases/json_test_suite/n_structure_lone-open-bracket.json
 create mode 100644 test/cases/json_test_suite/n_structure_no_data.json
 create mode 100644 test/cases/json_test_suite/n_structure_null-byte-outside-string.json
 create mode 100644 test/cases/json_test_suite/n_structure_number_with_trailing_garbage.json
 create mode 100644 test/cases/json_test_suite/n_structure_object_followed_by_closing_object.json
 create mode 100644 test/cases/json_test_suite/n_structure_object_unclosed_no_value.json
 create mode 100644 test/cases/json_test_suite/n_structure_object_with_comment.json
 create mode 100644 test/cases/json_test_suite/n_structure_object_with_trailing_garbage.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_array_apostrophe.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_array_comma.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_array_object.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_array_open_object.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_array_open_string.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_array_string.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_object.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_object_close_array.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_object_comma.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_object_open_array.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_object_open_string.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_object_string_with_apostrophes.json
 create mode 100644 test/cases/json_test_suite/n_structure_open_open.json
 create mode 100644 test/cases/json_test_suite/n_structure_single_eacute.json
 create mode 100644 test/cases/json_test_suite/n_structure_single_star.json
 create mode 100644 test/cases/json_test_suite/n_structure_trailing_#.json
 create mode 100644 test/cases/json_test_suite/n_structure_uescaped_LF_before_string.json
 create mode 100644 test/cases/json_test_suite/n_structure_unclosed_array.json
 create mode 100644 test/cases/json_test_suite/n_structure_unclosed_array_partial_null.json
 create mode 100644 test/cases/json_test_suite/n_structure_unclosed_array_unfinished_false.json
 create mode 100644 test/cases/json_test_suite/n_structure_unclosed_array_unfinished_true.json
 create mode 100644 test/cases/json_test_suite/n_structure_unclosed_object.json
 create mode 100644 test/cases/json_test_suite/n_structure_unicode-identifier.json
 create mode 100644 test/cases/json_test_suite/n_structure_whitespace_U+2060_word_joiner.json
 create mode 100644 test/cases/json_test_suite/n_structure_whitespace_formfeed.json
 create mode 100644 test/cases/json_test_suite/y_array_arraysWithSpaces.json
 create mode 100644 test/cases/json_test_suite/y_array_empty-string.json
 copy test/cases/{empty_array.json => json_test_suite/y_array_empty.json} (100%)
 create mode 100644 test/cases/json_test_suite/y_array_ending_with_newline.json
 create mode 100644 test/cases/json_test_suite/y_array_false.json
 create mode 100644 test/cases/json_test_suite/y_array_heterogeneous.json
 create mode 100644 test/cases/json_test_suite/y_array_null.json
 create mode 100644 test/cases/json_test_suite/y_array_with_1_and_newline.json
 create mode 100644 test/cases/json_test_suite/y_array_with_leading_space.json
 create mode 100644 test/cases/json_test_suite/y_array_with_several_null.json
 create mode 100644 test/cases/json_test_suite/y_array_with_trailing_space.json
 create mode 100644 test/cases/json_test_suite/y_number.json
 create mode 100644 test/cases/json_test_suite/y_number_0e+1.json
 create mode 100644 test/cases/json_test_suite/y_number_0e1.json
 create mode 100644 test/cases/json_test_suite/y_number_after_space.json
 create mode 100644 test/cases/json_test_suite/y_number_double_close_to_zero.json
 create mode 100644 test/cases/json_test_suite/y_number_int_with_exp.json
 create mode 100644 test/cases/json_test_suite/y_number_minus_zero.json
 create mode 100644 test/cases/json_test_suite/y_number_negative_int.json
 create mode 100644 test/cases/json_test_suite/y_number_negative_one.json
 create mode 100644 test/cases/json_test_suite/y_number_negative_zero.json
 create mode 100644 test/cases/json_test_suite/y_number_real_capital_e.json
 create mode 100644 test/cases/json_test_suite/y_number_real_capital_e_neg_exp.json
 create mode 100644 test/cases/json_test_suite/y_number_real_capital_e_pos_exp.json
 create mode 100644 test/cases/json_test_suite/y_number_real_exponent.json
 create mode 100644 test/cases/json_test_suite/y_number_real_fraction_exponent.json
 create mode 100644 test/cases/json_test_suite/y_number_real_neg_exp.json
 create mode 100644 test/cases/json_test_suite/y_number_real_pos_exponent.json
 create mode 100644 test/cases/json_test_suite/y_number_simple_int.json
 create mode 100644 test/cases/json_test_suite/y_number_simple_real.json
 create mode 100644 test/cases/json_test_suite/y_object.json
 create mode 100644 test/cases/json_test_suite/y_object_basic.json
 create mode 100644 test/cases/json_test_suite/y_object_duplicated_key.json
 create mode 100644 test/cases/json_test_suite/y_object_duplicated_key_and_value.json
 create mode 100644 test/cases/json_test_suite/y_object_empty.json
 create mode 100644 test/cases/json_test_suite/y_object_empty_key.json
 create mode 100644 test/cases/json_test_suite/y_object_escaped_null_in_key.json
 create mode 100644 test/cases/json_test_suite/y_object_extreme_numbers.json
 create mode 100644 test/cases/json_test_suite/y_object_long_strings.json
 create mode 100644 test/cases/json_test_suite/y_object_simple.json
 create mode 100644 test/cases/json_test_suite/y_object_string_unicode.json
 create mode 100644 test/cases/json_test_suite/y_object_with_newlines.json
 create mode 100644 test/cases/json_test_suite/y_string_1_2_3_bytes_UTF-8_sequences.json
 create mode 100644 test/cases/json_test_suite/y_string_accepted_surrogate_pair.json
 create mode 100644 test/cases/json_test_suite/y_string_accepted_surrogate_pairs.json
 create mode 100644 test/cases/json_test_suite/y_string_allowed_escapes.json
 create mode 100644 test/cases/json_test_suite/y_string_backslash_and_u_escaped_zero.json
 create mode 100644 test/cases/json_test_suite/y_string_backslash_doublequotes.json
 create mode 100644 test/cases/json_test_suite/y_string_comments.json
 create mode 100644 test/cases/json_test_suite/y_string_double_escape_a.json
 create mode 100644 test/cases/json_test_suite/y_string_double_escape_n.json
 create mode 100644 test/cases/json_test_suite/y_string_escaped_control_character.json
 create mode 100644 test/cases/json_test_suite/y_string_escaped_noncharacter.json
 create mode 100644 test/cases/json_test_suite/y_string_in_array.json
 create mode 100644 test/cases/json_test_suite/y_string_in_array_with_leading_space.json
 create mode 100644 test/cases/json_test_suite/y_string_last_surrogates_1_and_2.json
 create mode 100644 test/cases/json_test_suite/y_string_nbsp_uescaped.json
 create mode 100644 test/cases/json_test_suite/y_string_nonCharacterInUTF-8_U+10FFFF.json
 create mode 100644 test/cases/json_test_suite/y_string_nonCharacterInUTF-8_U+FFFF.json
 create mode 100644 test/cases/json_test_suite/y_string_null_escape.json
 create mode 100644 test/cases/json_test_suite/y_string_one-byte-utf-8.json
 create mode 100644 test/cases/json_test_suite/y_string_pi.json
 create mode 100644 test/cases/json_test_suite/y_string_reservedCharacterInUTF-8_U+1BFFF.json
 create mode 100644 test/cases/json_test_suite/y_string_simple_ascii.json
 create mode 100644 test/cases/json_test_suite/y_string_space.json
 create mode 100644 test/cases/json_test_suite/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json
 create mode 100644 test/cases/json_test_suite/y_string_three-byte-utf-8.json
 create mode 100644 test/cases/json_test_suite/y_string_two-byte-utf-8.json
 create mode 100644 test/cases/json_test_suite/y_string_u+2028_line_sep.json
 create mode 100644 test/cases/json_test_suite/y_string_u+2029_par_sep.json
 create mode 100644 test/cases/json_test_suite/y_string_uEscape.json
 create mode 100644 test/cases/json_test_suite/y_string_uescaped_newline.json
 create mode 100644 test/cases/json_test_suite/y_string_unescaped_char_delete.json
 create mode 100644 test/cases/json_test_suite/y_string_unicode.json
 create mode 100644 test/cases/json_test_suite/y_string_unicodeEscapedBackslash.json
 create mode 100644 test/cases/json_test_suite/y_string_unicode_2.json
 create mode 100644 test/cases/json_test_suite/y_string_unicode_U+10FFFE_nonchar.json
 create mode 100644 test/cases/json_test_suite/y_string_unicode_U+1FFFE_nonchar.json
 create mode 100644 test/cases/json_test_suite/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json
 create mode 100644 test/cases/json_test_suite/y_string_unicode_U+2064_invisible_plus.json
 create mode 100644 test/cases/json_test_suite/y_string_unicode_U+FDD0_nonchar.json
 create mode 100644 test/cases/json_test_suite/y_string_unicode_U+FFFE_nonchar.json
 create mode 100644 test/cases/json_test_suite/y_string_unicode_escaped_double_quote.json
 create mode 100644 test/cases/json_test_suite/y_string_utf8.json
 create mode 100644 test/cases/json_test_suite/y_string_with_del_character.json
 create mode 100644 test/cases/json_test_suite/y_structure_lonely_false.json
 create mode 100644 test/cases/json_test_suite/y_structure_lonely_int.json
 create mode 100644 test/cases/json_test_suite/y_structure_lonely_negative_real.json
 create mode 100644 test/cases/json_test_suite/y_structure_lonely_null.json
 create mode 100644 test/cases/json_test_suite/y_structure_lonely_string.json
 create mode 100644 test/cases/json_test_suite/y_structure_lonely_true.json
 create mode 100644 test/cases/json_test_suite/y_structure_string_empty.json
 create mode 100644 test/cases/json_test_suite/y_structure_trailing_newline.json
 create mode 100644 test/cases/json_test_suite/y_structure_true_in_array.json
 create mode 100644 test/cases/json_test_suite/y_structure_whitespace_array.json
 create mode 100755 test/install_eqc_mini.sh
 create mode 100644 test/jiffy_19_yielding_tests.erl
 create mode 100644 test/jiffy_20_lifecycle_tests.erl
 create mode 100644 test/jiffy_21_naughty_strings_tests.erl
 create mode 100644 test/jiffy_22_json_test_suite_tests.erl


(couchdb-jiffy) 30/49: No more FLTO

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 3ab87a6bebce441ddf9dfc2541690779fdcaee5a
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Thu Apr 9 20:34:16 2026 -0400

    No more FLTO
    
    Instead enable the equivalent optimization in compiler using includes and
    static inline functions.
    
    We had bunch of logic to enable FLTO and some issues around it not working
    sometimes. There was snippet even to do a test compile something to see if it
    can be enabled at all in the rebar script. Since it provided enough benefit
    that folks wanted it, let's just have an effective LTO all the time. Let's put
    the hot-path utilities like utf8 and bump-reductions into a header and use
    `static inline` functions. These will let the compiler do the-right-thing(tm)
    and optimize whole compilation unit and will eliminate the need for messing
    around to FLTO linker and compiler options.
---
 c_src/decoder.c                |  1 +
 c_src/encoder.c                |  1 +
 c_src/jiffy.h                  | 24 ++++++------
 c_src/{utf8.c => jiffy_utf8.h} | 87 ++++++++++++++++++++++--------------------
 c_src/util.c                   | 18 ---------
 rebar.config                   |  8 +---
 rebar.config.script            | 25 +-----------
 7 files changed, 64 insertions(+), 100 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index fd2e406..46abfec 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -9,6 +9,7 @@
 
 #include "erl_nif.h"
 #include "jiffy.h"
+#include "jiffy_utf8.h"
 
 #define STACK_SIZE_INC 64
 #define NUM_BUF_LEN 32
diff --git a/c_src/encoder.c b/c_src/encoder.c
index d952652..c8243fa 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -6,6 +6,7 @@
 #include <string.h>
 
 #include "jiffy.h"
+#include "jiffy_utf8.h"
 #include "ryu/ryu.h"
 
 #define BIN_INC_SIZE 2048
diff --git a/c_src/jiffy.h b/c_src/jiffy.h
index daf4bd2..252d2fc 100644
--- a/c_src/jiffy.h
+++ b/c_src/jiffy.h
@@ -63,7 +63,19 @@ int get_null_term(ErlNifEnv* env, ERL_NIF_TERM val, ERL_NIF_TERM *null_term);
 static inline size_t yield_threshold(size_t bytes_per_red) {
     return bytes_per_red * DEFAULT_ERLANG_REDUCTION_COUNT;
 }
-void bump_used_reds(ErlNifEnv* env, size_t used, size_t bytes_per_red);
+
+static inline void
+bump_used_reds(ErlNifEnv* env, size_t used, size_t bytes_per_red)
+{
+    size_t reds_used = used / bytes_per_red;
+    size_t pct_used = 100 * reds_used / DEFAULT_ERLANG_REDUCTION_COUNT;
+    if(pct_used > 0) {
+        if(pct_used > 100) {
+            pct_used = 100;
+        }
+        enif_consume_timeslice(env, pct_used);
+    }
+}
 
 ERL_NIF_TERM decode_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
 ERL_NIF_TERM decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
@@ -76,14 +88,4 @@ void enc_destroy(ErlNifEnv* env, void* obj);
 int make_object(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out,
         int ret_map, int dedupe_keys);
 
-int int_from_hex(const unsigned char* p);
-int int_to_hex(int val, unsigned char* p);
-int utf8_len(int c);
-int utf8_esc_len(int c);
-size_t utf8_validate(unsigned char* data, size_t size);
-int utf8_to_unicode(unsigned char* buf, size_t size);
-int unicode_to_utf8(int c, unsigned char* buf);
-int unicode_from_pair(int hi, int lo);
-int unicode_uescape(int c, unsigned char* buf);
-
 #endif // Included JIFFY_H
diff --git a/c_src/utf8.c b/c_src/jiffy_utf8.h
similarity index 96%
rename from c_src/utf8.c
rename to c_src/jiffy_utf8.h
index 6f8d3bd..b75b9ad 100644
--- a/c_src/utf8.c
+++ b/c_src/jiffy_utf8.h
@@ -1,7 +1,10 @@
 // This file is part of Jiffy released under the MIT license.
 // See the LICENSE file for more information.
-#include "jiffy.h"
-#include <stdio.h>
+
+#ifndef JIFFY_UTF8_H
+#define JIFFY_UTF8_H
+
+#include <string.h>
 
 static const unsigned char hexvals[256] = {
     255, 255, 255, 255, 255, 255, 255, 255,
@@ -46,7 +49,7 @@ static const char hexdigits[16] = {
     'C', 'D', 'E', 'F'
 };
 
-int
+static inline int
 int_from_hex(const unsigned char* p)
 {
     unsigned char* h = (unsigned char*) p;
@@ -65,7 +68,7 @@ int_from_hex(const unsigned char* p)
     return ret;
 }
 
-int
+static inline int
 int_to_hex(int val, unsigned char* p)
 {
     if(val < 0 || val > 65535)
@@ -79,7 +82,7 @@ int_to_hex(int val, unsigned char* p)
     return 1;
 }
 
-int
+static inline int
 utf8_len(int c)
 {
     if(c < 128) {
@@ -99,7 +102,7 @@ utf8_len(int c)
     }
 }
 
-int
+static inline int
 utf8_esc_len(int c)
 {
     if(c < 0x10000) {
@@ -111,7 +114,38 @@ utf8_esc_len(int c)
     }
 }
 
-size_t
+static inline int
+utf8_to_unicode(unsigned char* buf, size_t size)
+{
+    int ret;
+    if((buf[0] & 0x80) == 0x00) {
+        // 0xxxxxxx
+        ret = buf[0];
+    } else if((buf[0] & 0xE0) == 0xC0 && size >= 2) {
+        // 110xxxxy 10yyyyyy
+        ret = ((buf[0] & 0x1F) << 6)
+            | ((buf[1] & 0x3F));
+    } else if((buf[0] & 0xF0) == 0xE0 && size >= 3) {
+        // 1110xxxx 10xyyyyy 10yyyyyy
+        ret = ((buf[0] & 0x0F) << 12)
+            | ((buf[1] & 0x3F) << 6)
+            | ((buf[2] & 0x3F));
+        if(ret >= 0xD800 && ret <= 0xDFFF) {
+            ret = -1;
+        }
+    } else if((buf[0] & 0xF8) == 0xF0 && size >= 4) {
+        // 11110xxx 10xxyyyy 10yyyyyy 10yyyyyy
+        ret = ((buf[0] & 0x07) << 18)
+            | ((buf[1] & 0x3F) << 12)
+            | ((buf[2] & 0x3F) << 6)
+            | ((buf[3] & 0x3F));
+    } else {
+        ret = -1;
+    }
+    return ret;
+}
+
+static inline size_t
 utf8_validate(unsigned char* data, size_t size)
 {
     size_t ulen = 0;
@@ -175,38 +209,7 @@ utf8_validate(unsigned char* data, size_t size)
     return ulen;
 }
 
-int
-utf8_to_unicode(unsigned char* buf, size_t size)
-{
-    int ret;
-    if((buf[0] & 0x80) == 0x00) {
-        // 0xxxxxxx
-        ret = buf[0];
-    } else if((buf[0] & 0xE0) == 0xC0 && size >= 2) {
-        // 110xxxxy 10yyyyyy
-        ret = ((buf[0] & 0x1F) << 6)
-            | ((buf[1] & 0x3F));
-    } else if((buf[0] & 0xF0) == 0xE0 && size >= 3) {
-        // 1110xxxx 10xyyyyy 10yyyyyy
-        ret = ((buf[0] & 0x0F) << 12)
-            | ((buf[1] & 0x3F) << 6)
-            | ((buf[2] & 0x3F));
-        if(ret >= 0xD800 && ret <= 0xDFFF) {
-            ret = -1;
-        }
-    } else if((buf[0] & 0xF8) == 0xF0 && size >= 4) {
-        // 11110xxx 10xxyyyy 10yyyyyy 10yyyyyy
-        ret = ((buf[0] & 0x07) << 18)
-            | ((buf[1] & 0x3F) << 12)
-            | ((buf[2] & 0x3F) << 6)
-            | ((buf[3] & 0x3F));
-    } else {
-        ret = -1;
-    }
-    return ret;
-}
-
-int
+static inline int
 unicode_to_utf8(int c, unsigned char* buf)
 {
     if(c < 0x80) {
@@ -235,7 +238,7 @@ unicode_to_utf8(int c, unsigned char* buf)
     return -1;
 }
 
-int
+static inline int
 unicode_from_pair(int hi, int lo)
 {
     if(hi < 0xD800 || hi >= 0xDC00) return -1;
@@ -243,7 +246,7 @@ unicode_from_pair(int hi, int lo)
     return ((hi & 0x3FF) << 10) + (lo & 0x3FF) + 0x10000;
 }
 
-int
+static inline int
 unicode_uescape(int val, unsigned char* p)
 {
     int n;
@@ -270,3 +273,5 @@ unicode_uescape(int val, unsigned char* p)
     }
     return -1;
 }
+
+#endif // JIFFY_UTF8_H
diff --git a/c_src/util.c b/c_src/util.c
index b1a0242..cc23a15 100644
--- a/c_src/util.c
+++ b/c_src/util.c
@@ -100,21 +100,3 @@ get_null_term(ErlNifEnv* env, ERL_NIF_TERM val, ERL_NIF_TERM *null_term)
 
     return 1;
 }
-
-void
-bump_used_reds(ErlNifEnv* env, size_t used, size_t bytes_per_red)
-{
-    size_t reds_used;
-    size_t pct_used;
-
-    reds_used = used / bytes_per_red;
-    pct_used = 100 * reds_used / DEFAULT_ERLANG_REDUCTION_COUNT;
-
-    if(pct_used > 0) {
-        if(pct_used > 100) {
-            pct_used = 100;
-        }
-
-        enif_consume_timeslice(env, pct_used);
-    }
-}
diff --git a/rebar.config b/rebar.config
index 960b5ba..f98a95c 100644
--- a/rebar.config
+++ b/rebar.config
@@ -11,14 +11,8 @@
     % Drop -lerl_interface
     {"ERL_LDFLAGS", " -L$ERL_EI_LIBDIR -lei"},
     {"win32", "ERL_LDFLAGS", " /LIBPATH:$ERL_EI_LIBDIR ei.lib"},
-
-    {".*", "FLTO_FLAG", ""},
-
-    {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)",
-        "CFLAGS", "$CFLAGS -Ic_src/ -g -Wall $FLTO_FLAG -Werror -O3 -fvisibility=hidden"},
-
     {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)",
-        "LDFLAGS", "$LDFLAGS $FLTO_FLAG"}
+        "CFLAGS", "$CFLAGS -Ic_src/ -g -Wall -Werror -O3 -fvisibility=hidden"}
 ]}.
 
 {eunit_opts, [
diff --git a/rebar.config.script b/rebar.config.script
index 604f4de..375ef6f 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -27,27 +27,6 @@ Config1 = case lists:keyfind(erl_opts, 1, CONFIG) of
         CONFIG ++ [{erl_opts, ErlOpts}]
 end,
 
-Config2 = case os:type() of
-    {unix, _} ->
-        CC = case os:getenv("CC") of
-            false -> "cc";
-            Else -> Else
-        end,
-        FLTO_CHECK = "echo 'int main(int argc, char *argv[]) {return 0;}' | "
-                ++ CC ++ " -c -x c -o /dev/null -flto -",
-        case os:cmd(FLTO_CHECK) of
-            [] ->
-                {port_env, PortEnv} = lists:keyfind(port_env, 1, Config1),
-                NewFlag = {".*", "FLTO_FLAG", "-flto"},
-                NewPortEnv = lists:keyreplace("FLTO_FLAG", 2, PortEnv, NewFlag),
-                lists:keyreplace(port_env, 1, Config1, {port_env, NewPortEnv});
-            _ ->
-                Config1
-        end;
-    _ ->
-        Config1
-end,
-
 IsRebar2 = case lists:keyfind(rebar, 1, application:loaded_applications()) of
     {rebar, _Desc, Vsn} ->
         case string:split(Vsn, ".") of
@@ -60,9 +39,9 @@ end,
 
 case IsRebar2 of
     true ->
-        Config2;
+        Config1;
     false ->
-        Config2 ++ [
+        Config1 ++ [
             {plugins, [{pc, "~> 1.15"}]},
             case os:type() of
                 {win32, _} -> {artifacts, ["priv/jiffy.dll"]};


(couchdb-jiffy) 27/49: Switch to Ryu for float point encoding

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit b42c9a48c89e4a09d6e1090e99f971f099b5f7e6
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Fri Apr 3 10:14:49 2026 -0400

    Switch to Ryu for float point encoding
    
    [Ryu](https://github.com/ulfjack/ryu) is faster then `double-conversion`, is
    adopted by Erlang/OTP team (and other languages and frameworks), and is written
    in C instead of C++.
    
    Since we're an Erlang library we'll just use the Erlang/OTP version of
    [Ryu](https://github.com/erlang/otp/tree/master/erts/emulator/ryu). We'll
    accept its defaults and peculiariaties and go with it, even if it means not
    producing string-identical output as we did before (for instance, after this
    change we don't emit `+` signs in front of the exponent and we also switch to
    the scientific notation a lot sooner, but as they say "when in Rome ...").
---
 Makefile                                     |    4 +-
 README.ryu.md                                |   28 +
 c_src/double-conversion/AUTHORS              |   14 -
 c_src/double-conversion/COPYING              |   26 -
 c_src/double-conversion/LICENSE              |   26 -
 c_src/double-conversion/README.md            |   54 --
 c_src/double-conversion/VERSION              |    3 -
 c_src/double-conversion/bignum-dtoa.cc       |  641 ---------------
 c_src/double-conversion/bignum-dtoa.h        |   84 --
 c_src/double-conversion/bignum.cc            |  767 -----------------
 c_src/double-conversion/bignum.h             |  144 ----
 c_src/double-conversion/cached-powers.cc     |  175 ----
 c_src/double-conversion/cached-powers.h      |   64 --
 c_src/double-conversion/diy-fp.cc            |   57 --
 c_src/double-conversion/diy-fp.h             |  118 ---
 c_src/double-conversion/double-conversion.cc | 1144 --------------------------
 c_src/double-conversion/double-conversion.h  |  576 -------------
 c_src/double-conversion/fast-dtoa.cc         |  665 ---------------
 c_src/double-conversion/fast-dtoa.h          |   88 --
 c_src/double-conversion/fixed-dtoa.cc        |  405 ---------
 c_src/double-conversion/fixed-dtoa.h         |   56 --
 c_src/double-conversion/ieee.h               |  402 ---------
 c_src/double-conversion/strtod.cc            |  580 -------------
 c_src/double-conversion/strtod.h             |   45 -
 c_src/double-conversion/utils.h              |  367 ---------
 c_src/doubles.cc                             |   33 -
 c_src/encoder.c                              |   13 +-
 c_src/jiffy.h                                |    1 -
 c_src/ryu/LICENSE-Apache2                    |  201 +++++
 c_src/ryu/LICENSE-Boost                      |   23 +
 c_src/ryu/common.h                           |  116 +++
 c_src/ryu/d2s.c                              |  391 +++++++++
 c_src/ryu/d2s_full_table.h                   |  367 +++++++++
 c_src/ryu/d2s_intrinsics.h                   |  359 ++++++++
 c_src/ryu/digit_table.h                      |   35 +
 c_src/ryu/ryu.h                              |   37 +
 c_src/ryu/to_chars.h                         |  342 ++++++++
 rebar.config                                 |    2 +-
 test/jiffy_03_number_tests.erl               |   12 +-
 39 files changed, 1912 insertions(+), 6553 deletions(-)

diff --git a/Makefile b/Makefile
index 0a88ae6..71da10c 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ clean:
 	rm -f test/*.beam
 	rm -rf eqc
 	rm -rf _build
-	rm -f c_src/*.gcno c_src/*.gcda c_src/double-conversion/*.gcno c_src/double-conversion/*.gcda
+	rm -f c_src/*.gcno c_src/*.gcda c_src/ryu/*.gcno c_src/ryu/*.gcda
 	rm -f coverage.info coverage-jiffy.info
 	rm -rf coverage-html
 
@@ -44,7 +44,7 @@ coverage:
 	$(MAKE) clean
 	CFLAGS="--coverage -O0" CXXFLAGS="--coverage -O0" LDFLAGS="--coverage" $(MAKE) check-with-eqc
 	@lcov --capture --directory c_src -o coverage.info --ignore-errors inconsistent,unsupported
-	@lcov --extract coverage.info '*/c_src/*' --exclude '*/double-conversion/*' -o coverage-jiffy.info --ignore-errors inconsistent,unsupported
+	@lcov --extract coverage.info '*/c_src/*' --exclude '*/ryu/*' -o coverage-jiffy.info --ignore-errors inconsistent,unsupported
 	@genhtml coverage-jiffy.info -o coverage-html --title "jiffy lcov report"
 	@echo "For coverage report: open coverage-html/index.html"
 
diff --git a/README.ryu.md b/README.ryu.md
new file mode 100644
index 0000000..27ece6a
--- /dev/null
+++ b/README.ryu.md
@@ -0,0 +1,28 @@
+# Ryu
+
+Our version of Ryu cames as-is from Erlang/OTP, accepting its format. We're an
+Erlang library so we'll accept what Erlang/OTP team goes with.
+
+Erlang's Ryu in turn is a modified version of
+[Ryu](https://github.com/ulfjack/ryu), keeping only what's needed, and then
+modified to produce Erlang-like formatted floats. For example, original Ryu
+will produce `"1E1"` from `10.0`, while Erlang and Jiffy will write `"10.0"`.
+
+# How to update
+
+```sh
+OTP_SRC=/path/to/otp
+
+cp $OTP_SRC/erts/emulator/ryu/common.h         c_src/ryu/
+cp $OTP_SRC/erts/emulator/ryu/d2s.c            c_src/ryu/
+cp $OTP_SRC/erts/emulator/ryu/d2s_full_table.h c_src/ryu/
+cp $OTP_SRC/erts/emulator/ryu/d2s_intrinsics.h c_src/ryu/
+cp $OTP_SRC/erts/emulator/ryu/digit_table.h    c_src/ryu/
+cp $OTP_SRC/erts/emulator/ryu/ryu.h            c_src/ryu/
+cp $OTP_SRC/erts/emulator/ryu/to_chars.h       c_src/ryu/
+cp $OTP_SRC/erts/emulator/ryu/LICENSE-Apache2   c_src/ryu/
+cp $OTP_SRC/erts/emulator/ryu/LICENSE-Boost     c_src/ryu/
+```
+
+Note: we don't modify the ryu files after this. But we do handle the `-0.0` ==
+`0.0` case in `c_src/double.c`.
diff --git a/c_src/double-conversion/AUTHORS b/c_src/double-conversion/AUTHORS
deleted file mode 100644
index 88b38ae..0000000
--- a/c_src/double-conversion/AUTHORS
+++ /dev/null
@@ -1,14 +0,0 @@
-# Below is a list of people and organizations that have contributed
-# to the double-conversion project.  Names should be added to the
-# list like so:
-#
-#   Name/Organization <email address>
-
-Google Inc.
-Mozilla Foundation
-
-Jeff Muizelaar <jm...@mozilla.com>
-Mike Hommey <mh...@mozilla.com>
-Martin Olsson <mn...@minimum.se>
-Kent Williams <ch...@gmail.com>
-Elan Ruusamäe <gl...@delfi.ee>
diff --git a/c_src/double-conversion/COPYING b/c_src/double-conversion/COPYING
deleted file mode 100644
index 933718a..0000000
--- a/c_src/double-conversion/COPYING
+++ /dev/null
@@ -1,26 +0,0 @@
-Copyright 2006-2011, the V8 project authors. All rights reserved.
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-    * Redistributions of source code must retain the above copyright
-      notice, this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above
-      copyright notice, this list of conditions and the following
-      disclaimer in the documentation and/or other materials provided
-      with the distribution.
-    * Neither the name of Google Inc. nor the names of its
-      contributors may be used to endorse or promote products derived
-      from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/c_src/double-conversion/LICENSE b/c_src/double-conversion/LICENSE
deleted file mode 100644
index 933718a..0000000
--- a/c_src/double-conversion/LICENSE
+++ /dev/null
@@ -1,26 +0,0 @@
-Copyright 2006-2011, the V8 project authors. All rights reserved.
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-    * Redistributions of source code must retain the above copyright
-      notice, this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above
-      copyright notice, this list of conditions and the following
-      disclaimer in the documentation and/or other materials provided
-      with the distribution.
-    * Neither the name of Google Inc. nor the names of its
-      contributors may be used to endorse or promote products derived
-      from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/c_src/double-conversion/README.md b/c_src/double-conversion/README.md
deleted file mode 100644
index 4ae9be5..0000000
--- a/c_src/double-conversion/README.md
+++ /dev/null
@@ -1,54 +0,0 @@
-https://github.com/google/double-conversion
-
-This project (double-conversion) provides binary-decimal and decimal-binary
-routines for IEEE doubles.
-
-The library consists of efficient conversion routines that have been extracted
-from the V8 JavaScript engine. The code has been refactored and improved so that
-it can be used more easily in other projects.
-
-There is extensive documentation in `double-conversion/double-conversion.h`. Other 
-examples can be found in `test/cctest/test-conversions.cc`.
-
-
-Building
-========
-
-This library can be built with [scons][0] or [cmake][1].
-The checked-in Makefile simply forwards to scons, and provides a
-shortcut to run all tests:
-
-    make
-    make test
-
-Scons
------
-
-The easiest way to install this library is to use `scons`. It builds
-the static and shared library, and is set up to install those at the
-correct locations:
-
-    scons install
-
-Use the `DESTDIR` option to change the target directory:
-
-    scons DESTDIR=alternative_directory install
-
-Cmake
------
-
-To use cmake run `cmake .` in the root directory. This overwrites the
-existing Makefile.
-
-Use `-DBUILD_SHARED_LIBS=ON` to enable the compilation of shared libraries.
-Note that this disables static libraries. There is currently no way to
-build both libraries at the same time with cmake.
-
-Use `-DBUILD_TESTING=ON` to build the test executable.
-
-    cmake . -DBUILD_TESTING=ON
-    make
-    test/cctest/cctest --list | tr -d '<' | xargs test/cctest/cctest
-
-[0]: http://www.scons.org/
-[1]: https://cmake.org/
diff --git a/c_src/double-conversion/VERSION b/c_src/double-conversion/VERSION
deleted file mode 100644
index 89e9751..0000000
--- a/c_src/double-conversion/VERSION
+++ /dev/null
@@ -1,3 +0,0 @@
-Clone from: https://github.com/google/double-conversion
-Version: v3.1.4
-Commit: 8fcee05fae7f2d6962deb9093891c47993c3d6a2
diff --git a/c_src/double-conversion/bignum-dtoa.cc b/c_src/double-conversion/bignum-dtoa.cc
deleted file mode 100644
index d99ac2a..0000000
--- a/c_src/double-conversion/bignum-dtoa.cc
+++ /dev/null
@@ -1,641 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#include <cmath>
-
-#include "bignum-dtoa.h"
-
-#include "bignum.h"
-#include "ieee.h"
-
-namespace double_conversion {
-
-static int NormalizedExponent(uint64_t significand, int exponent) {
-  ASSERT(significand != 0);
-  while ((significand & Double::kHiddenBit) == 0) {
-    significand = significand << 1;
-    exponent = exponent - 1;
-  }
-  return exponent;
-}
-
-
-// Forward declarations:
-// Returns an estimation of k such that 10^(k-1) <= v < 10^k.
-static int EstimatePower(int exponent);
-// Computes v / 10^estimated_power exactly, as a ratio of two bignums, numerator
-// and denominator.
-static void InitialScaledStartValues(uint64_t significand,
-                                     int exponent,
-                                     bool lower_boundary_is_closer,
-                                     int estimated_power,
-                                     bool need_boundary_deltas,
-                                     Bignum* numerator,
-                                     Bignum* denominator,
-                                     Bignum* delta_minus,
-                                     Bignum* delta_plus);
-// Multiplies numerator/denominator so that its values lies in the range 1-10.
-// Returns decimal_point s.t.
-//  v = numerator'/denominator' * 10^(decimal_point-1)
-//     where numerator' and denominator' are the values of numerator and
-//     denominator after the call to this function.
-static void FixupMultiply10(int estimated_power, bool is_even,
-                            int* decimal_point,
-                            Bignum* numerator, Bignum* denominator,
-                            Bignum* delta_minus, Bignum* delta_plus);
-// Generates digits from the left to the right and stops when the generated
-// digits yield the shortest decimal representation of v.
-static void GenerateShortestDigits(Bignum* numerator, Bignum* denominator,
-                                   Bignum* delta_minus, Bignum* delta_plus,
-                                   bool is_even,
-                                   Vector<char> buffer, int* length);
-// Generates 'requested_digits' after the decimal point.
-static void BignumToFixed(int requested_digits, int* decimal_point,
-                          Bignum* numerator, Bignum* denominator,
-                          Vector<char>(buffer), int* length);
-// Generates 'count' digits of numerator/denominator.
-// Once 'count' digits have been produced rounds the result depending on the
-// remainder (remainders of exactly .5 round upwards). Might update the
-// decimal_point when rounding up (for example for 0.9999).
-static void GenerateCountedDigits(int count, int* decimal_point,
-                                  Bignum* numerator, Bignum* denominator,
-                                  Vector<char>(buffer), int* length);
-
-
-void BignumDtoa(double v, BignumDtoaMode mode, int requested_digits,
-                Vector<char> buffer, int* length, int* decimal_point) {
-  ASSERT(v > 0);
-  ASSERT(!Double(v).IsSpecial());
-  uint64_t significand;
-  int exponent;
-  bool lower_boundary_is_closer;
-  if (mode == BIGNUM_DTOA_SHORTEST_SINGLE) {
-    float f = static_cast<float>(v);
-    ASSERT(f == v);
-    significand = Single(f).Significand();
-    exponent = Single(f).Exponent();
-    lower_boundary_is_closer = Single(f).LowerBoundaryIsCloser();
-  } else {
-    significand = Double(v).Significand();
-    exponent = Double(v).Exponent();
-    lower_boundary_is_closer = Double(v).LowerBoundaryIsCloser();
-  }
-  bool need_boundary_deltas =
-      (mode == BIGNUM_DTOA_SHORTEST || mode == BIGNUM_DTOA_SHORTEST_SINGLE);
-
-  bool is_even = (significand & 1) == 0;
-  int normalized_exponent = NormalizedExponent(significand, exponent);
-  // estimated_power might be too low by 1.
-  int estimated_power = EstimatePower(normalized_exponent);
-
-  // Shortcut for Fixed.
-  // The requested digits correspond to the digits after the point. If the
-  // number is much too small, then there is no need in trying to get any
-  // digits.
-  if (mode == BIGNUM_DTOA_FIXED && -estimated_power - 1 > requested_digits) {
-    buffer[0] = '\0';
-    *length = 0;
-    // Set decimal-point to -requested_digits. This is what Gay does.
-    // Note that it should not have any effect anyways since the string is
-    // empty.
-    *decimal_point = -requested_digits;
-    return;
-  }
-
-  Bignum numerator;
-  Bignum denominator;
-  Bignum delta_minus;
-  Bignum delta_plus;
-  // Make sure the bignum can grow large enough. The smallest double equals
-  // 4e-324. In this case the denominator needs fewer than 324*4 binary digits.
-  // The maximum double is 1.7976931348623157e308 which needs fewer than
-  // 308*4 binary digits.
-  ASSERT(Bignum::kMaxSignificantBits >= 324*4);
-  InitialScaledStartValues(significand, exponent, lower_boundary_is_closer,
-                           estimated_power, need_boundary_deltas,
-                           &numerator, &denominator,
-                           &delta_minus, &delta_plus);
-  // We now have v = (numerator / denominator) * 10^estimated_power.
-  FixupMultiply10(estimated_power, is_even, decimal_point,
-                  &numerator, &denominator,
-                  &delta_minus, &delta_plus);
-  // We now have v = (numerator / denominator) * 10^(decimal_point-1), and
-  //  1 <= (numerator + delta_plus) / denominator < 10
-  switch (mode) {
-    case BIGNUM_DTOA_SHORTEST:
-    case BIGNUM_DTOA_SHORTEST_SINGLE:
-      GenerateShortestDigits(&numerator, &denominator,
-                             &delta_minus, &delta_plus,
-                             is_even, buffer, length);
-      break;
-    case BIGNUM_DTOA_FIXED:
-      BignumToFixed(requested_digits, decimal_point,
-                    &numerator, &denominator,
-                    buffer, length);
-      break;
-    case BIGNUM_DTOA_PRECISION:
-      GenerateCountedDigits(requested_digits, decimal_point,
-                            &numerator, &denominator,
-                            buffer, length);
-      break;
-    default:
-      UNREACHABLE();
-  }
-  buffer[*length] = '\0';
-}
-
-
-// The procedure starts generating digits from the left to the right and stops
-// when the generated digits yield the shortest decimal representation of v. A
-// decimal representation of v is a number lying closer to v than to any other
-// double, so it converts to v when read.
-//
-// This is true if d, the decimal representation, is between m- and m+, the
-// upper and lower boundaries. d must be strictly between them if !is_even.
-//           m- := (numerator - delta_minus) / denominator
-//           m+ := (numerator + delta_plus) / denominator
-//
-// Precondition: 0 <= (numerator+delta_plus) / denominator < 10.
-//   If 1 <= (numerator+delta_plus) / denominator < 10 then no leading 0 digit
-//   will be produced. This should be the standard precondition.
-static void GenerateShortestDigits(Bignum* numerator, Bignum* denominator,
-                                   Bignum* delta_minus, Bignum* delta_plus,
-                                   bool is_even,
-                                   Vector<char> buffer, int* length) {
-  // Small optimization: if delta_minus and delta_plus are the same just reuse
-  // one of the two bignums.
-  if (Bignum::Equal(*delta_minus, *delta_plus)) {
-    delta_plus = delta_minus;
-  }
-  *length = 0;
-  for (;;) {
-    uint16_t digit;
-    digit = numerator->DivideModuloIntBignum(*denominator);
-    ASSERT(digit <= 9);  // digit is a uint16_t and therefore always positive.
-    // digit = numerator / denominator (integer division).
-    // numerator = numerator % denominator.
-    buffer[(*length)++] = static_cast<char>(digit + '0');
-
-    // Can we stop already?
-    // If the remainder of the division is less than the distance to the lower
-    // boundary we can stop. In this case we simply round down (discarding the
-    // remainder).
-    // Similarly we test if we can round up (using the upper boundary).
-    bool in_delta_room_minus;
-    bool in_delta_room_plus;
-    if (is_even) {
-      in_delta_room_minus = Bignum::LessEqual(*numerator, *delta_minus);
-    } else {
-      in_delta_room_minus = Bignum::Less(*numerator, *delta_minus);
-    }
-    if (is_even) {
-      in_delta_room_plus =
-          Bignum::PlusCompare(*numerator, *delta_plus, *denominator) >= 0;
-    } else {
-      in_delta_room_plus =
-          Bignum::PlusCompare(*numerator, *delta_plus, *denominator) > 0;
-    }
-    if (!in_delta_room_minus && !in_delta_room_plus) {
-      // Prepare for next iteration.
-      numerator->Times10();
-      delta_minus->Times10();
-      // We optimized delta_plus to be equal to delta_minus (if they share the
-      // same value). So don't multiply delta_plus if they point to the same
-      // object.
-      if (delta_minus != delta_plus) {
-        delta_plus->Times10();
-      }
-    } else if (in_delta_room_minus && in_delta_room_plus) {
-      // Let's see if 2*numerator < denominator.
-      // If yes, then the next digit would be < 5 and we can round down.
-      int compare = Bignum::PlusCompare(*numerator, *numerator, *denominator);
-      if (compare < 0) {
-        // Remaining digits are less than .5. -> Round down (== do nothing).
-      } else if (compare > 0) {
-        // Remaining digits are more than .5 of denominator. -> Round up.
-        // Note that the last digit could not be a '9' as otherwise the whole
-        // loop would have stopped earlier.
-        // We still have an assert here in case the preconditions were not
-        // satisfied.
-        ASSERT(buffer[(*length) - 1] != '9');
-        buffer[(*length) - 1]++;
-      } else {
-        // Halfway case.
-        // TODO(floitsch): need a way to solve half-way cases.
-        //   For now let's round towards even (since this is what Gay seems to
-        //   do).
-
-        if ((buffer[(*length) - 1] - '0') % 2 == 0) {
-          // Round down => Do nothing.
-        } else {
-          ASSERT(buffer[(*length) - 1] != '9');
-          buffer[(*length) - 1]++;
-        }
-      }
-      return;
-    } else if (in_delta_room_minus) {
-      // Round down (== do nothing).
-      return;
-    } else {  // in_delta_room_plus
-      // Round up.
-      // Note again that the last digit could not be '9' since this would have
-      // stopped the loop earlier.
-      // We still have an ASSERT here, in case the preconditions were not
-      // satisfied.
-      ASSERT(buffer[(*length) -1] != '9');
-      buffer[(*length) - 1]++;
-      return;
-    }
-  }
-}
-
-
-// Let v = numerator / denominator < 10.
-// Then we generate 'count' digits of d = x.xxxxx... (without the decimal point)
-// from left to right. Once 'count' digits have been produced we decide wether
-// to round up or down. Remainders of exactly .5 round upwards. Numbers such
-// as 9.999999 propagate a carry all the way, and change the
-// exponent (decimal_point), when rounding upwards.
-static void GenerateCountedDigits(int count, int* decimal_point,
-                                  Bignum* numerator, Bignum* denominator,
-                                  Vector<char> buffer, int* length) {
-  ASSERT(count >= 0);
-  for (int i = 0; i < count - 1; ++i) {
-    uint16_t digit;
-    digit = numerator->DivideModuloIntBignum(*denominator);
-    ASSERT(digit <= 9);  // digit is a uint16_t and therefore always positive.
-    // digit = numerator / denominator (integer division).
-    // numerator = numerator % denominator.
-    buffer[i] = static_cast<char>(digit + '0');
-    // Prepare for next iteration.
-    numerator->Times10();
-  }
-  // Generate the last digit.
-  uint16_t digit;
-  digit = numerator->DivideModuloIntBignum(*denominator);
-  if (Bignum::PlusCompare(*numerator, *numerator, *denominator) >= 0) {
-    digit++;
-  }
-  ASSERT(digit <= 10);
-  buffer[count - 1] = static_cast<char>(digit + '0');
-  // Correct bad digits (in case we had a sequence of '9's). Propagate the
-  // carry until we hat a non-'9' or til we reach the first digit.
-  for (int i = count - 1; i > 0; --i) {
-    if (buffer[i] != '0' + 10) break;
-    buffer[i] = '0';
-    buffer[i - 1]++;
-  }
-  if (buffer[0] == '0' + 10) {
-    // Propagate a carry past the top place.
-    buffer[0] = '1';
-    (*decimal_point)++;
-  }
-  *length = count;
-}
-
-
-// Generates 'requested_digits' after the decimal point. It might omit
-// trailing '0's. If the input number is too small then no digits at all are
-// generated (ex.: 2 fixed digits for 0.00001).
-//
-// Input verifies:  1 <= (numerator + delta) / denominator < 10.
-static void BignumToFixed(int requested_digits, int* decimal_point,
-                          Bignum* numerator, Bignum* denominator,
-                          Vector<char>(buffer), int* length) {
-  // Note that we have to look at more than just the requested_digits, since
-  // a number could be rounded up. Example: v=0.5 with requested_digits=0.
-  // Even though the power of v equals 0 we can't just stop here.
-  if (-(*decimal_point) > requested_digits) {
-    // The number is definitively too small.
-    // Ex: 0.001 with requested_digits == 1.
-    // Set decimal-point to -requested_digits. This is what Gay does.
-    // Note that it should not have any effect anyways since the string is
-    // empty.
-    *decimal_point = -requested_digits;
-    *length = 0;
-    return;
-  } else if (-(*decimal_point) == requested_digits) {
-    // We only need to verify if the number rounds down or up.
-    // Ex: 0.04 and 0.06 with requested_digits == 1.
-    ASSERT(*decimal_point == -requested_digits);
-    // Initially the fraction lies in range (1, 10]. Multiply the denominator
-    // by 10 so that we can compare more easily.
-    denominator->Times10();
-    if (Bignum::PlusCompare(*numerator, *numerator, *denominator) >= 0) {
-      // If the fraction is >= 0.5 then we have to include the rounded
-      // digit.
-      buffer[0] = '1';
-      *length = 1;
-      (*decimal_point)++;
-    } else {
-      // Note that we caught most of similar cases earlier.
-      *length = 0;
-    }
-    return;
-  } else {
-    // The requested digits correspond to the digits after the point.
-    // The variable 'needed_digits' includes the digits before the point.
-    int needed_digits = (*decimal_point) + requested_digits;
-    GenerateCountedDigits(needed_digits, decimal_point,
-                          numerator, denominator,
-                          buffer, length);
-  }
-}
-
-
-// Returns an estimation of k such that 10^(k-1) <= v < 10^k where
-// v = f * 2^exponent and 2^52 <= f < 2^53.
-// v is hence a normalized double with the given exponent. The output is an
-// approximation for the exponent of the decimal approimation .digits * 10^k.
-//
-// The result might undershoot by 1 in which case 10^k <= v < 10^k+1.
-// Note: this property holds for v's upper boundary m+ too.
-//    10^k <= m+ < 10^k+1.
-//   (see explanation below).
-//
-// Examples:
-//  EstimatePower(0)   => 16
-//  EstimatePower(-52) => 0
-//
-// Note: e >= 0 => EstimatedPower(e) > 0. No similar claim can be made for e<0.
-static int EstimatePower(int exponent) {
-  // This function estimates log10 of v where v = f*2^e (with e == exponent).
-  // Note that 10^floor(log10(v)) <= v, but v <= 10^ceil(log10(v)).
-  // Note that f is bounded by its container size. Let p = 53 (the double's
-  // significand size). Then 2^(p-1) <= f < 2^p.
-  //
-  // Given that log10(v) == log2(v)/log2(10) and e+(len(f)-1) is quite close
-  // to log2(v) the function is simplified to (e+(len(f)-1)/log2(10)).
-  // The computed number undershoots by less than 0.631 (when we compute log3
-  // and not log10).
-  //
-  // Optimization: since we only need an approximated result this computation
-  // can be performed on 64 bit integers. On x86/x64 architecture the speedup is
-  // not really measurable, though.
-  //
-  // Since we want to avoid overshooting we decrement by 1e10 so that
-  // floating-point imprecisions don't affect us.
-  //
-  // Explanation for v's boundary m+: the computation takes advantage of
-  // the fact that 2^(p-1) <= f < 2^p. Boundaries still satisfy this requirement
-  // (even for denormals where the delta can be much more important).
-
-  const double k1Log10 = 0.30102999566398114;  // 1/lg(10)
-
-  // For doubles len(f) == 53 (don't forget the hidden bit).
-  const int kSignificandSize = Double::kSignificandSize;
-  double estimate = ceil((exponent + kSignificandSize - 1) * k1Log10 - 1e-10);
-  return static_cast<int>(estimate);
-}
-
-
-// See comments for InitialScaledStartValues.
-static void InitialScaledStartValuesPositiveExponent(
-    uint64_t significand, int exponent,
-    int estimated_power, bool need_boundary_deltas,
-    Bignum* numerator, Bignum* denominator,
-    Bignum* delta_minus, Bignum* delta_plus) {
-  // A positive exponent implies a positive power.
-  ASSERT(estimated_power >= 0);
-  // Since the estimated_power is positive we simply multiply the denominator
-  // by 10^estimated_power.
-
-  // numerator = v.
-  numerator->AssignUInt64(significand);
-  numerator->ShiftLeft(exponent);
-  // denominator = 10^estimated_power.
-  denominator->AssignPowerUInt16(10, estimated_power);
-
-  if (need_boundary_deltas) {
-    // Introduce a common denominator so that the deltas to the boundaries are
-    // integers.
-    denominator->ShiftLeft(1);
-    numerator->ShiftLeft(1);
-    // Let v = f * 2^e, then m+ - v = 1/2 * 2^e; With the common
-    // denominator (of 2) delta_plus equals 2^e.
-    delta_plus->AssignUInt16(1);
-    delta_plus->ShiftLeft(exponent);
-    // Same for delta_minus. The adjustments if f == 2^p-1 are done later.
-    delta_minus->AssignUInt16(1);
-    delta_minus->ShiftLeft(exponent);
-  }
-}
-
-
-// See comments for InitialScaledStartValues
-static void InitialScaledStartValuesNegativeExponentPositivePower(
-    uint64_t significand, int exponent,
-    int estimated_power, bool need_boundary_deltas,
-    Bignum* numerator, Bignum* denominator,
-    Bignum* delta_minus, Bignum* delta_plus) {
-  // v = f * 2^e with e < 0, and with estimated_power >= 0.
-  // This means that e is close to 0 (have a look at how estimated_power is
-  // computed).
-
-  // numerator = significand
-  //  since v = significand * 2^exponent this is equivalent to
-  //  numerator = v * / 2^-exponent
-  numerator->AssignUInt64(significand);
-  // denominator = 10^estimated_power * 2^-exponent (with exponent < 0)
-  denominator->AssignPowerUInt16(10, estimated_power);
-  denominator->ShiftLeft(-exponent);
-
-  if (need_boundary_deltas) {
-    // Introduce a common denominator so that the deltas to the boundaries are
-    // integers.
-    denominator->ShiftLeft(1);
-    numerator->ShiftLeft(1);
-    // Let v = f * 2^e, then m+ - v = 1/2 * 2^e; With the common
-    // denominator (of 2) delta_plus equals 2^e.
-    // Given that the denominator already includes v's exponent the distance
-    // to the boundaries is simply 1.
-    delta_plus->AssignUInt16(1);
-    // Same for delta_minus. The adjustments if f == 2^p-1 are done later.
-    delta_minus->AssignUInt16(1);
-  }
-}
-
-
-// See comments for InitialScaledStartValues
-static void InitialScaledStartValuesNegativeExponentNegativePower(
-    uint64_t significand, int exponent,
-    int estimated_power, bool need_boundary_deltas,
-    Bignum* numerator, Bignum* denominator,
-    Bignum* delta_minus, Bignum* delta_plus) {
-  // Instead of multiplying the denominator with 10^estimated_power we
-  // multiply all values (numerator and deltas) by 10^-estimated_power.
-
-  // Use numerator as temporary container for power_ten.
-  Bignum* power_ten = numerator;
-  power_ten->AssignPowerUInt16(10, -estimated_power);
-
-  if (need_boundary_deltas) {
-    // Since power_ten == numerator we must make a copy of 10^estimated_power
-    // before we complete the computation of the numerator.
-    // delta_plus = delta_minus = 10^estimated_power
-    delta_plus->AssignBignum(*power_ten);
-    delta_minus->AssignBignum(*power_ten);
-  }
-
-  // numerator = significand * 2 * 10^-estimated_power
-  //  since v = significand * 2^exponent this is equivalent to
-  // numerator = v * 10^-estimated_power * 2 * 2^-exponent.
-  // Remember: numerator has been abused as power_ten. So no need to assign it
-  //  to itself.
-  ASSERT(numerator == power_ten);
-  numerator->MultiplyByUInt64(significand);
-
-  // denominator = 2 * 2^-exponent with exponent < 0.
-  denominator->AssignUInt16(1);
-  denominator->ShiftLeft(-exponent);
-
-  if (need_boundary_deltas) {
-    // Introduce a common denominator so that the deltas to the boundaries are
-    // integers.
-    numerator->ShiftLeft(1);
-    denominator->ShiftLeft(1);
-    // With this shift the boundaries have their correct value, since
-    // delta_plus = 10^-estimated_power, and
-    // delta_minus = 10^-estimated_power.
-    // These assignments have been done earlier.
-    // The adjustments if f == 2^p-1 (lower boundary is closer) are done later.
-  }
-}
-
-
-// Let v = significand * 2^exponent.
-// Computes v / 10^estimated_power exactly, as a ratio of two bignums, numerator
-// and denominator. The functions GenerateShortestDigits and
-// GenerateCountedDigits will then convert this ratio to its decimal
-// representation d, with the required accuracy.
-// Then d * 10^estimated_power is the representation of v.
-// (Note: the fraction and the estimated_power might get adjusted before
-// generating the decimal representation.)
-//
-// The initial start values consist of:
-//  - a scaled numerator: s.t. numerator/denominator == v / 10^estimated_power.
-//  - a scaled (common) denominator.
-//  optionally (used by GenerateShortestDigits to decide if it has the shortest
-//  decimal converting back to v):
-//  - v - m-: the distance to the lower boundary.
-//  - m+ - v: the distance to the upper boundary.
-//
-// v, m+, m-, and therefore v - m- and m+ - v all share the same denominator.
-//
-// Let ep == estimated_power, then the returned values will satisfy:
-//  v / 10^ep = numerator / denominator.
-//  v's boundarys m- and m+:
-//    m- / 10^ep == v / 10^ep - delta_minus / denominator
-//    m+ / 10^ep == v / 10^ep + delta_plus / denominator
-//  Or in other words:
-//    m- == v - delta_minus * 10^ep / denominator;
-//    m+ == v + delta_plus * 10^ep / denominator;
-//
-// Since 10^(k-1) <= v < 10^k    (with k == estimated_power)
-//  or       10^k <= v < 10^(k+1)
-//  we then have 0.1 <= numerator/denominator < 1
-//           or    1 <= numerator/denominator < 10
-//
-// It is then easy to kickstart the digit-generation routine.
-//
-// The boundary-deltas are only filled if the mode equals BIGNUM_DTOA_SHORTEST
-// or BIGNUM_DTOA_SHORTEST_SINGLE.
-
-static void InitialScaledStartValues(uint64_t significand,
-                                     int exponent,
-                                     bool lower_boundary_is_closer,
-                                     int estimated_power,
-                                     bool need_boundary_deltas,
-                                     Bignum* numerator,
-                                     Bignum* denominator,
-                                     Bignum* delta_minus,
-                                     Bignum* delta_plus) {
-  if (exponent >= 0) {
-    InitialScaledStartValuesPositiveExponent(
-        significand, exponent, estimated_power, need_boundary_deltas,
-        numerator, denominator, delta_minus, delta_plus);
-  } else if (estimated_power >= 0) {
-    InitialScaledStartValuesNegativeExponentPositivePower(
-        significand, exponent, estimated_power, need_boundary_deltas,
-        numerator, denominator, delta_minus, delta_plus);
-  } else {
-    InitialScaledStartValuesNegativeExponentNegativePower(
-        significand, exponent, estimated_power, need_boundary_deltas,
-        numerator, denominator, delta_minus, delta_plus);
-  }
-
-  if (need_boundary_deltas && lower_boundary_is_closer) {
-    // The lower boundary is closer at half the distance of "normal" numbers.
-    // Increase the common denominator and adapt all but the delta_minus.
-    denominator->ShiftLeft(1);  // *2
-    numerator->ShiftLeft(1);    // *2
-    delta_plus->ShiftLeft(1);   // *2
-  }
-}
-
-
-// This routine multiplies numerator/denominator so that its values lies in the
-// range 1-10. That is after a call to this function we have:
-//    1 <= (numerator + delta_plus) /denominator < 10.
-// Let numerator the input before modification and numerator' the argument
-// after modification, then the output-parameter decimal_point is such that
-//  numerator / denominator * 10^estimated_power ==
-//    numerator' / denominator' * 10^(decimal_point - 1)
-// In some cases estimated_power was too low, and this is already the case. We
-// then simply adjust the power so that 10^(k-1) <= v < 10^k (with k ==
-// estimated_power) but do not touch the numerator or denominator.
-// Otherwise the routine multiplies the numerator and the deltas by 10.
-static void FixupMultiply10(int estimated_power, bool is_even,
-                            int* decimal_point,
-                            Bignum* numerator, Bignum* denominator,
-                            Bignum* delta_minus, Bignum* delta_plus) {
-  bool in_range;
-  if (is_even) {
-    // For IEEE doubles half-way cases (in decimal system numbers ending with 5)
-    // are rounded to the closest floating-point number with even significand.
-    in_range = Bignum::PlusCompare(*numerator, *delta_plus, *denominator) >= 0;
-  } else {
-    in_range = Bignum::PlusCompare(*numerator, *delta_plus, *denominator) > 0;
-  }
-  if (in_range) {
-    // Since numerator + delta_plus >= denominator we already have
-    // 1 <= numerator/denominator < 10. Simply update the estimated_power.
-    *decimal_point = estimated_power + 1;
-  } else {
-    *decimal_point = estimated_power;
-    numerator->Times10();
-    if (Bignum::Equal(*delta_minus, *delta_plus)) {
-      delta_minus->Times10();
-      delta_plus->AssignBignum(*delta_minus);
-    } else {
-      delta_minus->Times10();
-      delta_plus->Times10();
-    }
-  }
-}
-
-}  // namespace double_conversion
diff --git a/c_src/double-conversion/bignum-dtoa.h b/c_src/double-conversion/bignum-dtoa.h
deleted file mode 100644
index 34b9619..0000000
--- a/c_src/double-conversion/bignum-dtoa.h
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef DOUBLE_CONVERSION_BIGNUM_DTOA_H_
-#define DOUBLE_CONVERSION_BIGNUM_DTOA_H_
-
-#include "utils.h"
-
-namespace double_conversion {
-
-enum BignumDtoaMode {
-  // Return the shortest correct representation.
-  // For example the output of 0.299999999999999988897 is (the less accurate but
-  // correct) 0.3.
-  BIGNUM_DTOA_SHORTEST,
-  // Same as BIGNUM_DTOA_SHORTEST but for single-precision floats.
-  BIGNUM_DTOA_SHORTEST_SINGLE,
-  // Return a fixed number of digits after the decimal point.
-  // For instance fixed(0.1, 4) becomes 0.1000
-  // If the input number is big, the output will be big.
-  BIGNUM_DTOA_FIXED,
-  // Return a fixed number of digits, no matter what the exponent is.
-  BIGNUM_DTOA_PRECISION
-};
-
-// Converts the given double 'v' to ascii.
-// The result should be interpreted as buffer * 10^(point-length).
-// The buffer will be null-terminated.
-//
-// The input v must be > 0 and different from NaN, and Infinity.
-//
-// The output depends on the given mode:
-//  - SHORTEST: produce the least amount of digits for which the internal
-//   identity requirement is still satisfied. If the digits are printed
-//   (together with the correct exponent) then reading this number will give
-//   'v' again. The buffer will choose the representation that is closest to
-//   'v'. If there are two at the same distance, than the number is round up.
-//   In this mode the 'requested_digits' parameter is ignored.
-//  - FIXED: produces digits necessary to print a given number with
-//   'requested_digits' digits after the decimal point. The produced digits
-//   might be too short in which case the caller has to fill the gaps with '0's.
-//   Example: toFixed(0.001, 5) is allowed to return buffer="1", point=-2.
-//   Halfway cases are rounded up. The call toFixed(0.15, 2) thus returns
-//     buffer="2", point=0.
-//   Note: the length of the returned buffer has no meaning wrt the significance
-//   of its digits. That is, just because it contains '0's does not mean that
-//   any other digit would not satisfy the internal identity requirement.
-//  - PRECISION: produces 'requested_digits' where the first digit is not '0'.
-//   Even though the length of produced digits usually equals
-//   'requested_digits', the function is allowed to return fewer digits, in
-//   which case the caller has to fill the missing digits with '0's.
-//   Halfway cases are again rounded up.
-// 'BignumDtoa' expects the given buffer to be big enough to hold all digits
-// and a terminating null-character.
-void BignumDtoa(double v, BignumDtoaMode mode, int requested_digits,
-                Vector<char> buffer, int* length, int* point);
-
-}  // namespace double_conversion
-
-#endif  // DOUBLE_CONVERSION_BIGNUM_DTOA_H_
diff --git a/c_src/double-conversion/bignum.cc b/c_src/double-conversion/bignum.cc
deleted file mode 100644
index d077eef..0000000
--- a/c_src/double-conversion/bignum.cc
+++ /dev/null
@@ -1,767 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#include "bignum.h"
-#include "utils.h"
-
-namespace double_conversion {
-
-Bignum::Bignum()
-    : bigits_buffer_(), bigits_(bigits_buffer_, kBigitCapacity), used_digits_(0), exponent_(0) {
-  for (int i = 0; i < kBigitCapacity; ++i) {
-    bigits_[i] = 0;
-  }
-}
-
-
-template<typename S>
-static int BitSize(S value) {
-  (void) value;  // Mark variable as used.
-  return 8 * sizeof(value);
-}
-
-// Guaranteed to lie in one Bigit.
-void Bignum::AssignUInt16(uint16_t value) {
-  ASSERT(kBigitSize >= BitSize(value));
-  Zero();
-  if (value == 0) return;
-
-  EnsureCapacity(1);
-  bigits_[0] = value;
-  used_digits_ = 1;
-}
-
-
-void Bignum::AssignUInt64(uint64_t value) {
-  const int kUInt64Size = 64;
-
-  Zero();
-  if (value == 0) return;
-
-  int needed_bigits = kUInt64Size / kBigitSize + 1;
-  EnsureCapacity(needed_bigits);
-  for (int i = 0; i < needed_bigits; ++i) {
-    bigits_[i] = value & kBigitMask;
-    value = value >> kBigitSize;
-  }
-  used_digits_ = needed_bigits;
-  Clamp();
-}
-
-
-void Bignum::AssignBignum(const Bignum& other) {
-  exponent_ = other.exponent_;
-  for (int i = 0; i < other.used_digits_; ++i) {
-    bigits_[i] = other.bigits_[i];
-  }
-  // Clear the excess digits (if there were any).
-  for (int i = other.used_digits_; i < used_digits_; ++i) {
-    bigits_[i] = 0;
-  }
-  used_digits_ = other.used_digits_;
-}
-
-
-static uint64_t ReadUInt64(Vector<const char> buffer,
-                           int from,
-                           int digits_to_read) {
-  uint64_t result = 0;
-  for (int i = from; i < from + digits_to_read; ++i) {
-    int digit = buffer[i] - '0';
-    ASSERT(0 <= digit && digit <= 9);
-    result = result * 10 + digit;
-  }
-  return result;
-}
-
-
-void Bignum::AssignDecimalString(Vector<const char> value) {
-  // 2^64 = 18446744073709551616 > 10^19
-  const int kMaxUint64DecimalDigits = 19;
-  Zero();
-  int length = value.length();
-  unsigned int pos = 0;
-  // Let's just say that each digit needs 4 bits.
-  while (length >= kMaxUint64DecimalDigits) {
-    uint64_t digits = ReadUInt64(value, pos, kMaxUint64DecimalDigits);
-    pos += kMaxUint64DecimalDigits;
-    length -= kMaxUint64DecimalDigits;
-    MultiplyByPowerOfTen(kMaxUint64DecimalDigits);
-    AddUInt64(digits);
-  }
-  uint64_t digits = ReadUInt64(value, pos, length);
-  MultiplyByPowerOfTen(length);
-  AddUInt64(digits);
-  Clamp();
-}
-
-
-static int HexCharValue(char c) {
-  if ('0' <= c && c <= '9') return c - '0';
-  if ('a' <= c && c <= 'f') return 10 + c - 'a';
-  ASSERT('A' <= c && c <= 'F');
-  return 10 + c - 'A';
-}
-
-
-void Bignum::AssignHexString(Vector<const char> value) {
-  Zero();
-  int length = value.length();
-
-  int needed_bigits = length * 4 / kBigitSize + 1;
-  EnsureCapacity(needed_bigits);
-  int string_index = length - 1;
-  for (int i = 0; i < needed_bigits - 1; ++i) {
-    // These bigits are guaranteed to be "full".
-    Chunk current_bigit = 0;
-    for (int j = 0; j < kBigitSize / 4; j++) {
-      current_bigit += HexCharValue(value[string_index--]) << (j * 4);
-    }
-    bigits_[i] = current_bigit;
-  }
-  used_digits_ = needed_bigits - 1;
-
-  Chunk most_significant_bigit = 0;  // Could be = 0;
-  for (int j = 0; j <= string_index; ++j) {
-    most_significant_bigit <<= 4;
-    most_significant_bigit += HexCharValue(value[j]);
-  }
-  if (most_significant_bigit != 0) {
-    bigits_[used_digits_] = most_significant_bigit;
-    used_digits_++;
-  }
-  Clamp();
-}
-
-
-void Bignum::AddUInt64(uint64_t operand) {
-  if (operand == 0) return;
-  Bignum other;
-  other.AssignUInt64(operand);
-  AddBignum(other);
-}
-
-
-void Bignum::AddBignum(const Bignum& other) {
-  ASSERT(IsClamped());
-  ASSERT(other.IsClamped());
-
-  // If this has a greater exponent than other append zero-bigits to this.
-  // After this call exponent_ <= other.exponent_.
-  Align(other);
-
-  // There are two possibilities:
-  //   aaaaaaaaaaa 0000  (where the 0s represent a's exponent)
-  //     bbbbb 00000000
-  //   ----------------
-  //   ccccccccccc 0000
-  // or
-  //    aaaaaaaaaa 0000
-  //  bbbbbbbbb 0000000
-  //  -----------------
-  //  cccccccccccc 0000
-  // In both cases we might need a carry bigit.
-
-  EnsureCapacity(1 + Max(BigitLength(), other.BigitLength()) - exponent_);
-  Chunk carry = 0;
-  int bigit_pos = other.exponent_ - exponent_;
-  ASSERT(bigit_pos >= 0);
-  for (int i = 0; i < other.used_digits_; ++i) {
-    Chunk sum = bigits_[bigit_pos] + other.bigits_[i] + carry;
-    bigits_[bigit_pos] = sum & kBigitMask;
-    carry = sum >> kBigitSize;
-    bigit_pos++;
-  }
-
-  while (carry != 0) {
-    Chunk sum = bigits_[bigit_pos] + carry;
-    bigits_[bigit_pos] = sum & kBigitMask;
-    carry = sum >> kBigitSize;
-    bigit_pos++;
-  }
-  used_digits_ = Max(bigit_pos, used_digits_);
-  ASSERT(IsClamped());
-}
-
-
-void Bignum::SubtractBignum(const Bignum& other) {
-  ASSERT(IsClamped());
-  ASSERT(other.IsClamped());
-  // We require this to be bigger than other.
-  ASSERT(LessEqual(other, *this));
-
-  Align(other);
-
-  int offset = other.exponent_ - exponent_;
-  Chunk borrow = 0;
-  int i;
-  for (i = 0; i < other.used_digits_; ++i) {
-    ASSERT((borrow == 0) || (borrow == 1));
-    Chunk difference = bigits_[i + offset] - other.bigits_[i] - borrow;
-    bigits_[i + offset] = difference & kBigitMask;
-    borrow = difference >> (kChunkSize - 1);
-  }
-  while (borrow != 0) {
-    Chunk difference = bigits_[i + offset] - borrow;
-    bigits_[i + offset] = difference & kBigitMask;
-    borrow = difference >> (kChunkSize - 1);
-    ++i;
-  }
-  Clamp();
-}
-
-
-void Bignum::ShiftLeft(int shift_amount) {
-  if (used_digits_ == 0) return;
-  exponent_ += shift_amount / kBigitSize;
-  int local_shift = shift_amount % kBigitSize;
-  EnsureCapacity(used_digits_ + 1);
-  BigitsShiftLeft(local_shift);
-}
-
-
-void Bignum::MultiplyByUInt32(uint32_t factor) {
-  if (factor == 1) return;
-  if (factor == 0) {
-    Zero();
-    return;
-  }
-  if (used_digits_ == 0) return;
-
-  // The product of a bigit with the factor is of size kBigitSize + 32.
-  // Assert that this number + 1 (for the carry) fits into double chunk.
-  ASSERT(kDoubleChunkSize >= kBigitSize + 32 + 1);
-  DoubleChunk carry = 0;
-  for (int i = 0; i < used_digits_; ++i) {
-    DoubleChunk product = static_cast<DoubleChunk>(factor) * bigits_[i] + carry;
-    bigits_[i] = static_cast<Chunk>(product & kBigitMask);
-    carry = (product >> kBigitSize);
-  }
-  while (carry != 0) {
-    EnsureCapacity(used_digits_ + 1);
-    bigits_[used_digits_] = carry & kBigitMask;
-    used_digits_++;
-    carry >>= kBigitSize;
-  }
-}
-
-
-void Bignum::MultiplyByUInt64(uint64_t factor) {
-  if (factor == 1) return;
-  if (factor == 0) {
-    Zero();
-    return;
-  }
-  ASSERT(kBigitSize < 32);
-  uint64_t carry = 0;
-  uint64_t low = factor & 0xFFFFFFFF;
-  uint64_t high = factor >> 32;
-  for (int i = 0; i < used_digits_; ++i) {
-    uint64_t product_low = low * bigits_[i];
-    uint64_t product_high = high * bigits_[i];
-    uint64_t tmp = (carry & kBigitMask) + product_low;
-    bigits_[i] = tmp & kBigitMask;
-    carry = (carry >> kBigitSize) + (tmp >> kBigitSize) +
-        (product_high << (32 - kBigitSize));
-  }
-  while (carry != 0) {
-    EnsureCapacity(used_digits_ + 1);
-    bigits_[used_digits_] = carry & kBigitMask;
-    used_digits_++;
-    carry >>= kBigitSize;
-  }
-}
-
-
-void Bignum::MultiplyByPowerOfTen(int exponent) {
-  const uint64_t kFive27 = UINT64_2PART_C(0x6765c793, fa10079d);
-  const uint16_t kFive1 = 5;
-  const uint16_t kFive2 = kFive1 * 5;
-  const uint16_t kFive3 = kFive2 * 5;
-  const uint16_t kFive4 = kFive3 * 5;
-  const uint16_t kFive5 = kFive4 * 5;
-  const uint16_t kFive6 = kFive5 * 5;
-  const uint32_t kFive7 = kFive6 * 5;
-  const uint32_t kFive8 = kFive7 * 5;
-  const uint32_t kFive9 = kFive8 * 5;
-  const uint32_t kFive10 = kFive9 * 5;
-  const uint32_t kFive11 = kFive10 * 5;
-  const uint32_t kFive12 = kFive11 * 5;
-  const uint32_t kFive13 = kFive12 * 5;
-  const uint32_t kFive1_to_12[] =
-      { kFive1, kFive2, kFive3, kFive4, kFive5, kFive6,
-        kFive7, kFive8, kFive9, kFive10, kFive11, kFive12 };
-
-  ASSERT(exponent >= 0);
-  if (exponent == 0) return;
-  if (used_digits_ == 0) return;
-
-  // We shift by exponent at the end just before returning.
-  int remaining_exponent = exponent;
-  while (remaining_exponent >= 27) {
-    MultiplyByUInt64(kFive27);
-    remaining_exponent -= 27;
-  }
-  while (remaining_exponent >= 13) {
-    MultiplyByUInt32(kFive13);
-    remaining_exponent -= 13;
-  }
-  if (remaining_exponent > 0) {
-    MultiplyByUInt32(kFive1_to_12[remaining_exponent - 1]);
-  }
-  ShiftLeft(exponent);
-}
-
-
-void Bignum::Square() {
-  ASSERT(IsClamped());
-  int product_length = 2 * used_digits_;
-  EnsureCapacity(product_length);
-
-  // Comba multiplication: compute each column separately.
-  // Example: r = a2a1a0 * b2b1b0.
-  //    r =  1    * a0b0 +
-  //        10    * (a1b0 + a0b1) +
-  //        100   * (a2b0 + a1b1 + a0b2) +
-  //        1000  * (a2b1 + a1b2) +
-  //        10000 * a2b2
-  //
-  // In the worst case we have to accumulate nb-digits products of digit*digit.
-  //
-  // Assert that the additional number of bits in a DoubleChunk are enough to
-  // sum up used_digits of Bigit*Bigit.
-  if ((1 << (2 * (kChunkSize - kBigitSize))) <= used_digits_) {
-    UNIMPLEMENTED();
-  }
-  DoubleChunk accumulator = 0;
-  // First shift the digits so we don't overwrite them.
-  int copy_offset = used_digits_;
-  for (int i = 0; i < used_digits_; ++i) {
-    bigits_[copy_offset + i] = bigits_[i];
-  }
-  // We have two loops to avoid some 'if's in the loop.
-  for (int i = 0; i < used_digits_; ++i) {
-    // Process temporary digit i with power i.
-    // The sum of the two indices must be equal to i.
-    int bigit_index1 = i;
-    int bigit_index2 = 0;
-    // Sum all of the sub-products.
-    while (bigit_index1 >= 0) {
-      Chunk chunk1 = bigits_[copy_offset + bigit_index1];
-      Chunk chunk2 = bigits_[copy_offset + bigit_index2];
-      accumulator += static_cast<DoubleChunk>(chunk1) * chunk2;
-      bigit_index1--;
-      bigit_index2++;
-    }
-    bigits_[i] = static_cast<Chunk>(accumulator) & kBigitMask;
-    accumulator >>= kBigitSize;
-  }
-  for (int i = used_digits_; i < product_length; ++i) {
-    int bigit_index1 = used_digits_ - 1;
-    int bigit_index2 = i - bigit_index1;
-    // Invariant: sum of both indices is again equal to i.
-    // Inner loop runs 0 times on last iteration, emptying accumulator.
-    while (bigit_index2 < used_digits_) {
-      Chunk chunk1 = bigits_[copy_offset + bigit_index1];
-      Chunk chunk2 = bigits_[copy_offset + bigit_index2];
-      accumulator += static_cast<DoubleChunk>(chunk1) * chunk2;
-      bigit_index1--;
-      bigit_index2++;
-    }
-    // The overwritten bigits_[i] will never be read in further loop iterations,
-    // because bigit_index1 and bigit_index2 are always greater
-    // than i - used_digits_.
-    bigits_[i] = static_cast<Chunk>(accumulator) & kBigitMask;
-    accumulator >>= kBigitSize;
-  }
-  // Since the result was guaranteed to lie inside the number the
-  // accumulator must be 0 now.
-  ASSERT(accumulator == 0);
-
-  // Don't forget to update the used_digits and the exponent.
-  used_digits_ = product_length;
-  exponent_ *= 2;
-  Clamp();
-}
-
-
-void Bignum::AssignPowerUInt16(uint16_t base, int power_exponent) {
-  ASSERT(base != 0);
-  ASSERT(power_exponent >= 0);
-  if (power_exponent == 0) {
-    AssignUInt16(1);
-    return;
-  }
-  Zero();
-  int shifts = 0;
-  // We expect base to be in range 2-32, and most often to be 10.
-  // It does not make much sense to implement different algorithms for counting
-  // the bits.
-  while ((base & 1) == 0) {
-    base >>= 1;
-    shifts++;
-  }
-  int bit_size = 0;
-  int tmp_base = base;
-  while (tmp_base != 0) {
-    tmp_base >>= 1;
-    bit_size++;
-  }
-  int final_size = bit_size * power_exponent;
-  // 1 extra bigit for the shifting, and one for rounded final_size.
-  EnsureCapacity(final_size / kBigitSize + 2);
-
-  // Left to Right exponentiation.
-  int mask = 1;
-  while (power_exponent >= mask) mask <<= 1;
-
-  // The mask is now pointing to the bit above the most significant 1-bit of
-  // power_exponent.
-  // Get rid of first 1-bit;
-  mask >>= 2;
-  uint64_t this_value = base;
-
-  bool delayed_multiplication = false;
-  const uint64_t max_32bits = 0xFFFFFFFF;
-  while (mask != 0 && this_value <= max_32bits) {
-    this_value = this_value * this_value;
-    // Verify that there is enough space in this_value to perform the
-    // multiplication.  The first bit_size bits must be 0.
-    if ((power_exponent & mask) != 0) {
-      ASSERT(bit_size > 0);
-      uint64_t base_bits_mask =
-          ~((static_cast<uint64_t>(1) << (64 - bit_size)) - 1);
-      bool high_bits_zero = (this_value & base_bits_mask) == 0;
-      if (high_bits_zero) {
-        this_value *= base;
-      } else {
-        delayed_multiplication = true;
-      }
-    }
-    mask >>= 1;
-  }
-  AssignUInt64(this_value);
-  if (delayed_multiplication) {
-    MultiplyByUInt32(base);
-  }
-
-  // Now do the same thing as a bignum.
-  while (mask != 0) {
-    Square();
-    if ((power_exponent & mask) != 0) {
-      MultiplyByUInt32(base);
-    }
-    mask >>= 1;
-  }
-
-  // And finally add the saved shifts.
-  ShiftLeft(shifts * power_exponent);
-}
-
-
-// Precondition: this/other < 16bit.
-uint16_t Bignum::DivideModuloIntBignum(const Bignum& other) {
-  ASSERT(IsClamped());
-  ASSERT(other.IsClamped());
-  ASSERT(other.used_digits_ > 0);
-
-  // Easy case: if we have less digits than the divisor than the result is 0.
-  // Note: this handles the case where this == 0, too.
-  if (BigitLength() < other.BigitLength()) {
-    return 0;
-  }
-
-  Align(other);
-
-  uint16_t result = 0;
-
-  // Start by removing multiples of 'other' until both numbers have the same
-  // number of digits.
-  while (BigitLength() > other.BigitLength()) {
-    // This naive approach is extremely inefficient if `this` divided by other
-    // is big. This function is implemented for doubleToString where
-    // the result should be small (less than 10).
-    ASSERT(other.bigits_[other.used_digits_ - 1] >= ((1 << kBigitSize) / 16));
-    ASSERT(bigits_[used_digits_ - 1] < 0x10000);
-    // Remove the multiples of the first digit.
-    // Example this = 23 and other equals 9. -> Remove 2 multiples.
-    result += static_cast<uint16_t>(bigits_[used_digits_ - 1]);
-    SubtractTimes(other, bigits_[used_digits_ - 1]);
-  }
-
-  ASSERT(BigitLength() == other.BigitLength());
-
-  // Both bignums are at the same length now.
-  // Since other has more than 0 digits we know that the access to
-  // bigits_[used_digits_ - 1] is safe.
-  Chunk this_bigit = bigits_[used_digits_ - 1];
-  Chunk other_bigit = other.bigits_[other.used_digits_ - 1];
-
-  if (other.used_digits_ == 1) {
-    // Shortcut for easy (and common) case.
-    int quotient = this_bigit / other_bigit;
-    bigits_[used_digits_ - 1] = this_bigit - other_bigit * quotient;
-    ASSERT(quotient < 0x10000);
-    result += static_cast<uint16_t>(quotient);
-    Clamp();
-    return result;
-  }
-
-  int division_estimate = this_bigit / (other_bigit + 1);
-  ASSERT(division_estimate < 0x10000);
-  result += static_cast<uint16_t>(division_estimate);
-  SubtractTimes(other, division_estimate);
-
-  if (other_bigit * (division_estimate + 1) > this_bigit) {
-    // No need to even try to subtract. Even if other's remaining digits were 0
-    // another subtraction would be too much.
-    return result;
-  }
-
-  while (LessEqual(other, *this)) {
-    SubtractBignum(other);
-    result++;
-  }
-  return result;
-}
-
-
-template<typename S>
-static int SizeInHexChars(S number) {
-  ASSERT(number > 0);
-  int result = 0;
-  while (number != 0) {
-    number >>= 4;
-    result++;
-  }
-  return result;
-}
-
-
-static char HexCharOfValue(int value) {
-  ASSERT(0 <= value && value <= 16);
-  if (value < 10) return static_cast<char>(value + '0');
-  return static_cast<char>(value - 10 + 'A');
-}
-
-
-bool Bignum::ToHexString(char* buffer, int buffer_size) const {
-  ASSERT(IsClamped());
-  // Each bigit must be printable as separate hex-character.
-  ASSERT(kBigitSize % 4 == 0);
-  const int kHexCharsPerBigit = kBigitSize / 4;
-
-  if (used_digits_ == 0) {
-    if (buffer_size < 2) return false;
-    buffer[0] = '0';
-    buffer[1] = '\0';
-    return true;
-  }
-  // We add 1 for the terminating '\0' character.
-  int needed_chars = (BigitLength() - 1) * kHexCharsPerBigit +
-      SizeInHexChars(bigits_[used_digits_ - 1]) + 1;
-  if (needed_chars > buffer_size) return false;
-  int string_index = needed_chars - 1;
-  buffer[string_index--] = '\0';
-  for (int i = 0; i < exponent_; ++i) {
-    for (int j = 0; j < kHexCharsPerBigit; ++j) {
-      buffer[string_index--] = '0';
-    }
-  }
-  for (int i = 0; i < used_digits_ - 1; ++i) {
-    Chunk current_bigit = bigits_[i];
-    for (int j = 0; j < kHexCharsPerBigit; ++j) {
-      buffer[string_index--] = HexCharOfValue(current_bigit & 0xF);
-      current_bigit >>= 4;
-    }
-  }
-  // And finally the last bigit.
-  Chunk most_significant_bigit = bigits_[used_digits_ - 1];
-  while (most_significant_bigit != 0) {
-    buffer[string_index--] = HexCharOfValue(most_significant_bigit & 0xF);
-    most_significant_bigit >>= 4;
-  }
-  return true;
-}
-
-
-Bignum::Chunk Bignum::BigitAt(int index) const {
-  if (index >= BigitLength()) return 0;
-  if (index < exponent_) return 0;
-  return bigits_[index - exponent_];
-}
-
-
-int Bignum::Compare(const Bignum& a, const Bignum& b) {
-  ASSERT(a.IsClamped());
-  ASSERT(b.IsClamped());
-  int bigit_length_a = a.BigitLength();
-  int bigit_length_b = b.BigitLength();
-  if (bigit_length_a < bigit_length_b) return -1;
-  if (bigit_length_a > bigit_length_b) return +1;
-  for (int i = bigit_length_a - 1; i >= Min(a.exponent_, b.exponent_); --i) {
-    Chunk bigit_a = a.BigitAt(i);
-    Chunk bigit_b = b.BigitAt(i);
-    if (bigit_a < bigit_b) return -1;
-    if (bigit_a > bigit_b) return +1;
-    // Otherwise they are equal up to this digit. Try the next digit.
-  }
-  return 0;
-}
-
-
-int Bignum::PlusCompare(const Bignum& a, const Bignum& b, const Bignum& c) {
-  ASSERT(a.IsClamped());
-  ASSERT(b.IsClamped());
-  ASSERT(c.IsClamped());
-  if (a.BigitLength() < b.BigitLength()) {
-    return PlusCompare(b, a, c);
-  }
-  if (a.BigitLength() + 1 < c.BigitLength()) return -1;
-  if (a.BigitLength() > c.BigitLength()) return +1;
-  // The exponent encodes 0-bigits. So if there are more 0-digits in 'a' than
-  // 'b' has digits, then the bigit-length of 'a'+'b' must be equal to the one
-  // of 'a'.
-  if (a.exponent_ >= b.BigitLength() && a.BigitLength() < c.BigitLength()) {
-    return -1;
-  }
-
-  Chunk borrow = 0;
-  // Starting at min_exponent all digits are == 0. So no need to compare them.
-  int min_exponent = Min(Min(a.exponent_, b.exponent_), c.exponent_);
-  for (int i = c.BigitLength() - 1; i >= min_exponent; --i) {
-    Chunk chunk_a = a.BigitAt(i);
-    Chunk chunk_b = b.BigitAt(i);
-    Chunk chunk_c = c.BigitAt(i);
-    Chunk sum = chunk_a + chunk_b;
-    if (sum > chunk_c + borrow) {
-      return +1;
-    } else {
-      borrow = chunk_c + borrow - sum;
-      if (borrow > 1) return -1;
-      borrow <<= kBigitSize;
-    }
-  }
-  if (borrow == 0) return 0;
-  return -1;
-}
-
-
-void Bignum::Clamp() {
-  while (used_digits_ > 0 && bigits_[used_digits_ - 1] == 0) {
-    used_digits_--;
-  }
-  if (used_digits_ == 0) {
-    // Zero.
-    exponent_ = 0;
-  }
-}
-
-
-bool Bignum::IsClamped() const {
-  return used_digits_ == 0 || bigits_[used_digits_ - 1] != 0;
-}
-
-
-void Bignum::Zero() {
-  for (int i = 0; i < used_digits_; ++i) {
-    bigits_[i] = 0;
-  }
-  used_digits_ = 0;
-  exponent_ = 0;
-}
-
-
-void Bignum::Align(const Bignum& other) {
-  if (exponent_ > other.exponent_) {
-    // If "X" represents a "hidden" digit (by the exponent) then we are in the
-    // following case (a == this, b == other):
-    // a:  aaaaaaXXXX   or a:   aaaaaXXX
-    // b:     bbbbbbX      b: bbbbbbbbXX
-    // We replace some of the hidden digits (X) of a with 0 digits.
-    // a:  aaaaaa000X   or a:   aaaaa0XX
-    int zero_digits = exponent_ - other.exponent_;
-    EnsureCapacity(used_digits_ + zero_digits);
-    for (int i = used_digits_ - 1; i >= 0; --i) {
-      bigits_[i + zero_digits] = bigits_[i];
-    }
-    for (int i = 0; i < zero_digits; ++i) {
-      bigits_[i] = 0;
-    }
-    used_digits_ += zero_digits;
-    exponent_ -= zero_digits;
-    ASSERT(used_digits_ >= 0);
-    ASSERT(exponent_ >= 0);
-  }
-}
-
-
-void Bignum::BigitsShiftLeft(int shift_amount) {
-  ASSERT(shift_amount < kBigitSize);
-  ASSERT(shift_amount >= 0);
-  Chunk carry = 0;
-  for (int i = 0; i < used_digits_; ++i) {
-    Chunk new_carry = bigits_[i] >> (kBigitSize - shift_amount);
-    bigits_[i] = ((bigits_[i] << shift_amount) + carry) & kBigitMask;
-    carry = new_carry;
-  }
-  if (carry != 0) {
-    bigits_[used_digits_] = carry;
-    used_digits_++;
-  }
-}
-
-
-void Bignum::SubtractTimes(const Bignum& other, int factor) {
-  ASSERT(exponent_ <= other.exponent_);
-  if (factor < 3) {
-    for (int i = 0; i < factor; ++i) {
-      SubtractBignum(other);
-    }
-    return;
-  }
-  Chunk borrow = 0;
-  int exponent_diff = other.exponent_ - exponent_;
-  for (int i = 0; i < other.used_digits_; ++i) {
-    DoubleChunk product = static_cast<DoubleChunk>(factor) * other.bigits_[i];
-    DoubleChunk remove = borrow + product;
-    Chunk difference = bigits_[i + exponent_diff] - (remove & kBigitMask);
-    bigits_[i + exponent_diff] = difference & kBigitMask;
-    borrow = static_cast<Chunk>((difference >> (kChunkSize - 1)) +
-                                (remove >> kBigitSize));
-  }
-  for (int i = other.used_digits_ + exponent_diff; i < used_digits_; ++i) {
-    if (borrow == 0) return;
-    Chunk difference = bigits_[i] - borrow;
-    bigits_[i] = difference & kBigitMask;
-    borrow = difference >> (kChunkSize - 1);
-  }
-  Clamp();
-}
-
-
-}  // namespace double_conversion
diff --git a/c_src/double-conversion/bignum.h b/c_src/double-conversion/bignum.h
deleted file mode 100644
index 7c289fa..0000000
--- a/c_src/double-conversion/bignum.h
+++ /dev/null
@@ -1,144 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef DOUBLE_CONVERSION_BIGNUM_H_
-#define DOUBLE_CONVERSION_BIGNUM_H_
-
-#include "utils.h"
-
-namespace double_conversion {
-
-class Bignum {
- public:
-  // 3584 = 128 * 28. We can represent 2^3584 > 10^1000 accurately.
-  // This bignum can encode much bigger numbers, since it contains an
-  // exponent.
-  static const int kMaxSignificantBits = 3584;
-
-  Bignum();
-  void AssignUInt16(uint16_t value);
-  void AssignUInt64(uint64_t value);
-  void AssignBignum(const Bignum& other);
-
-  void AssignDecimalString(Vector<const char> value);
-  void AssignHexString(Vector<const char> value);
-
-  void AssignPowerUInt16(uint16_t base, int exponent);
-
-  void AddUInt64(uint64_t operand);
-  void AddBignum(const Bignum& other);
-  // Precondition: this >= other.
-  void SubtractBignum(const Bignum& other);
-
-  void Square();
-  void ShiftLeft(int shift_amount);
-  void MultiplyByUInt32(uint32_t factor);
-  void MultiplyByUInt64(uint64_t factor);
-  void MultiplyByPowerOfTen(int exponent);
-  void Times10() { return MultiplyByUInt32(10); }
-  // Pseudocode:
-  //  int result = this / other;
-  //  this = this % other;
-  // In the worst case this function is in O(this/other).
-  uint16_t DivideModuloIntBignum(const Bignum& other);
-
-  bool ToHexString(char* buffer, int buffer_size) const;
-
-  // Returns
-  //  -1 if a < b,
-  //   0 if a == b, and
-  //  +1 if a > b.
-  static int Compare(const Bignum& a, const Bignum& b);
-  static bool Equal(const Bignum& a, const Bignum& b) {
-    return Compare(a, b) == 0;
-  }
-  static bool LessEqual(const Bignum& a, const Bignum& b) {
-    return Compare(a, b) <= 0;
-  }
-  static bool Less(const Bignum& a, const Bignum& b) {
-    return Compare(a, b) < 0;
-  }
-  // Returns Compare(a + b, c);
-  static int PlusCompare(const Bignum& a, const Bignum& b, const Bignum& c);
-  // Returns a + b == c
-  static bool PlusEqual(const Bignum& a, const Bignum& b, const Bignum& c) {
-    return PlusCompare(a, b, c) == 0;
-  }
-  // Returns a + b <= c
-  static bool PlusLessEqual(const Bignum& a, const Bignum& b, const Bignum& c) {
-    return PlusCompare(a, b, c) <= 0;
-  }
-  // Returns a + b < c
-  static bool PlusLess(const Bignum& a, const Bignum& b, const Bignum& c) {
-    return PlusCompare(a, b, c) < 0;
-  }
- private:
-  typedef uint32_t Chunk;
-  typedef uint64_t DoubleChunk;
-
-  static const int kChunkSize = sizeof(Chunk) * 8;
-  static const int kDoubleChunkSize = sizeof(DoubleChunk) * 8;
-  // With bigit size of 28 we loose some bits, but a double still fits easily
-  // into two chunks, and more importantly we can use the Comba multiplication.
-  static const int kBigitSize = 28;
-  static const Chunk kBigitMask = (1 << kBigitSize) - 1;
-  // Every instance allocates kBigitLength chunks on the stack. Bignums cannot
-  // grow. There are no checks if the stack-allocated space is sufficient.
-  static const int kBigitCapacity = kMaxSignificantBits / kBigitSize;
-
-  void EnsureCapacity(int size) {
-    if (size > kBigitCapacity) {
-      UNREACHABLE();
-    }
-  }
-  void Align(const Bignum& other);
-  void Clamp();
-  bool IsClamped() const;
-  void Zero();
-  // Requires this to have enough capacity (no tests done).
-  // Updates used_digits_ if necessary.
-  // shift_amount must be < kBigitSize.
-  void BigitsShiftLeft(int shift_amount);
-  // BigitLength includes the "hidden" digits encoded in the exponent.
-  int BigitLength() const { return used_digits_ + exponent_; }
-  Chunk BigitAt(int index) const;
-  void SubtractTimes(const Bignum& other, int factor);
-
-  Chunk bigits_buffer_[kBigitCapacity];
-  // A vector backed by bigits_buffer_. This way accesses to the array are
-  // checked for out-of-bounds errors.
-  Vector<Chunk> bigits_;
-  int used_digits_;
-  // The Bignum's value equals value(bigits_) * 2^(exponent_ * kBigitSize).
-  int exponent_;
-
-  DC_DISALLOW_COPY_AND_ASSIGN(Bignum);
-};
-
-}  // namespace double_conversion
-
-#endif  // DOUBLE_CONVERSION_BIGNUM_H_
diff --git a/c_src/double-conversion/cached-powers.cc b/c_src/double-conversion/cached-powers.cc
deleted file mode 100644
index 8ab281a..0000000
--- a/c_src/double-conversion/cached-powers.cc
+++ /dev/null
@@ -1,175 +0,0 @@
-// Copyright 2006-2008 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#include <climits>
-#include <cmath>
-#include <cstdarg>
-
-#include "utils.h"
-
-#include "cached-powers.h"
-
-namespace double_conversion {
-
-struct CachedPower {
-  uint64_t significand;
-  int16_t binary_exponent;
-  int16_t decimal_exponent;
-};
-
-static const CachedPower kCachedPowers[] = {
-  {UINT64_2PART_C(0xfa8fd5a0, 081c0288), -1220, -348},
-  {UINT64_2PART_C(0xbaaee17f, a23ebf76), -1193, -340},
-  {UINT64_2PART_C(0x8b16fb20, 3055ac76), -1166, -332},
-  {UINT64_2PART_C(0xcf42894a, 5dce35ea), -1140, -324},
-  {UINT64_2PART_C(0x9a6bb0aa, 55653b2d), -1113, -316},
-  {UINT64_2PART_C(0xe61acf03, 3d1a45df), -1087, -308},
-  {UINT64_2PART_C(0xab70fe17, c79ac6ca), -1060, -300},
-  {UINT64_2PART_C(0xff77b1fc, bebcdc4f), -1034, -292},
-  {UINT64_2PART_C(0xbe5691ef, 416bd60c), -1007, -284},
-  {UINT64_2PART_C(0x8dd01fad, 907ffc3c), -980, -276},
-  {UINT64_2PART_C(0xd3515c28, 31559a83), -954, -268},
-  {UINT64_2PART_C(0x9d71ac8f, ada6c9b5), -927, -260},
-  {UINT64_2PART_C(0xea9c2277, 23ee8bcb), -901, -252},
-  {UINT64_2PART_C(0xaecc4991, 4078536d), -874, -244},
-  {UINT64_2PART_C(0x823c1279, 5db6ce57), -847, -236},
-  {UINT64_2PART_C(0xc2109436, 4dfb5637), -821, -228},
-  {UINT64_2PART_C(0x9096ea6f, 3848984f), -794, -220},
-  {UINT64_2PART_C(0xd77485cb, 25823ac7), -768, -212},
-  {UINT64_2PART_C(0xa086cfcd, 97bf97f4), -741, -204},
-  {UINT64_2PART_C(0xef340a98, 172aace5), -715, -196},
-  {UINT64_2PART_C(0xb23867fb, 2a35b28e), -688, -188},
-  {UINT64_2PART_C(0x84c8d4df, d2c63f3b), -661, -180},
-  {UINT64_2PART_C(0xc5dd4427, 1ad3cdba), -635, -172},
-  {UINT64_2PART_C(0x936b9fce, bb25c996), -608, -164},
-  {UINT64_2PART_C(0xdbac6c24, 7d62a584), -582, -156},
-  {UINT64_2PART_C(0xa3ab6658, 0d5fdaf6), -555, -148},
-  {UINT64_2PART_C(0xf3e2f893, dec3f126), -529, -140},
-  {UINT64_2PART_C(0xb5b5ada8, aaff80b8), -502, -132},
-  {UINT64_2PART_C(0x87625f05, 6c7c4a8b), -475, -124},
-  {UINT64_2PART_C(0xc9bcff60, 34c13053), -449, -116},
-  {UINT64_2PART_C(0x964e858c, 91ba2655), -422, -108},
-  {UINT64_2PART_C(0xdff97724, 70297ebd), -396, -100},
-  {UINT64_2PART_C(0xa6dfbd9f, b8e5b88f), -369, -92},
-  {UINT64_2PART_C(0xf8a95fcf, 88747d94), -343, -84},
-  {UINT64_2PART_C(0xb9447093, 8fa89bcf), -316, -76},
-  {UINT64_2PART_C(0x8a08f0f8, bf0f156b), -289, -68},
-  {UINT64_2PART_C(0xcdb02555, 653131b6), -263, -60},
-  {UINT64_2PART_C(0x993fe2c6, d07b7fac), -236, -52},
-  {UINT64_2PART_C(0xe45c10c4, 2a2b3b06), -210, -44},
-  {UINT64_2PART_C(0xaa242499, 697392d3), -183, -36},
-  {UINT64_2PART_C(0xfd87b5f2, 8300ca0e), -157, -28},
-  {UINT64_2PART_C(0xbce50864, 92111aeb), -130, -20},
-  {UINT64_2PART_C(0x8cbccc09, 6f5088cc), -103, -12},
-  {UINT64_2PART_C(0xd1b71758, e219652c), -77, -4},
-  {UINT64_2PART_C(0x9c400000, 00000000), -50, 4},
-  {UINT64_2PART_C(0xe8d4a510, 00000000), -24, 12},
-  {UINT64_2PART_C(0xad78ebc5, ac620000), 3, 20},
-  {UINT64_2PART_C(0x813f3978, f8940984), 30, 28},
-  {UINT64_2PART_C(0xc097ce7b, c90715b3), 56, 36},
-  {UINT64_2PART_C(0x8f7e32ce, 7bea5c70), 83, 44},
-  {UINT64_2PART_C(0xd5d238a4, abe98068), 109, 52},
-  {UINT64_2PART_C(0x9f4f2726, 179a2245), 136, 60},
-  {UINT64_2PART_C(0xed63a231, d4c4fb27), 162, 68},
-  {UINT64_2PART_C(0xb0de6538, 8cc8ada8), 189, 76},
-  {UINT64_2PART_C(0x83c7088e, 1aab65db), 216, 84},
-  {UINT64_2PART_C(0xc45d1df9, 42711d9a), 242, 92},
-  {UINT64_2PART_C(0x924d692c, a61be758), 269, 100},
-  {UINT64_2PART_C(0xda01ee64, 1a708dea), 295, 108},
-  {UINT64_2PART_C(0xa26da399, 9aef774a), 322, 116},
-  {UINT64_2PART_C(0xf209787b, b47d6b85), 348, 124},
-  {UINT64_2PART_C(0xb454e4a1, 79dd1877), 375, 132},
-  {UINT64_2PART_C(0x865b8692, 5b9bc5c2), 402, 140},
-  {UINT64_2PART_C(0xc83553c5, c8965d3d), 428, 148},
-  {UINT64_2PART_C(0x952ab45c, fa97a0b3), 455, 156},
-  {UINT64_2PART_C(0xde469fbd, 99a05fe3), 481, 164},
-  {UINT64_2PART_C(0xa59bc234, db398c25), 508, 172},
-  {UINT64_2PART_C(0xf6c69a72, a3989f5c), 534, 180},
-  {UINT64_2PART_C(0xb7dcbf53, 54e9bece), 561, 188},
-  {UINT64_2PART_C(0x88fcf317, f22241e2), 588, 196},
-  {UINT64_2PART_C(0xcc20ce9b, d35c78a5), 614, 204},
-  {UINT64_2PART_C(0x98165af3, 7b2153df), 641, 212},
-  {UINT64_2PART_C(0xe2a0b5dc, 971f303a), 667, 220},
-  {UINT64_2PART_C(0xa8d9d153, 5ce3b396), 694, 228},
-  {UINT64_2PART_C(0xfb9b7cd9, a4a7443c), 720, 236},
-  {UINT64_2PART_C(0xbb764c4c, a7a44410), 747, 244},
-  {UINT64_2PART_C(0x8bab8eef, b6409c1a), 774, 252},
-  {UINT64_2PART_C(0xd01fef10, a657842c), 800, 260},
-  {UINT64_2PART_C(0x9b10a4e5, e9913129), 827, 268},
-  {UINT64_2PART_C(0xe7109bfb, a19c0c9d), 853, 276},
-  {UINT64_2PART_C(0xac2820d9, 623bf429), 880, 284},
-  {UINT64_2PART_C(0x80444b5e, 7aa7cf85), 907, 292},
-  {UINT64_2PART_C(0xbf21e440, 03acdd2d), 933, 300},
-  {UINT64_2PART_C(0x8e679c2f, 5e44ff8f), 960, 308},
-  {UINT64_2PART_C(0xd433179d, 9c8cb841), 986, 316},
-  {UINT64_2PART_C(0x9e19db92, b4e31ba9), 1013, 324},
-  {UINT64_2PART_C(0xeb96bf6e, badf77d9), 1039, 332},
-  {UINT64_2PART_C(0xaf87023b, 9bf0ee6b), 1066, 340},
-};
-
-static const int kCachedPowersOffset = 348;  // -1 * the first decimal_exponent.
-static const double kD_1_LOG2_10 = 0.30102999566398114;  //  1 / lg(10)
-// Difference between the decimal exponents in the table above.
-const int PowersOfTenCache::kDecimalExponentDistance = 8;
-const int PowersOfTenCache::kMinDecimalExponent = -348;
-const int PowersOfTenCache::kMaxDecimalExponent = 340;
-
-void PowersOfTenCache::GetCachedPowerForBinaryExponentRange(
-    int min_exponent,
-    int max_exponent,
-    DiyFp* power,
-    int* decimal_exponent) {
-  int kQ = DiyFp::kSignificandSize;
-  double k = ceil((min_exponent + kQ - 1) * kD_1_LOG2_10);
-  int foo = kCachedPowersOffset;
-  int index =
-      (foo + static_cast<int>(k) - 1) / kDecimalExponentDistance + 1;
-  ASSERT(0 <= index && index < static_cast<int>(ARRAY_SIZE(kCachedPowers)));
-  CachedPower cached_power = kCachedPowers[index];
-  ASSERT(min_exponent <= cached_power.binary_exponent);
-  (void) max_exponent;  // Mark variable as used.
-  ASSERT(cached_power.binary_exponent <= max_exponent);
-  *decimal_exponent = cached_power.decimal_exponent;
-  *power = DiyFp(cached_power.significand, cached_power.binary_exponent);
-}
-
-
-void PowersOfTenCache::GetCachedPowerForDecimalExponent(int requested_exponent,
-                                                        DiyFp* power,
-                                                        int* found_exponent) {
-  ASSERT(kMinDecimalExponent <= requested_exponent);
-  ASSERT(requested_exponent < kMaxDecimalExponent + kDecimalExponentDistance);
-  int index =
-      (requested_exponent + kCachedPowersOffset) / kDecimalExponentDistance;
-  CachedPower cached_power = kCachedPowers[index];
-  *power = DiyFp(cached_power.significand, cached_power.binary_exponent);
-  *found_exponent = cached_power.decimal_exponent;
-  ASSERT(*found_exponent <= requested_exponent);
-  ASSERT(requested_exponent < *found_exponent + kDecimalExponentDistance);
-}
-
-}  // namespace double_conversion
diff --git a/c_src/double-conversion/cached-powers.h b/c_src/double-conversion/cached-powers.h
deleted file mode 100644
index 61a5061..0000000
--- a/c_src/double-conversion/cached-powers.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef DOUBLE_CONVERSION_CACHED_POWERS_H_
-#define DOUBLE_CONVERSION_CACHED_POWERS_H_
-
-#include "diy-fp.h"
-
-namespace double_conversion {
-
-class PowersOfTenCache {
- public:
-
-  // Not all powers of ten are cached. The decimal exponent of two neighboring
-  // cached numbers will differ by kDecimalExponentDistance.
-  static const int kDecimalExponentDistance;
-
-  static const int kMinDecimalExponent;
-  static const int kMaxDecimalExponent;
-
-  // Returns a cached power-of-ten with a binary exponent in the range
-  // [min_exponent; max_exponent] (boundaries included).
-  static void GetCachedPowerForBinaryExponentRange(int min_exponent,
-                                                   int max_exponent,
-                                                   DiyFp* power,
-                                                   int* decimal_exponent);
-
-  // Returns a cached power of ten x ~= 10^k such that
-  //   k <= decimal_exponent < k + kCachedPowersDecimalDistance.
-  // The given decimal_exponent must satisfy
-  //   kMinDecimalExponent <= requested_exponent, and
-  //   requested_exponent < kMaxDecimalExponent + kDecimalExponentDistance.
-  static void GetCachedPowerForDecimalExponent(int requested_exponent,
-                                               DiyFp* power,
-                                               int* found_exponent);
-};
-
-}  // namespace double_conversion
-
-#endif  // DOUBLE_CONVERSION_CACHED_POWERS_H_
diff --git a/c_src/double-conversion/diy-fp.cc b/c_src/double-conversion/diy-fp.cc
deleted file mode 100644
index ddd1891..0000000
--- a/c_src/double-conversion/diy-fp.cc
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-
-#include "diy-fp.h"
-#include "utils.h"
-
-namespace double_conversion {
-
-void DiyFp::Multiply(const DiyFp& other) {
-  // Simply "emulates" a 128 bit multiplication.
-  // However: the resulting number only contains 64 bits. The least
-  // significant 64 bits are only used for rounding the most significant 64
-  // bits.
-  const uint64_t kM32 = 0xFFFFFFFFU;
-  uint64_t a = f_ >> 32;
-  uint64_t b = f_ & kM32;
-  uint64_t c = other.f_ >> 32;
-  uint64_t d = other.f_ & kM32;
-  uint64_t ac = a * c;
-  uint64_t bc = b * c;
-  uint64_t ad = a * d;
-  uint64_t bd = b * d;
-  uint64_t tmp = (bd >> 32) + (ad & kM32) + (bc & kM32);
-  // By adding 1U << 31 to tmp we round the final result.
-  // Halfway cases will be round up.
-  tmp += 1U << 31;
-  uint64_t result_f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32);
-  e_ += other.e_ + 64;
-  f_ = result_f;
-}
-
-}  // namespace double_conversion
diff --git a/c_src/double-conversion/diy-fp.h b/c_src/double-conversion/diy-fp.h
deleted file mode 100644
index 2edf346..0000000
--- a/c_src/double-conversion/diy-fp.h
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef DOUBLE_CONVERSION_DIY_FP_H_
-#define DOUBLE_CONVERSION_DIY_FP_H_
-
-#include "utils.h"
-
-namespace double_conversion {
-
-// This "Do It Yourself Floating Point" class implements a floating-point number
-// with a uint64 significand and an int exponent. Normalized DiyFp numbers will
-// have the most significant bit of the significand set.
-// Multiplication and Subtraction do not normalize their results.
-// DiyFp are not designed to contain special doubles (NaN and Infinity).
-class DiyFp {
- public:
-  static const int kSignificandSize = 64;
-
-  DiyFp() : f_(0), e_(0) {}
-  DiyFp(uint64_t significand, int exponent) : f_(significand), e_(exponent) {}
-
-  // this = this - other.
-  // The exponents of both numbers must be the same and the significand of this
-  // must be bigger than the significand of other.
-  // The result will not be normalized.
-  void Subtract(const DiyFp& other) {
-    ASSERT(e_ == other.e_);
-    ASSERT(f_ >= other.f_);
-    f_ -= other.f_;
-  }
-
-  // Returns a - b.
-  // The exponents of both numbers must be the same and this must be bigger
-  // than other. The result will not be normalized.
-  static DiyFp Minus(const DiyFp& a, const DiyFp& b) {
-    DiyFp result = a;
-    result.Subtract(b);
-    return result;
-  }
-
-
-  // this = this * other.
-  void Multiply(const DiyFp& other);
-
-  // returns a * b;
-  static DiyFp Times(const DiyFp& a, const DiyFp& b) {
-    DiyFp result = a;
-    result.Multiply(b);
-    return result;
-  }
-
-  void Normalize() {
-    ASSERT(f_ != 0);
-    uint64_t significand = f_;
-    int exponent = e_;
-
-    // This method is mainly called for normalizing boundaries. In general
-    // boundaries need to be shifted by 10 bits. We thus optimize for this case.
-    const uint64_t k10MSBits = UINT64_2PART_C(0xFFC00000, 00000000);
-    while ((significand & k10MSBits) == 0) {
-      significand <<= 10;
-      exponent -= 10;
-    }
-    while ((significand & kUint64MSB) == 0) {
-      significand <<= 1;
-      exponent--;
-    }
-    f_ = significand;
-    e_ = exponent;
-  }
-
-  static DiyFp Normalize(const DiyFp& a) {
-    DiyFp result = a;
-    result.Normalize();
-    return result;
-  }
-
-  uint64_t f() const { return f_; }
-  int e() const { return e_; }
-
-  void set_f(uint64_t new_value) { f_ = new_value; }
-  void set_e(int new_value) { e_ = new_value; }
-
- private:
-  static const uint64_t kUint64MSB = UINT64_2PART_C(0x80000000, 00000000);
-
-  uint64_t f_;
-  int e_;
-};
-
-}  // namespace double_conversion
-
-#endif  // DOUBLE_CONVERSION_DIY_FP_H_
diff --git a/c_src/double-conversion/double-conversion.cc b/c_src/double-conversion/double-conversion.cc
deleted file mode 100644
index 163d41b..0000000
--- a/c_src/double-conversion/double-conversion.cc
+++ /dev/null
@@ -1,1144 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#include <climits>
-#include <locale>
-#include <cmath>
-
-#include "double-conversion.h"
-
-#include "bignum-dtoa.h"
-#include "fast-dtoa.h"
-#include "fixed-dtoa.h"
-#include "ieee.h"
-#include "strtod.h"
-#include "utils.h"
-
-namespace double_conversion {
-
-const DoubleToStringConverter& DoubleToStringConverter::EcmaScriptConverter() {
-  int flags = UNIQUE_ZERO | EMIT_POSITIVE_EXPONENT_SIGN;
-  static DoubleToStringConverter converter(flags,
-                                           "Infinity",
-                                           "NaN",
-                                           'e',
-                                           -6, 21,
-                                           6, 0);
-  return converter;
-}
-
-
-bool DoubleToStringConverter::HandleSpecialValues(
-    double value,
-    StringBuilder* result_builder) const {
-  Double double_inspect(value);
-  if (double_inspect.IsInfinite()) {
-    if (infinity_symbol_ == NULL) return false;
-    if (value < 0) {
-      result_builder->AddCharacter('-');
-    }
-    result_builder->AddString(infinity_symbol_);
-    return true;
-  }
-  if (double_inspect.IsNan()) {
-    if (nan_symbol_ == NULL) return false;
-    result_builder->AddString(nan_symbol_);
-    return true;
-  }
-  return false;
-}
-
-
-void DoubleToStringConverter::CreateExponentialRepresentation(
-    const char* decimal_digits,
-    int length,
-    int exponent,
-    StringBuilder* result_builder) const {
-  ASSERT(length != 0);
-  result_builder->AddCharacter(decimal_digits[0]);
-  if (length != 1) {
-    result_builder->AddCharacter('.');
-    result_builder->AddSubstring(&decimal_digits[1], length-1);
-  }
-  result_builder->AddCharacter(exponent_character_);
-  if (exponent < 0) {
-    result_builder->AddCharacter('-');
-    exponent = -exponent;
-  } else {
-    if ((flags_ & EMIT_POSITIVE_EXPONENT_SIGN) != 0) {
-      result_builder->AddCharacter('+');
-    }
-  }
-  if (exponent == 0) {
-    result_builder->AddCharacter('0');
-    return;
-  }
-  ASSERT(exponent < 1e4);
-  const int kMaxExponentLength = 5;
-  char buffer[kMaxExponentLength + 1];
-  buffer[kMaxExponentLength] = '\0';
-  int first_char_pos = kMaxExponentLength;
-  while (exponent > 0) {
-    buffer[--first_char_pos] = '0' + (exponent % 10);
-    exponent /= 10;
-  }
-  result_builder->AddSubstring(&buffer[first_char_pos],
-                               kMaxExponentLength - first_char_pos);
-}
-
-
-void DoubleToStringConverter::CreateDecimalRepresentation(
-    const char* decimal_digits,
-    int length,
-    int decimal_point,
-    int digits_after_point,
-    StringBuilder* result_builder) const {
-  // Create a representation that is padded with zeros if needed.
-  if (decimal_point <= 0) {
-      // "0.00000decimal_rep" or "0.000decimal_rep00".
-    result_builder->AddCharacter('0');
-    if (digits_after_point > 0) {
-      result_builder->AddCharacter('.');
-      result_builder->AddPadding('0', -decimal_point);
-      ASSERT(length <= digits_after_point - (-decimal_point));
-      result_builder->AddSubstring(decimal_digits, length);
-      int remaining_digits = digits_after_point - (-decimal_point) - length;
-      result_builder->AddPadding('0', remaining_digits);
-    }
-  } else if (decimal_point >= length) {
-    // "decimal_rep0000.00000" or "decimal_rep.0000".
-    result_builder->AddSubstring(decimal_digits, length);
-    result_builder->AddPadding('0', decimal_point - length);
-    if (digits_after_point > 0) {
-      result_builder->AddCharacter('.');
-      result_builder->AddPadding('0', digits_after_point);
-    }
-  } else {
-    // "decima.l_rep000".
-    ASSERT(digits_after_point > 0);
-    result_builder->AddSubstring(decimal_digits, decimal_point);
-    result_builder->AddCharacter('.');
-    ASSERT(length - decimal_point <= digits_after_point);
-    result_builder->AddSubstring(&decimal_digits[decimal_point],
-                                 length - decimal_point);
-    int remaining_digits = digits_after_point - (length - decimal_point);
-    result_builder->AddPadding('0', remaining_digits);
-  }
-  if (digits_after_point == 0) {
-    if ((flags_ & EMIT_TRAILING_DECIMAL_POINT) != 0) {
-      result_builder->AddCharacter('.');
-    }
-    if ((flags_ & EMIT_TRAILING_ZERO_AFTER_POINT) != 0) {
-      result_builder->AddCharacter('0');
-    }
-  }
-}
-
-
-bool DoubleToStringConverter::ToShortestIeeeNumber(
-    double value,
-    StringBuilder* result_builder,
-    DoubleToStringConverter::DtoaMode mode) const {
-  ASSERT(mode == SHORTEST || mode == SHORTEST_SINGLE);
-  if (Double(value).IsSpecial()) {
-    return HandleSpecialValues(value, result_builder);
-  }
-
-  int decimal_point;
-  bool sign;
-  const int kDecimalRepCapacity = kBase10MaximalLength + 1;
-  char decimal_rep[kDecimalRepCapacity];
-  int decimal_rep_length;
-
-  DoubleToAscii(value, mode, 0, decimal_rep, kDecimalRepCapacity,
-                &sign, &decimal_rep_length, &decimal_point);
-
-  bool unique_zero = (flags_ & UNIQUE_ZERO) != 0;
-  if (sign && (value != 0.0 || !unique_zero)) {
-    result_builder->AddCharacter('-');
-  }
-
-  int exponent = decimal_point - 1;
-  if ((decimal_in_shortest_low_ <= exponent) &&
-      (exponent < decimal_in_shortest_high_)) {
-    CreateDecimalRepresentation(decimal_rep, decimal_rep_length,
-                                decimal_point,
-                                Max(0, decimal_rep_length - decimal_point),
-                                result_builder);
-  } else {
-    CreateExponentialRepresentation(decimal_rep, decimal_rep_length, exponent,
-                                    result_builder);
-  }
-  return true;
-}
-
-
-bool DoubleToStringConverter::ToFixed(double value,
-                                      int requested_digits,
-                                      StringBuilder* result_builder) const {
-  ASSERT(kMaxFixedDigitsBeforePoint == 60);
-  const double kFirstNonFixed = 1e60;
-
-  if (Double(value).IsSpecial()) {
-    return HandleSpecialValues(value, result_builder);
-  }
-
-  if (requested_digits > kMaxFixedDigitsAfterPoint) return false;
-  if (value >= kFirstNonFixed || value <= -kFirstNonFixed) return false;
-
-  // Find a sufficiently precise decimal representation of n.
-  int decimal_point;
-  bool sign;
-  // Add space for the '\0' byte.
-  const int kDecimalRepCapacity =
-      kMaxFixedDigitsBeforePoint + kMaxFixedDigitsAfterPoint + 1;
-  char decimal_rep[kDecimalRepCapacity];
-  int decimal_rep_length;
-  DoubleToAscii(value, FIXED, requested_digits,
-                decimal_rep, kDecimalRepCapacity,
-                &sign, &decimal_rep_length, &decimal_point);
-
-  bool unique_zero = ((flags_ & UNIQUE_ZERO) != 0);
-  if (sign && (value != 0.0 || !unique_zero)) {
-    result_builder->AddCharacter('-');
-  }
-
-  CreateDecimalRepresentation(decimal_rep, decimal_rep_length, decimal_point,
-                              requested_digits, result_builder);
-  return true;
-}
-
-
-bool DoubleToStringConverter::ToExponential(
-    double value,
-    int requested_digits,
-    StringBuilder* result_builder) const {
-  if (Double(value).IsSpecial()) {
-    return HandleSpecialValues(value, result_builder);
-  }
-
-  if (requested_digits < -1) return false;
-  if (requested_digits > kMaxExponentialDigits) return false;
-
-  int decimal_point;
-  bool sign;
-  // Add space for digit before the decimal point and the '\0' character.
-  const int kDecimalRepCapacity = kMaxExponentialDigits + 2;
-  ASSERT(kDecimalRepCapacity > kBase10MaximalLength);
-  char decimal_rep[kDecimalRepCapacity];
-#ifndef NDEBUG
-  // Problem: there is an assert in StringBuilder::AddSubstring() that
-  // will pass this buffer to strlen(), and this buffer is not generally
-  // null-terminated.
-  memset(decimal_rep, 0, sizeof(decimal_rep));
-#endif
-  int decimal_rep_length;
-
-  if (requested_digits == -1) {
-    DoubleToAscii(value, SHORTEST, 0,
-                  decimal_rep, kDecimalRepCapacity,
-                  &sign, &decimal_rep_length, &decimal_point);
-  } else {
-    DoubleToAscii(value, PRECISION, requested_digits + 1,
-                  decimal_rep, kDecimalRepCapacity,
-                  &sign, &decimal_rep_length, &decimal_point);
-    ASSERT(decimal_rep_length <= requested_digits + 1);
-
-    for (int i = decimal_rep_length; i < requested_digits + 1; ++i) {
-      decimal_rep[i] = '0';
-    }
-    decimal_rep_length = requested_digits + 1;
-  }
-
-  bool unique_zero = ((flags_ & UNIQUE_ZERO) != 0);
-  if (sign && (value != 0.0 || !unique_zero)) {
-    result_builder->AddCharacter('-');
-  }
-
-  int exponent = decimal_point - 1;
-  CreateExponentialRepresentation(decimal_rep,
-                                  decimal_rep_length,
-                                  exponent,
-                                  result_builder);
-  return true;
-}
-
-
-bool DoubleToStringConverter::ToPrecision(double value,
-                                          int precision,
-                                          StringBuilder* result_builder) const {
-  if (Double(value).IsSpecial()) {
-    return HandleSpecialValues(value, result_builder);
-  }
-
-  if (precision < kMinPrecisionDigits || precision > kMaxPrecisionDigits) {
-    return false;
-  }
-
-  // Find a sufficiently precise decimal representation of n.
-  int decimal_point;
-  bool sign;
-  // Add one for the terminating null character.
-  const int kDecimalRepCapacity = kMaxPrecisionDigits + 1;
-  char decimal_rep[kDecimalRepCapacity];
-  int decimal_rep_length;
-
-  DoubleToAscii(value, PRECISION, precision,
-                decimal_rep, kDecimalRepCapacity,
-                &sign, &decimal_rep_length, &decimal_point);
-  ASSERT(decimal_rep_length <= precision);
-
-  bool unique_zero = ((flags_ & UNIQUE_ZERO) != 0);
-  if (sign && (value != 0.0 || !unique_zero)) {
-    result_builder->AddCharacter('-');
-  }
-
-  // The exponent if we print the number as x.xxeyyy. That is with the
-  // decimal point after the first digit.
-  int exponent = decimal_point - 1;
-
-  int extra_zero = ((flags_ & EMIT_TRAILING_ZERO_AFTER_POINT) != 0) ? 1 : 0;
-  if ((-decimal_point + 1 > max_leading_padding_zeroes_in_precision_mode_) ||
-      (decimal_point - precision + extra_zero >
-       max_trailing_padding_zeroes_in_precision_mode_)) {
-    // Fill buffer to contain 'precision' digits.
-    // Usually the buffer is already at the correct length, but 'DoubleToAscii'
-    // is allowed to return less characters.
-    for (int i = decimal_rep_length; i < precision; ++i) {
-      decimal_rep[i] = '0';
-    }
-
-    CreateExponentialRepresentation(decimal_rep,
-                                    precision,
-                                    exponent,
-                                    result_builder);
-  } else {
-    CreateDecimalRepresentation(decimal_rep, decimal_rep_length, decimal_point,
-                                Max(0, precision - decimal_point),
-                                result_builder);
-  }
-  return true;
-}
-
-
-static BignumDtoaMode DtoaToBignumDtoaMode(
-    DoubleToStringConverter::DtoaMode dtoa_mode) {
-  switch (dtoa_mode) {
-    case DoubleToStringConverter::SHORTEST:  return BIGNUM_DTOA_SHORTEST;
-    case DoubleToStringConverter::SHORTEST_SINGLE:
-        return BIGNUM_DTOA_SHORTEST_SINGLE;
-    case DoubleToStringConverter::FIXED:     return BIGNUM_DTOA_FIXED;
-    case DoubleToStringConverter::PRECISION: return BIGNUM_DTOA_PRECISION;
-    default:
-      UNREACHABLE();
-  }
-}
-
-
-void DoubleToStringConverter::DoubleToAscii(double v,
-                                            DtoaMode mode,
-                                            int requested_digits,
-                                            char* buffer,
-                                            int buffer_length,
-                                            bool* sign,
-                                            int* length,
-                                            int* point) {
-  Vector<char> vector(buffer, buffer_length);
-  ASSERT(!Double(v).IsSpecial());
-  ASSERT(mode == SHORTEST || mode == SHORTEST_SINGLE || requested_digits >= 0);
-
-  if (Double(v).Sign() < 0) {
-    *sign = true;
-    v = -v;
-  } else {
-    *sign = false;
-  }
-
-  if (mode == PRECISION && requested_digits == 0) {
-    vector[0] = '\0';
-    *length = 0;
-    return;
-  }
-
-  if (v == 0) {
-    vector[0] = '0';
-    vector[1] = '\0';
-    *length = 1;
-    *point = 1;
-    return;
-  }
-
-  bool fast_worked;
-  switch (mode) {
-    case SHORTEST:
-      fast_worked = FastDtoa(v, FAST_DTOA_SHORTEST, 0, vector, length, point);
-      break;
-    case SHORTEST_SINGLE:
-      fast_worked = FastDtoa(v, FAST_DTOA_SHORTEST_SINGLE, 0,
-                             vector, length, point);
-      break;
-    case FIXED:
-      fast_worked = FastFixedDtoa(v, requested_digits, vector, length, point);
-      break;
-    case PRECISION:
-      fast_worked = FastDtoa(v, FAST_DTOA_PRECISION, requested_digits,
-                             vector, length, point);
-      break;
-    default:
-      fast_worked = false;
-      UNREACHABLE();
-  }
-  if (fast_worked) return;
-
-  // If the fast dtoa didn't succeed use the slower bignum version.
-  BignumDtoaMode bignum_mode = DtoaToBignumDtoaMode(mode);
-  BignumDtoa(v, bignum_mode, requested_digits, vector, length, point);
-  vector[*length] = '\0';
-}
-
-
-namespace {
-
-inline char ToLower(char ch) {
-  static const std::ctype<char>& cType =
-      std::use_facet<std::ctype<char> >(std::locale::classic());
-  return cType.tolower(ch);
-}
-
-inline char Pass(char ch) {
-  return ch;
-}
-
-template <class Iterator, class Converter>
-static inline bool ConsumeSubStringImpl(Iterator* current,
-                                        Iterator end,
-                                        const char* substring,
-                                        Converter converter) {
-  ASSERT(converter(**current) == *substring);
-  for (substring++; *substring != '\0'; substring++) {
-    ++*current;
-    if (*current == end || converter(**current) != *substring) {
-      return false;
-    }
-  }
-  ++*current;
-  return true;
-}
-
-// Consumes the given substring from the iterator.
-// Returns false, if the substring does not match.
-template <class Iterator>
-static bool ConsumeSubString(Iterator* current,
-                             Iterator end,
-                             const char* substring,
-                             bool allow_case_insensibility) {
-  if (allow_case_insensibility) {
-    return ConsumeSubStringImpl(current, end, substring, ToLower);
-  } else {
-    return ConsumeSubStringImpl(current, end, substring, Pass);
-  }
-}
-
-// Consumes first character of the str is equal to ch
-inline bool ConsumeFirstCharacter(char ch,
-                                         const char* str,
-                                         bool case_insensibility) {
-  return case_insensibility ? ToLower(ch) == str[0] : ch == str[0];
-}
-}  // namespace
-
-// Maximum number of significant digits in decimal representation.
-// The longest possible double in decimal representation is
-// (2^53 - 1) * 2 ^ -1074 that is (2 ^ 53 - 1) * 5 ^ 1074 / 10 ^ 1074
-// (768 digits). If we parse a number whose first digits are equal to a
-// mean of 2 adjacent doubles (that could have up to 769 digits) the result
-// must be rounded to the bigger one unless the tail consists of zeros, so
-// we don't need to preserve all the digits.
-const int kMaxSignificantDigits = 772;
-
-
-static const char kWhitespaceTable7[] = { 32, 13, 10, 9, 11, 12 };
-static const int kWhitespaceTable7Length = ARRAY_SIZE(kWhitespaceTable7);
-
-
-static const uc16 kWhitespaceTable16[] = {
-  160, 8232, 8233, 5760, 6158, 8192, 8193, 8194, 8195,
-  8196, 8197, 8198, 8199, 8200, 8201, 8202, 8239, 8287, 12288, 65279
-};
-static const int kWhitespaceTable16Length = ARRAY_SIZE(kWhitespaceTable16);
-
-
-static bool isWhitespace(int x) {
-  if (x < 128) {
-    for (int i = 0; i < kWhitespaceTable7Length; i++) {
-      if (kWhitespaceTable7[i] == x) return true;
-    }
-  } else {
-    for (int i = 0; i < kWhitespaceTable16Length; i++) {
-      if (kWhitespaceTable16[i] == x) return true;
-    }
-  }
-  return false;
-}
-
-
-// Returns true if a nonspace found and false if the end has reached.
-template <class Iterator>
-static inline bool AdvanceToNonspace(Iterator* current, Iterator end) {
-  while (*current != end) {
-    if (!isWhitespace(**current)) return true;
-    ++*current;
-  }
-  return false;
-}
-
-
-static bool isDigit(int x, int radix) {
-  return (x >= '0' && x <= '9' && x < '0' + radix)
-      || (radix > 10 && x >= 'a' && x < 'a' + radix - 10)
-      || (radix > 10 && x >= 'A' && x < 'A' + radix - 10);
-}
-
-
-static double SignedZero(bool sign) {
-  return sign ? -0.0 : 0.0;
-}
-
-
-// Returns true if 'c' is a decimal digit that is valid for the given radix.
-//
-// The function is small and could be inlined, but VS2012 emitted a warning
-// because it constant-propagated the radix and concluded that the last
-// condition was always true. By moving it into a separate function the
-// compiler wouldn't warn anymore.
-#ifdef _MSC_VER
-#pragma optimize("",off)
-static bool IsDecimalDigitForRadix(int c, int radix) {
-  return '0' <= c && c <= '9' && (c - '0') < radix;
-}
-#pragma optimize("",on)
-#else
-static bool inline IsDecimalDigitForRadix(int c, int radix) {
-  return '0' <= c && c <= '9' && (c - '0') < radix;
-}
-#endif
-// Returns true if 'c' is a character digit that is valid for the given radix.
-// The 'a_character' should be 'a' or 'A'.
-//
-// The function is small and could be inlined, but VS2012 emitted a warning
-// because it constant-propagated the radix and concluded that the first
-// condition was always false. By moving it into a separate function the
-// compiler wouldn't warn anymore.
-static bool IsCharacterDigitForRadix(int c, int radix, char a_character) {
-  return radix > 10 && c >= a_character && c < a_character + radix - 10;
-}
-
-// Returns true, when the iterator is equal to end.
-template<class Iterator>
-static bool Advance (Iterator* it, uc16 separator, int base, Iterator& end) {
-  if (separator == StringToDoubleConverter::kNoSeparator) {
-    ++(*it);
-    return *it == end;
-  }
-  if (!isDigit(**it, base)) {
-    ++(*it);
-    return *it == end;
-  }
-  ++(*it);
-  if (*it == end) return true;
-  if (*it + 1 == end) return false;
-  if (**it == separator && isDigit(*(*it + 1), base)) {
-    ++(*it);
-  }
-  return *it == end;
-}
-
-// Checks whether the string in the range start-end is a hex-float string.
-// This function assumes that the leading '0x'/'0X' is already consumed.
-//
-// Hex float strings are of one of the following forms:
-//   - hex_digits+ 'p' ('+'|'-')? exponent_digits+
-//   - hex_digits* '.' hex_digits+ 'p' ('+'|'-')? exponent_digits+
-//   - hex_digits+ '.' 'p' ('+'|'-')? exponent_digits+
-template<class Iterator>
-static bool IsHexFloatString(Iterator start,
-                             Iterator end,
-                             uc16 separator,
-                             bool allow_trailing_junk) {
-  ASSERT(start != end);
-
-  Iterator current = start;
-
-  bool saw_digit = false;
-  while (isDigit(*current, 16)) {
-    saw_digit = true;
-    if (Advance(&current, separator, 16, end)) return false;
-  }
-  if (*current == '.') {
-    if (Advance(&current, separator, 16, end)) return false;
-    while (isDigit(*current, 16)) {
-      saw_digit = true;
-      if (Advance(&current, separator, 16, end)) return false;
-    }
-    if (!saw_digit) return false;  // Only the '.', but no digits.
-  }
-  if (*current != 'p' && *current != 'P') return false;
-  if (Advance(&current, separator, 16, end)) return false;
-  if (*current == '+' || *current == '-') {
-    if (Advance(&current, separator, 16, end)) return false;
-  }
-  if (!isDigit(*current, 10)) return false;
-  if (Advance(&current, separator, 16, end)) return true;
-  while (isDigit(*current, 10)) {
-    if (Advance(&current, separator, 16, end)) return true;
-  }
-  return allow_trailing_junk || !AdvanceToNonspace(&current, end);
-}
-
-
-// Parsing integers with radix 2, 4, 8, 16, 32. Assumes current != end.
-//
-// If parse_as_hex_float is true, then the string must be a valid
-// hex-float.
-template <int radix_log_2, class Iterator>
-static double RadixStringToIeee(Iterator* current,
-                                Iterator end,
-                                bool sign,
-                                uc16 separator,
-                                bool parse_as_hex_float,
-                                bool allow_trailing_junk,
-                                double junk_string_value,
-                                bool read_as_double,
-                                bool* result_is_junk) {
-  ASSERT(*current != end);
-  ASSERT(!parse_as_hex_float ||
-      IsHexFloatString(*current, end, separator, allow_trailing_junk));
-
-  const int kDoubleSize = Double::kSignificandSize;
-  const int kSingleSize = Single::kSignificandSize;
-  const int kSignificandSize = read_as_double? kDoubleSize: kSingleSize;
-
-  *result_is_junk = true;
-
-  int64_t number = 0;
-  int exponent = 0;
-  const int radix = (1 << radix_log_2);
-  // Whether we have encountered a '.' and are parsing the decimal digits.
-  // Only relevant if parse_as_hex_float is true.
-  bool post_decimal = false;
-
-  // Skip leading 0s.
-  while (**current == '0') {
-    if (Advance(current, separator, radix, end)) {
-      *result_is_junk = false;
-      return SignedZero(sign);
-    }
-  }
-
-  while (true) {
-    int digit;
-    if (IsDecimalDigitForRadix(**current, radix)) {
-      digit = static_cast<char>(**current) - '0';
-      if (post_decimal) exponent -= radix_log_2;
-    } else if (IsCharacterDigitForRadix(**current, radix, 'a')) {
-      digit = static_cast<char>(**current) - 'a' + 10;
-      if (post_decimal) exponent -= radix_log_2;
-    } else if (IsCharacterDigitForRadix(**current, radix, 'A')) {
-      digit = static_cast<char>(**current) - 'A' + 10;
-      if (post_decimal) exponent -= radix_log_2;
-    } else if (parse_as_hex_float && **current == '.') {
-      post_decimal = true;
-      Advance(current, separator, radix, end);
-      ASSERT(*current != end);
-      continue;
-    } else if (parse_as_hex_float && (**current == 'p' || **current == 'P')) {
-      break;
-    } else {
-      if (allow_trailing_junk || !AdvanceToNonspace(current, end)) {
-        break;
-      } else {
-        return junk_string_value;
-      }
-    }
-
-    number = number * radix + digit;
-    int overflow = static_cast<int>(number >> kSignificandSize);
-    if (overflow != 0) {
-      // Overflow occurred. Need to determine which direction to round the
-      // result.
-      int overflow_bits_count = 1;
-      while (overflow > 1) {
-        overflow_bits_count++;
-        overflow >>= 1;
-      }
-
-      int dropped_bits_mask = ((1 << overflow_bits_count) - 1);
-      int dropped_bits = static_cast<int>(number) & dropped_bits_mask;
-      number >>= overflow_bits_count;
-      exponent += overflow_bits_count;
-
-      bool zero_tail = true;
-      for (;;) {
-        if (Advance(current, separator, radix, end)) break;
-        if (parse_as_hex_float && **current == '.') {
-          // Just run over the '.'. We are just trying to see whether there is
-          // a non-zero digit somewhere.
-          Advance(current, separator, radix, end);
-          ASSERT(*current != end);
-          post_decimal = true;
-        }
-        if (!isDigit(**current, radix)) break;
-        zero_tail = zero_tail && **current == '0';
-        if (!post_decimal) exponent += radix_log_2;
-      }
-
-      if (!parse_as_hex_float &&
-          !allow_trailing_junk &&
-          AdvanceToNonspace(current, end)) {
-        return junk_string_value;
-      }
-
-      int middle_value = (1 << (overflow_bits_count - 1));
-      if (dropped_bits > middle_value) {
-        number++;  // Rounding up.
-      } else if (dropped_bits == middle_value) {
-        // Rounding to even to consistency with decimals: half-way case rounds
-        // up if significant part is odd and down otherwise.
-        if ((number & 1) != 0 || !zero_tail) {
-          number++;  // Rounding up.
-        }
-      }
-
-      // Rounding up may cause overflow.
-      if ((number & ((int64_t)1 << kSignificandSize)) != 0) {
-        exponent++;
-        number >>= 1;
-      }
-      break;
-    }
-    if (Advance(current, separator, radix, end)) break;
-  }
-
-  ASSERT(number < ((int64_t)1 << kSignificandSize));
-  ASSERT(static_cast<int64_t>(static_cast<double>(number)) == number);
-
-  *result_is_junk = false;
-
-  if (parse_as_hex_float) {
-    ASSERT(**current == 'p' || **current == 'P');
-    Advance(current, separator, radix, end);
-    ASSERT(*current != end);
-    bool is_negative = false;
-    if (**current == '+') {
-      Advance(current, separator, radix, end);
-      ASSERT(*current != end);
-    } else if (**current == '-') {
-      is_negative = true;
-      Advance(current, separator, radix, end);
-      ASSERT(*current != end);
-    }
-    int written_exponent = 0;
-    while (IsDecimalDigitForRadix(**current, 10)) {
-      written_exponent = 10 * written_exponent + **current - '0';
-      if (Advance(current, separator, radix, end)) break;
-    }
-    if (is_negative) written_exponent = -written_exponent;
-    exponent += written_exponent;
-  }
-
-  if (exponent == 0 || number == 0) {
-    if (sign) {
-      if (number == 0) return -0.0;
-      number = -number;
-    }
-    return static_cast<double>(number);
-  }
-
-  ASSERT(number != 0);
-  double result = Double(DiyFp(number, exponent)).value();
-  return sign ? -result : result;
-}
-
-template <class Iterator>
-double StringToDoubleConverter::StringToIeee(
-    Iterator input,
-    int length,
-    bool read_as_double,
-    int* processed_characters_count) const {
-  Iterator current = input;
-  Iterator end = input + length;
-
-  *processed_characters_count = 0;
-
-  const bool allow_trailing_junk = (flags_ & ALLOW_TRAILING_JUNK) != 0;
-  const bool allow_leading_spaces = (flags_ & ALLOW_LEADING_SPACES) != 0;
-  const bool allow_trailing_spaces = (flags_ & ALLOW_TRAILING_SPACES) != 0;
-  const bool allow_spaces_after_sign = (flags_ & ALLOW_SPACES_AFTER_SIGN) != 0;
-  const bool allow_case_insensibility = (flags_ & ALLOW_CASE_INSENSIBILITY) != 0;
-
-  // To make sure that iterator dereferencing is valid the following
-  // convention is used:
-  // 1. Each '++current' statement is followed by check for equality to 'end'.
-  // 2. If AdvanceToNonspace returned false then current == end.
-  // 3. If 'current' becomes equal to 'end' the function returns or goes to
-  // 'parsing_done'.
-  // 4. 'current' is not dereferenced after the 'parsing_done' label.
-  // 5. Code before 'parsing_done' may rely on 'current != end'.
-  if (current == end) return empty_string_value_;
-
-  if (allow_leading_spaces || allow_trailing_spaces) {
-    if (!AdvanceToNonspace(&current, end)) {
-      *processed_characters_count = static_cast<int>(current - input);
-      return empty_string_value_;
-    }
-    if (!allow_leading_spaces && (input != current)) {
-      // No leading spaces allowed, but AdvanceToNonspace moved forward.
-      return junk_string_value_;
-    }
-  }
-
-  // The longest form of simplified number is: "-<significant digits>.1eXXX\0".
-  const int kBufferSize = kMaxSignificantDigits + 10;
-  char buffer[kBufferSize];  // NOLINT: size is known at compile time.
-  int buffer_pos = 0;
-
-  // Exponent will be adjusted if insignificant digits of the integer part
-  // or insignificant leading zeros of the fractional part are dropped.
-  int exponent = 0;
-  int significant_digits = 0;
-  int insignificant_digits = 0;
-  bool nonzero_digit_dropped = false;
-
-  bool sign = false;
-
-  if (*current == '+' || *current == '-') {
-    sign = (*current == '-');
-    ++current;
-    Iterator next_non_space = current;
-    // Skip following spaces (if allowed).
-    if (!AdvanceToNonspace(&next_non_space, end)) return junk_string_value_;
-    if (!allow_spaces_after_sign && (current != next_non_space)) {
-      return junk_string_value_;
-    }
-    current = next_non_space;
-  }
-
-  if (infinity_symbol_ != NULL) {
-    if (ConsumeFirstCharacter(*current, infinity_symbol_, allow_case_insensibility)) {
-      if (!ConsumeSubString(&current, end, infinity_symbol_, allow_case_insensibility)) {
-        return junk_string_value_;
-      }
-
-      if (!(allow_trailing_spaces || allow_trailing_junk) && (current != end)) {
-        return junk_string_value_;
-      }
-      if (!allow_trailing_junk && AdvanceToNonspace(&current, end)) {
-        return junk_string_value_;
-      }
-
-      ASSERT(buffer_pos == 0);
-      *processed_characters_count = static_cast<int>(current - input);
-      return sign ? -Double::Infinity() : Double::Infinity();
-    }
-  }
-
-  if (nan_symbol_ != NULL) {
-    if (ConsumeFirstCharacter(*current, nan_symbol_, allow_case_insensibility)) {
-      if (!ConsumeSubString(&current, end, nan_symbol_, allow_case_insensibility)) {
-        return junk_string_value_;
-      }
-
-      if (!(allow_trailing_spaces || allow_trailing_junk) && (current != end)) {
-        return junk_string_value_;
-      }
-      if (!allow_trailing_junk && AdvanceToNonspace(&current, end)) {
-        return junk_string_value_;
-      }
-
-      ASSERT(buffer_pos == 0);
-      *processed_characters_count = static_cast<int>(current - input);
-      return sign ? -Double::NaN() : Double::NaN();
-    }
-  }
-
-  bool leading_zero = false;
-  if (*current == '0') {
-    if (Advance(&current, separator_, 10, end)) {
-      *processed_characters_count = static_cast<int>(current - input);
-      return SignedZero(sign);
-    }
-
-    leading_zero = true;
-
-    // It could be hexadecimal value.
-    if (((flags_ & ALLOW_HEX) || (flags_ & ALLOW_HEX_FLOATS)) &&
-        (*current == 'x' || *current == 'X')) {
-      ++current;
-
-      bool parse_as_hex_float = (flags_ & ALLOW_HEX_FLOATS) &&
-                IsHexFloatString(current, end, separator_, allow_trailing_junk);
-
-      if (current == end) return junk_string_value_;  // "0x"
-      if (!parse_as_hex_float && !isDigit(*current, 16)) {
-        return junk_string_value_;
-      }
-
-      bool result_is_junk;
-      double result = RadixStringToIeee<4>(&current,
-                                           end,
-                                           sign,
-                                           separator_,
-                                           parse_as_hex_float,
-                                           allow_trailing_junk,
-                                           junk_string_value_,
-                                           read_as_double,
-                                           &result_is_junk);
-      if (!result_is_junk) {
-        if (allow_trailing_spaces) AdvanceToNonspace(&current, end);
-        *processed_characters_count = static_cast<int>(current - input);
-      }
-      return result;
-    }
-
-    // Ignore leading zeros in the integer part.
-    while (*current == '0') {
-      if (Advance(&current, separator_, 10, end)) {
-        *processed_characters_count = static_cast<int>(current - input);
-        return SignedZero(sign);
-      }
-    }
-  }
-
-  bool octal = leading_zero && (flags_ & ALLOW_OCTALS) != 0;
-
-  // Copy significant digits of the integer part (if any) to the buffer.
-  while (*current >= '0' && *current <= '9') {
-    if (significant_digits < kMaxSignificantDigits) {
-      ASSERT(buffer_pos < kBufferSize);
-      buffer[buffer_pos++] = static_cast<char>(*current);
-      significant_digits++;
-      // Will later check if it's an octal in the buffer.
-    } else {
-      insignificant_digits++;  // Move the digit into the exponential part.
-      nonzero_digit_dropped = nonzero_digit_dropped || *current != '0';
-    }
-    octal = octal && *current < '8';
-    if (Advance(&current, separator_, 10, end)) goto parsing_done;
-  }
-
-  if (significant_digits == 0) {
-    octal = false;
-  }
-
-  if (*current == '.') {
-    if (octal && !allow_trailing_junk) return junk_string_value_;
-    if (octal) goto parsing_done;
-
-    if (Advance(&current, separator_, 10, end)) {
-      if (significant_digits == 0 && !leading_zero) {
-        return junk_string_value_;
-      } else {
-        goto parsing_done;
-      }
-    }
-
-    if (significant_digits == 0) {
-      // octal = false;
-      // Integer part consists of 0 or is absent. Significant digits start after
-      // leading zeros (if any).
-      while (*current == '0') {
-        if (Advance(&current, separator_, 10, end)) {
-          *processed_characters_count = static_cast<int>(current - input);
-          return SignedZero(sign);
-        }
-        exponent--;  // Move this 0 into the exponent.
-      }
-    }
-
-    // There is a fractional part.
-    // We don't emit a '.', but adjust the exponent instead.
-    while (*current >= '0' && *current <= '9') {
-      if (significant_digits < kMaxSignificantDigits) {
-        ASSERT(buffer_pos < kBufferSize);
-        buffer[buffer_pos++] = static_cast<char>(*current);
-        significant_digits++;
-        exponent--;
-      } else {
-        // Ignore insignificant digits in the fractional part.
-        nonzero_digit_dropped = nonzero_digit_dropped || *current != '0';
-      }
-      if (Advance(&current, separator_, 10, end)) goto parsing_done;
-    }
-  }
-
-  if (!leading_zero && exponent == 0 && significant_digits == 0) {
-    // If leading_zeros is true then the string contains zeros.
-    // If exponent < 0 then string was [+-]\.0*...
-    // If significant_digits != 0 the string is not equal to 0.
-    // Otherwise there are no digits in the string.
-    return junk_string_value_;
-  }
-
-  // Parse exponential part.
-  if (*current == 'e' || *current == 'E') {
-    if (octal && !allow_trailing_junk) return junk_string_value_;
-    if (octal) goto parsing_done;
-    Iterator junk_begin = current;
-    ++current;
-    if (current == end) {
-      if (allow_trailing_junk) {
-        current = junk_begin;
-        goto parsing_done;
-      } else {
-        return junk_string_value_;
-      }
-    }
-    char exponen_sign = '+';
-    if (*current == '+' || *current == '-') {
-      exponen_sign = static_cast<char>(*current);
-      ++current;
-      if (current == end) {
-        if (allow_trailing_junk) {
-          current = junk_begin;
-          goto parsing_done;
-        } else {
-          return junk_string_value_;
-        }
-      }
-    }
-
-    if (current == end || *current < '0' || *current > '9') {
-      if (allow_trailing_junk) {
-        current = junk_begin;
-        goto parsing_done;
-      } else {
-        return junk_string_value_;
-      }
-    }
-
-    const int max_exponent = INT_MAX / 2;
-    ASSERT(-max_exponent / 2 <= exponent && exponent <= max_exponent / 2);
-    int num = 0;
-    do {
-      // Check overflow.
-      int digit = *current - '0';
-      if (num >= max_exponent / 10
-          && !(num == max_exponent / 10 && digit <= max_exponent % 10)) {
-        num = max_exponent;
-      } else {
-        num = num * 10 + digit;
-      }
-      ++current;
-    } while (current != end && *current >= '0' && *current <= '9');
-
-    exponent += (exponen_sign == '-' ? -num : num);
-  }
-
-  if (!(allow_trailing_spaces || allow_trailing_junk) && (current != end)) {
-    return junk_string_value_;
-  }
-  if (!allow_trailing_junk && AdvanceToNonspace(&current, end)) {
-    return junk_string_value_;
-  }
-  if (allow_trailing_spaces) {
-    AdvanceToNonspace(&current, end);
-  }
-
-  parsing_done:
-  exponent += insignificant_digits;
-
-  if (octal) {
-    double result;
-    bool result_is_junk;
-    char* start = buffer;
-    result = RadixStringToIeee<3>(&start,
-                                  buffer + buffer_pos,
-                                  sign,
-                                  separator_,
-                                  false, // Don't parse as hex_float.
-                                  allow_trailing_junk,
-                                  junk_string_value_,
-                                  read_as_double,
-                                  &result_is_junk);
-    ASSERT(!result_is_junk);
-    *processed_characters_count = static_cast<int>(current - input);
-    return result;
-  }
-
-  if (nonzero_digit_dropped) {
-    buffer[buffer_pos++] = '1';
-    exponent--;
-  }
-
-  ASSERT(buffer_pos < kBufferSize);
-  buffer[buffer_pos] = '\0';
-
-  double converted;
-  if (read_as_double) {
-    converted = Strtod(Vector<const char>(buffer, buffer_pos), exponent);
-  } else {
-    converted = Strtof(Vector<const char>(buffer, buffer_pos), exponent);
-  }
-  *processed_characters_count = static_cast<int>(current - input);
-  return sign? -converted: converted;
-}
-
-
-double StringToDoubleConverter::StringToDouble(
-    const char* buffer,
-    int length,
-    int* processed_characters_count) const {
-  return StringToIeee(buffer, length, true, processed_characters_count);
-}
-
-
-double StringToDoubleConverter::StringToDouble(
-    const uc16* buffer,
-    int length,
-    int* processed_characters_count) const {
-  return StringToIeee(buffer, length, true, processed_characters_count);
-}
-
-
-float StringToDoubleConverter::StringToFloat(
-    const char* buffer,
-    int length,
-    int* processed_characters_count) const {
-  return static_cast<float>(StringToIeee(buffer, length, false,
-                                         processed_characters_count));
-}
-
-
-float StringToDoubleConverter::StringToFloat(
-    const uc16* buffer,
-    int length,
-    int* processed_characters_count) const {
-  return static_cast<float>(StringToIeee(buffer, length, false,
-                                         processed_characters_count));
-}
-
-}  // namespace double_conversion
diff --git a/c_src/double-conversion/double-conversion.h b/c_src/double-conversion/double-conversion.h
deleted file mode 100644
index 6dbc099..0000000
--- a/c_src/double-conversion/double-conversion.h
+++ /dev/null
@@ -1,576 +0,0 @@
-// Copyright 2012 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef DOUBLE_CONVERSION_DOUBLE_CONVERSION_H_
-#define DOUBLE_CONVERSION_DOUBLE_CONVERSION_H_
-
-#include "utils.h"
-
-namespace double_conversion {
-
-class DoubleToStringConverter {
- public:
-  // When calling ToFixed with a double > 10^kMaxFixedDigitsBeforePoint
-  // or a requested_digits parameter > kMaxFixedDigitsAfterPoint then the
-  // function returns false.
-  static const int kMaxFixedDigitsBeforePoint = 60;
-  static const int kMaxFixedDigitsAfterPoint = 60;
-
-  // When calling ToExponential with a requested_digits
-  // parameter > kMaxExponentialDigits then the function returns false.
-  static const int kMaxExponentialDigits = 120;
-
-  // When calling ToPrecision with a requested_digits
-  // parameter < kMinPrecisionDigits or requested_digits > kMaxPrecisionDigits
-  // then the function returns false.
-  static const int kMinPrecisionDigits = 1;
-  static const int kMaxPrecisionDigits = 120;
-
-  enum Flags {
-    NO_FLAGS = 0,
-    EMIT_POSITIVE_EXPONENT_SIGN = 1,
-    EMIT_TRAILING_DECIMAL_POINT = 2,
-    EMIT_TRAILING_ZERO_AFTER_POINT = 4,
-    UNIQUE_ZERO = 8
-  };
-
-  // Flags should be a bit-or combination of the possible Flags-enum.
-  //  - NO_FLAGS: no special flags.
-  //  - EMIT_POSITIVE_EXPONENT_SIGN: when the number is converted into exponent
-  //    form, emits a '+' for positive exponents. Example: 1.2e+2.
-  //  - EMIT_TRAILING_DECIMAL_POINT: when the input number is an integer and is
-  //    converted into decimal format then a trailing decimal point is appended.
-  //    Example: 2345.0 is converted to "2345.".
-  //  - EMIT_TRAILING_ZERO_AFTER_POINT: in addition to a trailing decimal point
-  //    emits a trailing '0'-character. This flag requires the
-  //    EXMIT_TRAILING_DECIMAL_POINT flag.
-  //    Example: 2345.0 is converted to "2345.0".
-  //  - UNIQUE_ZERO: "-0.0" is converted to "0.0".
-  //
-  // Infinity symbol and nan_symbol provide the string representation for these
-  // special values. If the string is NULL and the special value is encountered
-  // then the conversion functions return false.
-  //
-  // The exponent_character is used in exponential representations. It is
-  // usually 'e' or 'E'.
-  //
-  // When converting to the shortest representation the converter will
-  // represent input numbers in decimal format if they are in the interval
-  // [10^decimal_in_shortest_low; 10^decimal_in_shortest_high[
-  //    (lower boundary included, greater boundary excluded).
-  // Example: with decimal_in_shortest_low = -6 and
-  //               decimal_in_shortest_high = 21:
-  //   ToShortest(0.000001)  -> "0.000001"
-  //   ToShortest(0.0000001) -> "1e-7"
-  //   ToShortest(111111111111111111111.0)  -> "111111111111111110000"
-  //   ToShortest(100000000000000000000.0)  -> "100000000000000000000"
-  //   ToShortest(1111111111111111111111.0) -> "1.1111111111111111e+21"
-  //
-  // When converting to precision mode the converter may add
-  // max_leading_padding_zeroes before returning the number in exponential
-  // format.
-  // Example with max_leading_padding_zeroes_in_precision_mode = 6.
-  //   ToPrecision(0.0000012345, 2) -> "0.0000012"
-  //   ToPrecision(0.00000012345, 2) -> "1.2e-7"
-  // Similarily the converter may add up to
-  // max_trailing_padding_zeroes_in_precision_mode in precision mode to avoid
-  // returning an exponential representation. A zero added by the
-  // EMIT_TRAILING_ZERO_AFTER_POINT flag is counted for this limit.
-  // Examples for max_trailing_padding_zeroes_in_precision_mode = 1:
-  //   ToPrecision(230.0, 2) -> "230"
-  //   ToPrecision(230.0, 2) -> "230."  with EMIT_TRAILING_DECIMAL_POINT.
-  //   ToPrecision(230.0, 2) -> "2.3e2" with EMIT_TRAILING_ZERO_AFTER_POINT.
-  DoubleToStringConverter(int flags,
-                          const char* infinity_symbol,
-                          const char* nan_symbol,
-                          char exponent_character,
-                          int decimal_in_shortest_low,
-                          int decimal_in_shortest_high,
-                          int max_leading_padding_zeroes_in_precision_mode,
-                          int max_trailing_padding_zeroes_in_precision_mode)
-      : flags_(flags),
-        infinity_symbol_(infinity_symbol),
-        nan_symbol_(nan_symbol),
-        exponent_character_(exponent_character),
-        decimal_in_shortest_low_(decimal_in_shortest_low),
-        decimal_in_shortest_high_(decimal_in_shortest_high),
-        max_leading_padding_zeroes_in_precision_mode_(
-            max_leading_padding_zeroes_in_precision_mode),
-        max_trailing_padding_zeroes_in_precision_mode_(
-            max_trailing_padding_zeroes_in_precision_mode) {
-    // When 'trailing zero after the point' is set, then 'trailing point'
-    // must be set too.
-    ASSERT(((flags & EMIT_TRAILING_DECIMAL_POINT) != 0) ||
-        !((flags & EMIT_TRAILING_ZERO_AFTER_POINT) != 0));
-  }
-
-  // Returns a converter following the EcmaScript specification.
-  static const DoubleToStringConverter& EcmaScriptConverter();
-
-  // Computes the shortest string of digits that correctly represent the input
-  // number. Depending on decimal_in_shortest_low and decimal_in_shortest_high
-  // (see constructor) it then either returns a decimal representation, or an
-  // exponential representation.
-  // Example with decimal_in_shortest_low = -6,
-  //              decimal_in_shortest_high = 21,
-  //              EMIT_POSITIVE_EXPONENT_SIGN activated, and
-  //              EMIT_TRAILING_DECIMAL_POINT deactived:
-  //   ToShortest(0.000001)  -> "0.000001"
-  //   ToShortest(0.0000001) -> "1e-7"
-  //   ToShortest(111111111111111111111.0)  -> "111111111111111110000"
-  //   ToShortest(100000000000000000000.0)  -> "100000000000000000000"
-  //   ToShortest(1111111111111111111111.0) -> "1.1111111111111111e+21"
-  //
-  // Note: the conversion may round the output if the returned string
-  // is accurate enough to uniquely identify the input-number.
-  // For example the most precise representation of the double 9e59 equals
-  // "899999999999999918767229449717619953810131273674690656206848", but
-  // the converter will return the shorter (but still correct) "9e59".
-  //
-  // Returns true if the conversion succeeds. The conversion always succeeds
-  // except when the input value is special and no infinity_symbol or
-  // nan_symbol has been given to the constructor.
-  bool ToShortest(double value, StringBuilder* result_builder) const {
-    return ToShortestIeeeNumber(value, result_builder, SHORTEST);
-  }
-
-  // Same as ToShortest, but for single-precision floats.
-  bool ToShortestSingle(float value, StringBuilder* result_builder) const {
-    return ToShortestIeeeNumber(value, result_builder, SHORTEST_SINGLE);
-  }
-
-
-  // Computes a decimal representation with a fixed number of digits after the
-  // decimal point. The last emitted digit is rounded.
-  //
-  // Examples:
-  //   ToFixed(3.12, 1) -> "3.1"
-  //   ToFixed(3.1415, 3) -> "3.142"
-  //   ToFixed(1234.56789, 4) -> "1234.5679"
-  //   ToFixed(1.23, 5) -> "1.23000"
-  //   ToFixed(0.1, 4) -> "0.1000"
-  //   ToFixed(1e30, 2) -> "1000000000000000019884624838656.00"
-  //   ToFixed(0.1, 30) -> "0.100000000000000005551115123126"
-  //   ToFixed(0.1, 17) -> "0.10000000000000001"
-  //
-  // If requested_digits equals 0, then the tail of the result depends on
-  // the EMIT_TRAILING_DECIMAL_POINT and EMIT_TRAILING_ZERO_AFTER_POINT.
-  // Examples, for requested_digits == 0,
-  //   let EMIT_TRAILING_DECIMAL_POINT and EMIT_TRAILING_ZERO_AFTER_POINT be
-  //    - false and false: then 123.45 -> 123
-  //                             0.678 -> 1
-  //    - true and false: then 123.45 -> 123.
-  //                            0.678 -> 1.
-  //    - true and true: then 123.45 -> 123.0
-  //                           0.678 -> 1.0
-  //
-  // Returns true if the conversion succeeds. The conversion always succeeds
-  // except for the following cases:
-  //   - the input value is special and no infinity_symbol or nan_symbol has
-  //     been provided to the constructor,
-  //   - 'value' > 10^kMaxFixedDigitsBeforePoint, or
-  //   - 'requested_digits' > kMaxFixedDigitsAfterPoint.
-  // The last two conditions imply that the result will never contain more than
-  // 1 + kMaxFixedDigitsBeforePoint + 1 + kMaxFixedDigitsAfterPoint characters
-  // (one additional character for the sign, and one for the decimal point).
-  bool ToFixed(double value,
-               int requested_digits,
-               StringBuilder* result_builder) const;
-
-  // Computes a representation in exponential format with requested_digits
-  // after the decimal point. The last emitted digit is rounded.
-  // If requested_digits equals -1, then the shortest exponential representation
-  // is computed.
-  //
-  // Examples with EMIT_POSITIVE_EXPONENT_SIGN deactivated, and
-  //               exponent_character set to 'e'.
-  //   ToExponential(3.12, 1) -> "3.1e0"
-  //   ToExponential(5.0, 3) -> "5.000e0"
-  //   ToExponential(0.001, 2) -> "1.00e-3"
-  //   ToExponential(3.1415, -1) -> "3.1415e0"
-  //   ToExponential(3.1415, 4) -> "3.1415e0"
-  //   ToExponential(3.1415, 3) -> "3.142e0"
-  //   ToExponential(123456789000000, 3) -> "1.235e14"
-  //   ToExponential(1000000000000000019884624838656.0, -1) -> "1e30"
-  //   ToExponential(1000000000000000019884624838656.0, 32) ->
-  //                     "1.00000000000000001988462483865600e30"
-  //   ToExponential(1234, 0) -> "1e3"
-  //
-  // Returns true if the conversion succeeds. The conversion always succeeds
-  // except for the following cases:
-  //   - the input value is special and no infinity_symbol or nan_symbol has
-  //     been provided to the constructor,
-  //   - 'requested_digits' > kMaxExponentialDigits.
-  // The last condition implies that the result will never contain more than
-  // kMaxExponentialDigits + 8 characters (the sign, the digit before the
-  // decimal point, the decimal point, the exponent character, the
-  // exponent's sign, and at most 3 exponent digits).
-  bool ToExponential(double value,
-                     int requested_digits,
-                     StringBuilder* result_builder) const;
-
-  // Computes 'precision' leading digits of the given 'value' and returns them
-  // either in exponential or decimal format, depending on
-  // max_{leading|trailing}_padding_zeroes_in_precision_mode (given to the
-  // constructor).
-  // The last computed digit is rounded.
-  //
-  // Example with max_leading_padding_zeroes_in_precision_mode = 6.
-  //   ToPrecision(0.0000012345, 2) -> "0.0000012"
-  //   ToPrecision(0.00000012345, 2) -> "1.2e-7"
-  // Similarily the converter may add up to
-  // max_trailing_padding_zeroes_in_precision_mode in precision mode to avoid
-  // returning an exponential representation. A zero added by the
-  // EMIT_TRAILING_ZERO_AFTER_POINT flag is counted for this limit.
-  // Examples for max_trailing_padding_zeroes_in_precision_mode = 1:
-  //   ToPrecision(230.0, 2) -> "230"
-  //   ToPrecision(230.0, 2) -> "230."  with EMIT_TRAILING_DECIMAL_POINT.
-  //   ToPrecision(230.0, 2) -> "2.3e2" with EMIT_TRAILING_ZERO_AFTER_POINT.
-  // Examples for max_trailing_padding_zeroes_in_precision_mode = 3, and no
-  //    EMIT_TRAILING_ZERO_AFTER_POINT:
-  //   ToPrecision(123450.0, 6) -> "123450"
-  //   ToPrecision(123450.0, 5) -> "123450"
-  //   ToPrecision(123450.0, 4) -> "123500"
-  //   ToPrecision(123450.0, 3) -> "123000"
-  //   ToPrecision(123450.0, 2) -> "1.2e5"
-  //
-  // Returns true if the conversion succeeds. The conversion always succeeds
-  // except for the following cases:
-  //   - the input value is special and no infinity_symbol or nan_symbol has
-  //     been provided to the constructor,
-  //   - precision < kMinPericisionDigits
-  //   - precision > kMaxPrecisionDigits
-  // The last condition implies that the result will never contain more than
-  // kMaxPrecisionDigits + 7 characters (the sign, the decimal point, the
-  // exponent character, the exponent's sign, and at most 3 exponent digits).
-  bool ToPrecision(double value,
-                   int precision,
-                   StringBuilder* result_builder) const;
-
-  enum DtoaMode {
-    // Produce the shortest correct representation.
-    // For example the output of 0.299999999999999988897 is (the less accurate
-    // but correct) 0.3.
-    SHORTEST,
-    // Same as SHORTEST, but for single-precision floats.
-    SHORTEST_SINGLE,
-    // Produce a fixed number of digits after the decimal point.
-    // For instance fixed(0.1, 4) becomes 0.1000
-    // If the input number is big, the output will be big.
-    FIXED,
-    // Fixed number of digits (independent of the decimal point).
-    PRECISION
-  };
-
-  // The maximal number of digits that are needed to emit a double in base 10.
-  // A higher precision can be achieved by using more digits, but the shortest
-  // accurate representation of any double will never use more digits than
-  // kBase10MaximalLength.
-  // Note that DoubleToAscii null-terminates its input. So the given buffer
-  // should be at least kBase10MaximalLength + 1 characters long.
-  static const int kBase10MaximalLength = 17;
-
-  // Converts the given double 'v' to digit characters. 'v' must not be NaN,
-  // +Infinity, or -Infinity. In SHORTEST_SINGLE-mode this restriction also
-  // applies to 'v' after it has been casted to a single-precision float. That
-  // is, in this mode static_cast<float>(v) must not be NaN, +Infinity or
-  // -Infinity.
-  //
-  // The result should be interpreted as buffer * 10^(point-length).
-  //
-  // The digits are written to the buffer in the platform's charset, which is
-  // often UTF-8 (with ASCII-range digits) but may be another charset, such
-  // as EBCDIC.
-  //
-  // The output depends on the given mode:
-  //  - SHORTEST: produce the least amount of digits for which the internal
-  //   identity requirement is still satisfied. If the digits are printed
-  //   (together with the correct exponent) then reading this number will give
-  //   'v' again. The buffer will choose the representation that is closest to
-  //   'v'. If there are two at the same distance, than the one farther away
-  //   from 0 is chosen (halfway cases - ending with 5 - are rounded up).
-  //   In this mode the 'requested_digits' parameter is ignored.
-  //  - SHORTEST_SINGLE: same as SHORTEST but with single-precision.
-  //  - FIXED: produces digits necessary to print a given number with
-  //   'requested_digits' digits after the decimal point. The produced digits
-  //   might be too short in which case the caller has to fill the remainder
-  //   with '0's.
-  //   Example: toFixed(0.001, 5) is allowed to return buffer="1", point=-2.
-  //   Halfway cases are rounded towards +/-Infinity (away from 0). The call
-  //   toFixed(0.15, 2) thus returns buffer="2", point=0.
-  //   The returned buffer may contain digits that would be truncated from the
-  //   shortest representation of the input.
-  //  - PRECISION: produces 'requested_digits' where the first digit is not '0'.
-  //   Even though the length of produced digits usually equals
-  //   'requested_digits', the function is allowed to return fewer digits, in
-  //   which case the caller has to fill the missing digits with '0's.
-  //   Halfway cases are again rounded away from 0.
-  // DoubleToAscii expects the given buffer to be big enough to hold all
-  // digits and a terminating null-character. In SHORTEST-mode it expects a
-  // buffer of at least kBase10MaximalLength + 1. In all other modes the
-  // requested_digits parameter and the padding-zeroes limit the size of the
-  // output. Don't forget the decimal point, the exponent character and the
-  // terminating null-character when computing the maximal output size.
-  // The given length is only used in debug mode to ensure the buffer is big
-  // enough.
-  static void DoubleToAscii(double v,
-                            DtoaMode mode,
-                            int requested_digits,
-                            char* buffer,
-                            int buffer_length,
-                            bool* sign,
-                            int* length,
-                            int* point);
-
- private:
-  // Implementation for ToShortest and ToShortestSingle.
-  bool ToShortestIeeeNumber(double value,
-                            StringBuilder* result_builder,
-                            DtoaMode mode) const;
-
-  // If the value is a special value (NaN or Infinity) constructs the
-  // corresponding string using the configured infinity/nan-symbol.
-  // If either of them is NULL or the value is not special then the
-  // function returns false.
-  bool HandleSpecialValues(double value, StringBuilder* result_builder) const;
-  // Constructs an exponential representation (i.e. 1.234e56).
-  // The given exponent assumes a decimal point after the first decimal digit.
-  void CreateExponentialRepresentation(const char* decimal_digits,
-                                       int length,
-                                       int exponent,
-                                       StringBuilder* result_builder) const;
-  // Creates a decimal representation (i.e 1234.5678).
-  void CreateDecimalRepresentation(const char* decimal_digits,
-                                   int length,
-                                   int decimal_point,
-                                   int digits_after_point,
-                                   StringBuilder* result_builder) const;
-
-  const int flags_;
-  const char* const infinity_symbol_;
-  const char* const nan_symbol_;
-  const char exponent_character_;
-  const int decimal_in_shortest_low_;
-  const int decimal_in_shortest_high_;
-  const int max_leading_padding_zeroes_in_precision_mode_;
-  const int max_trailing_padding_zeroes_in_precision_mode_;
-
-  DC_DISALLOW_IMPLICIT_CONSTRUCTORS(DoubleToStringConverter);
-};
-
-
-class StringToDoubleConverter {
- public:
-  // Enumeration for allowing octals and ignoring junk when converting
-  // strings to numbers.
-  enum Flags {
-    NO_FLAGS = 0,
-    ALLOW_HEX = 1,
-    ALLOW_OCTALS = 2,
-    ALLOW_TRAILING_JUNK = 4,
-    ALLOW_LEADING_SPACES = 8,
-    ALLOW_TRAILING_SPACES = 16,
-    ALLOW_SPACES_AFTER_SIGN = 32,
-    ALLOW_CASE_INSENSIBILITY = 64,
-    ALLOW_HEX_FLOATS = 128,
-  };
-
-  static const uc16 kNoSeparator = '\0';
-
-  // Flags should be a bit-or combination of the possible Flags-enum.
-  //  - NO_FLAGS: no special flags.
-  //  - ALLOW_HEX: recognizes the prefix "0x". Hex numbers may only be integers.
-  //      Ex: StringToDouble("0x1234") -> 4660.0
-  //          In StringToDouble("0x1234.56") the characters ".56" are trailing
-  //          junk. The result of the call is hence dependent on
-  //          the ALLOW_TRAILING_JUNK flag and/or the junk value.
-  //      With this flag "0x" is a junk-string. Even with ALLOW_TRAILING_JUNK,
-  //      the string will not be parsed as "0" followed by junk.
-  //
-  //  - ALLOW_OCTALS: recognizes the prefix "0" for octals:
-  //      If a sequence of octal digits starts with '0', then the number is
-  //      read as octal integer. Octal numbers may only be integers.
-  //      Ex: StringToDouble("01234") -> 668.0
-  //          StringToDouble("012349") -> 12349.0  // Not a sequence of octal
-  //                                               // digits.
-  //          In StringToDouble("01234.56") the characters ".56" are trailing
-  //          junk. The result of the call is hence dependent on
-  //          the ALLOW_TRAILING_JUNK flag and/or the junk value.
-  //          In StringToDouble("01234e56") the characters "e56" are trailing
-  //          junk, too.
-  //  - ALLOW_TRAILING_JUNK: ignore trailing characters that are not part of
-  //      a double literal.
-  //  - ALLOW_LEADING_SPACES: skip over leading whitespace, including spaces,
-  //                          new-lines, and tabs.
-  //  - ALLOW_TRAILING_SPACES: ignore trailing whitespace.
-  //  - ALLOW_SPACES_AFTER_SIGN: ignore whitespace after the sign.
-  //       Ex: StringToDouble("-   123.2") -> -123.2.
-  //           StringToDouble("+   123.2") -> 123.2
-  //  - ALLOW_CASE_INSENSIBILITY: ignore case of characters for special values:
-  //      infinity and nan.
-  //  - ALLOW_HEX_FLOATS: allows hexadecimal float literals.
-  //      This *must* start with "0x" and separate the exponent with "p".
-  //      Examples: 0x1.2p3 == 9.0
-  //                0x10.1p0 == 16.0625
-  //      ALLOW_HEX and ALLOW_HEX_FLOATS are indendent.
-  //
-  // empty_string_value is returned when an empty string is given as input.
-  // If ALLOW_LEADING_SPACES or ALLOW_TRAILING_SPACES are set, then a string
-  // containing only spaces is converted to the 'empty_string_value', too.
-  //
-  // junk_string_value is returned when
-  //  a) ALLOW_TRAILING_JUNK is not set, and a junk character (a character not
-  //     part of a double-literal) is found.
-  //  b) ALLOW_TRAILING_JUNK is set, but the string does not start with a
-  //     double literal.
-  //
-  // infinity_symbol and nan_symbol are strings that are used to detect
-  // inputs that represent infinity and NaN. They can be null, in which case
-  // they are ignored.
-  // The conversion routine first reads any possible signs. Then it compares the
-  // following character of the input-string with the first character of
-  // the infinity, and nan-symbol. If either matches, the function assumes, that
-  // a match has been found, and expects the following input characters to match
-  // the remaining characters of the special-value symbol.
-  // This means that the following restrictions apply to special-value symbols:
-  //  - they must not start with signs ('+', or '-'),
-  //  - they must not have the same first character.
-  //  - they must not start with digits.
-  //
-  // If the separator character is not kNoSeparator, then that specific
-  // character is ignored when in between two valid digits of the significant.
-  // It is not allowed to appear in the exponent.
-  // It is not allowed to lead or trail the number.
-  // It is not allowed to appear twice next to each other.
-  //
-  // Examples:
-  //  flags = ALLOW_HEX | ALLOW_TRAILING_JUNK,
-  //  empty_string_value = 0.0,
-  //  junk_string_value = NaN,
-  //  infinity_symbol = "infinity",
-  //  nan_symbol = "nan":
-  //    StringToDouble("0x1234") -> 4660.0.
-  //    StringToDouble("0x1234K") -> 4660.0.
-  //    StringToDouble("") -> 0.0  // empty_string_value.
-  //    StringToDouble(" ") -> NaN  // junk_string_value.
-  //    StringToDouble(" 1") -> NaN  // junk_string_value.
-  //    StringToDouble("0x") -> NaN  // junk_string_value.
-  //    StringToDouble("-123.45") -> -123.45.
-  //    StringToDouble("--123.45") -> NaN  // junk_string_value.
-  //    StringToDouble("123e45") -> 123e45.
-  //    StringToDouble("123E45") -> 123e45.
-  //    StringToDouble("123e+45") -> 123e45.
-  //    StringToDouble("123E-45") -> 123e-45.
-  //    StringToDouble("123e") -> 123.0  // trailing junk ignored.
-  //    StringToDouble("123e-") -> 123.0  // trailing junk ignored.
-  //    StringToDouble("+NaN") -> NaN  // NaN string literal.
-  //    StringToDouble("-infinity") -> -inf.  // infinity literal.
-  //    StringToDouble("Infinity") -> NaN  // junk_string_value.
-  //
-  //  flags = ALLOW_OCTAL | ALLOW_LEADING_SPACES,
-  //  empty_string_value = 0.0,
-  //  junk_string_value = NaN,
-  //  infinity_symbol = NULL,
-  //  nan_symbol = NULL:
-  //    StringToDouble("0x1234") -> NaN  // junk_string_value.
-  //    StringToDouble("01234") -> 668.0.
-  //    StringToDouble("") -> 0.0  // empty_string_value.
-  //    StringToDouble(" ") -> 0.0  // empty_string_value.
-  //    StringToDouble(" 1") -> 1.0
-  //    StringToDouble("0x") -> NaN  // junk_string_value.
-  //    StringToDouble("0123e45") -> NaN  // junk_string_value.
-  //    StringToDouble("01239E45") -> 1239e45.
-  //    StringToDouble("-infinity") -> NaN  // junk_string_value.
-  //    StringToDouble("NaN") -> NaN  // junk_string_value.
-  //
-  //  flags = NO_FLAGS,
-  //  separator = ' ':
-  //    StringToDouble("1 2 3 4") -> 1234.0
-  //    StringToDouble("1  2") -> NaN // junk_string_value
-  //    StringToDouble("1 000 000.0") -> 1000000.0
-  //    StringToDouble("1.000 000") -> 1.0
-  //    StringToDouble("1.0e1 000") -> NaN // junk_string_value
-  StringToDoubleConverter(int flags,
-                          double empty_string_value,
-                          double junk_string_value,
-                          const char* infinity_symbol,
-                          const char* nan_symbol,
-                          uc16 separator = kNoSeparator)
-      : flags_(flags),
-        empty_string_value_(empty_string_value),
-        junk_string_value_(junk_string_value),
-        infinity_symbol_(infinity_symbol),
-        nan_symbol_(nan_symbol),
-        separator_(separator) {
-  }
-
-  // Performs the conversion.
-  // The output parameter 'processed_characters_count' is set to the number
-  // of characters that have been processed to read the number.
-  // Spaces than are processed with ALLOW_{LEADING|TRAILING}_SPACES are included
-  // in the 'processed_characters_count'. Trailing junk is never included.
-  double StringToDouble(const char* buffer,
-                        int length,
-                        int* processed_characters_count) const;
-
-  // Same as StringToDouble above but for 16 bit characters.
-  double StringToDouble(const uc16* buffer,
-                        int length,
-                        int* processed_characters_count) const;
-
-  // Same as StringToDouble but reads a float.
-  // Note that this is not equivalent to static_cast<float>(StringToDouble(...))
-  // due to potential double-rounding.
-  float StringToFloat(const char* buffer,
-                      int length,
-                      int* processed_characters_count) const;
-
-  // Same as StringToFloat above but for 16 bit characters.
-  float StringToFloat(const uc16* buffer,
-                      int length,
-                      int* processed_characters_count) const;
-
- private:
-  const int flags_;
-  const double empty_string_value_;
-  const double junk_string_value_;
-  const char* const infinity_symbol_;
-  const char* const nan_symbol_;
-  const uc16 separator_;
-
-  template <class Iterator>
-  double StringToIeee(Iterator start_pointer,
-                      int length,
-                      bool read_as_double,
-                      int* processed_characters_count) const;
-
-  DC_DISALLOW_IMPLICIT_CONSTRUCTORS(StringToDoubleConverter);
-};
-
-}  // namespace double_conversion
-
-#endif  // DOUBLE_CONVERSION_DOUBLE_CONVERSION_H_
diff --git a/c_src/double-conversion/fast-dtoa.cc b/c_src/double-conversion/fast-dtoa.cc
deleted file mode 100644
index 6135038..0000000
--- a/c_src/double-conversion/fast-dtoa.cc
+++ /dev/null
@@ -1,665 +0,0 @@
-// Copyright 2012 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#include "fast-dtoa.h"
-
-#include "cached-powers.h"
-#include "diy-fp.h"
-#include "ieee.h"
-
-namespace double_conversion {
-
-// The minimal and maximal target exponent define the range of w's binary
-// exponent, where 'w' is the result of multiplying the input by a cached power
-// of ten.
-//
-// A different range might be chosen on a different platform, to optimize digit
-// generation, but a smaller range requires more powers of ten to be cached.
-static const int kMinimalTargetExponent = -60;
-static const int kMaximalTargetExponent = -32;
-
-
-// Adjusts the last digit of the generated number, and screens out generated
-// solutions that may be inaccurate. A solution may be inaccurate if it is
-// outside the safe interval, or if we cannot prove that it is closer to the
-// input than a neighboring representation of the same length.
-//
-// Input: * buffer containing the digits of too_high / 10^kappa
-//        * the buffer's length
-//        * distance_too_high_w == (too_high - w).f() * unit
-//        * unsafe_interval == (too_high - too_low).f() * unit
-//        * rest = (too_high - buffer * 10^kappa).f() * unit
-//        * ten_kappa = 10^kappa * unit
-//        * unit = the common multiplier
-// Output: returns true if the buffer is guaranteed to contain the closest
-//    representable number to the input.
-//  Modifies the generated digits in the buffer to approach (round towards) w.
-static bool RoundWeed(Vector<char> buffer,
-                      int length,
-                      uint64_t distance_too_high_w,
-                      uint64_t unsafe_interval,
-                      uint64_t rest,
-                      uint64_t ten_kappa,
-                      uint64_t unit) {
-  uint64_t small_distance = distance_too_high_w - unit;
-  uint64_t big_distance = distance_too_high_w + unit;
-  // Let w_low  = too_high - big_distance, and
-  //     w_high = too_high - small_distance.
-  // Note: w_low < w < w_high
-  //
-  // The real w (* unit) must lie somewhere inside the interval
-  // ]w_low; w_high[ (often written as "(w_low; w_high)")
-
-  // Basically the buffer currently contains a number in the unsafe interval
-  // ]too_low; too_high[ with too_low < w < too_high
-  //
-  //  too_high - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-  //                     ^v 1 unit            ^      ^                 ^      ^
-  //  boundary_high ---------------------     .      .                 .      .
-  //                     ^v 1 unit            .      .                 .      .
-  //   - - - - - - - - - - - - - - - - - - -  +  - - + - - - - - -     .      .
-  //                                          .      .         ^       .      .
-  //                                          .  big_distance  .       .      .
-  //                                          .      .         .       .    rest
-  //                              small_distance     .         .       .      .
-  //                                          v      .         .       .      .
-  //  w_high - - - - - - - - - - - - - - - - - -     .         .       .      .
-  //                     ^v 1 unit                   .         .       .      .
-  //  w ----------------------------------------     .         .       .      .
-  //                     ^v 1 unit                   v         .       .      .
-  //  w_low  - - - - - - - - - - - - - - - - - - - - -         .       .      .
-  //                                                           .       .      v
-  //  buffer --------------------------------------------------+-------+--------
-  //                                                           .       .
-  //                                                  safe_interval    .
-  //                                                           v       .
-  //   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -     .
-  //                     ^v 1 unit                                     .
-  //  boundary_low -------------------------                     unsafe_interval
-  //                     ^v 1 unit                                     v
-  //  too_low  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-  //
-  //
-  // Note that the value of buffer could lie anywhere inside the range too_low
-  // to too_high.
-  //
-  // boundary_low, boundary_high and w are approximations of the real boundaries
-  // and v (the input number). They are guaranteed to be precise up to one unit.
-  // In fact the error is guaranteed to be strictly less than one unit.
-  //
-  // Anything that lies outside the unsafe interval is guaranteed not to round
-  // to v when read again.
-  // Anything that lies inside the safe interval is guaranteed to round to v
-  // when read again.
-  // If the number inside the buffer lies inside the unsafe interval but not
-  // inside the safe interval then we simply do not know and bail out (returning
-  // false).
-  //
-  // Similarly we have to take into account the imprecision of 'w' when finding
-  // the closest representation of 'w'. If we have two potential
-  // representations, and one is closer to both w_low and w_high, then we know
-  // it is closer to the actual value v.
-  //
-  // By generating the digits of too_high we got the largest (closest to
-  // too_high) buffer that is still in the unsafe interval. In the case where
-  // w_high < buffer < too_high we try to decrement the buffer.
-  // This way the buffer approaches (rounds towards) w.
-  // There are 3 conditions that stop the decrementation process:
-  //   1) the buffer is already below w_high
-  //   2) decrementing the buffer would make it leave the unsafe interval
-  //   3) decrementing the buffer would yield a number below w_high and farther
-  //      away than the current number. In other words:
-  //              (buffer{-1} < w_high) && w_high - buffer{-1} > buffer - w_high
-  // Instead of using the buffer directly we use its distance to too_high.
-  // Conceptually rest ~= too_high - buffer
-  // We need to do the following tests in this order to avoid over- and
-  // underflows.
-  ASSERT(rest <= unsafe_interval);
-  while (rest < small_distance &&  // Negated condition 1
-         unsafe_interval - rest >= ten_kappa &&  // Negated condition 2
-         (rest + ten_kappa < small_distance ||  // buffer{-1} > w_high
-          small_distance - rest >= rest + ten_kappa - small_distance)) {
-    buffer[length - 1]--;
-    rest += ten_kappa;
-  }
-
-  // We have approached w+ as much as possible. We now test if approaching w-
-  // would require changing the buffer. If yes, then we have two possible
-  // representations close to w, but we cannot decide which one is closer.
-  if (rest < big_distance &&
-      unsafe_interval - rest >= ten_kappa &&
-      (rest + ten_kappa < big_distance ||
-       big_distance - rest > rest + ten_kappa - big_distance)) {
-    return false;
-  }
-
-  // Weeding test.
-  //   The safe interval is [too_low + 2 ulp; too_high - 2 ulp]
-  //   Since too_low = too_high - unsafe_interval this is equivalent to
-  //      [too_high - unsafe_interval + 4 ulp; too_high - 2 ulp]
-  //   Conceptually we have: rest ~= too_high - buffer
-  return (2 * unit <= rest) && (rest <= unsafe_interval - 4 * unit);
-}
-
-
-// Rounds the buffer upwards if the result is closer to v by possibly adding
-// 1 to the buffer. If the precision of the calculation is not sufficient to
-// round correctly, return false.
-// The rounding might shift the whole buffer in which case the kappa is
-// adjusted. For example "99", kappa = 3 might become "10", kappa = 4.
-//
-// If 2*rest > ten_kappa then the buffer needs to be round up.
-// rest can have an error of +/- 1 unit. This function accounts for the
-// imprecision and returns false, if the rounding direction cannot be
-// unambiguously determined.
-//
-// Precondition: rest < ten_kappa.
-static bool RoundWeedCounted(Vector<char> buffer,
-                             int length,
-                             uint64_t rest,
-                             uint64_t ten_kappa,
-                             uint64_t unit,
-                             int* kappa) {
-  ASSERT(rest < ten_kappa);
-  // The following tests are done in a specific order to avoid overflows. They
-  // will work correctly with any uint64 values of rest < ten_kappa and unit.
-  //
-  // If the unit is too big, then we don't know which way to round. For example
-  // a unit of 50 means that the real number lies within rest +/- 50. If
-  // 10^kappa == 40 then there is no way to tell which way to round.
-  if (unit >= ten_kappa) return false;
-  // Even if unit is just half the size of 10^kappa we are already completely
-  // lost. (And after the previous test we know that the expression will not
-  // over/underflow.)
-  if (ten_kappa - unit <= unit) return false;
-  // If 2 * (rest + unit) <= 10^kappa we can safely round down.
-  if ((ten_kappa - rest > rest) && (ten_kappa - 2 * rest >= 2 * unit)) {
-    return true;
-  }
-  // If 2 * (rest - unit) >= 10^kappa, then we can safely round up.
-  if ((rest > unit) && (ten_kappa - (rest - unit) <= (rest - unit))) {
-    // Increment the last digit recursively until we find a non '9' digit.
-    buffer[length - 1]++;
-    for (int i = length - 1; i > 0; --i) {
-      if (buffer[i] != '0' + 10) break;
-      buffer[i] = '0';
-      buffer[i - 1]++;
-    }
-    // If the first digit is now '0'+ 10 we had a buffer with all '9's. With the
-    // exception of the first digit all digits are now '0'. Simply switch the
-    // first digit to '1' and adjust the kappa. Example: "99" becomes "10" and
-    // the power (the kappa) is increased.
-    if (buffer[0] == '0' + 10) {
-      buffer[0] = '1';
-      (*kappa) += 1;
-    }
-    return true;
-  }
-  return false;
-}
-
-// Returns the biggest power of ten that is less than or equal to the given
-// number. We furthermore receive the maximum number of bits 'number' has.
-//
-// Returns power == 10^(exponent_plus_one-1) such that
-//    power <= number < power * 10.
-// If number_bits == 0 then 0^(0-1) is returned.
-// The number of bits must be <= 32.
-// Precondition: number < (1 << (number_bits + 1)).
-
-// Inspired by the method for finding an integer log base 10 from here:
-// http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10
-static unsigned int const kSmallPowersOfTen[] =
-    {0, 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000,
-     1000000000};
-
-static void BiggestPowerTen(uint32_t number,
-                            int number_bits,
-                            uint32_t* power,
-                            int* exponent_plus_one) {
-  ASSERT(number < (1u << (number_bits + 1)));
-  // 1233/4096 is approximately 1/lg(10).
-  int exponent_plus_one_guess = ((number_bits + 1) * 1233 >> 12);
-  // We increment to skip over the first entry in the kPowersOf10 table.
-  // Note: kPowersOf10[i] == 10^(i-1).
-  exponent_plus_one_guess++;
-  // We don't have any guarantees that 2^number_bits <= number.
-  if (number < kSmallPowersOfTen[exponent_plus_one_guess]) {
-    exponent_plus_one_guess--;
-  }
-  *power = kSmallPowersOfTen[exponent_plus_one_guess];
-  *exponent_plus_one = exponent_plus_one_guess;
-}
-
-// Generates the digits of input number w.
-// w is a floating-point number (DiyFp), consisting of a significand and an
-// exponent. Its exponent is bounded by kMinimalTargetExponent and
-// kMaximalTargetExponent.
-//       Hence -60 <= w.e() <= -32.
-//
-// Returns false if it fails, in which case the generated digits in the buffer
-// should not be used.
-// Preconditions:
-//  * low, w and high are correct up to 1 ulp (unit in the last place). That
-//    is, their error must be less than a unit of their last digits.
-//  * low.e() == w.e() == high.e()
-//  * low < w < high, and taking into account their error: low~ <= high~
-//  * kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent
-// Postconditions: returns false if procedure fails.
-//   otherwise:
-//     * buffer is not null-terminated, but len contains the number of digits.
-//     * buffer contains the shortest possible decimal digit-sequence
-//       such that LOW < buffer * 10^kappa < HIGH, where LOW and HIGH are the
-//       correct values of low and high (without their error).
-//     * if more than one decimal representation gives the minimal number of
-//       decimal digits then the one closest to W (where W is the correct value
-//       of w) is chosen.
-// Remark: this procedure takes into account the imprecision of its input
-//   numbers. If the precision is not enough to guarantee all the postconditions
-//   then false is returned. This usually happens rarely (~0.5%).
-//
-// Say, for the sake of example, that
-//   w.e() == -48, and w.f() == 0x1234567890abcdef
-// w's value can be computed by w.f() * 2^w.e()
-// We can obtain w's integral digits by simply shifting w.f() by -w.e().
-//  -> w's integral part is 0x1234
-//  w's fractional part is therefore 0x567890abcdef.
-// Printing w's integral part is easy (simply print 0x1234 in decimal).
-// In order to print its fraction we repeatedly multiply the fraction by 10 and
-// get each digit. Example the first digit after the point would be computed by
-//   (0x567890abcdef * 10) >> 48. -> 3
-// The whole thing becomes slightly more complicated because we want to stop
-// once we have enough digits. That is, once the digits inside the buffer
-// represent 'w' we can stop. Everything inside the interval low - high
-// represents w. However we have to pay attention to low, high and w's
-// imprecision.
-static bool DigitGen(DiyFp low,
-                     DiyFp w,
-                     DiyFp high,
-                     Vector<char> buffer,
-                     int* length,
-                     int* kappa) {
-  ASSERT(low.e() == w.e() && w.e() == high.e());
-  ASSERT(low.f() + 1 <= high.f() - 1);
-  ASSERT(kMinimalTargetExponent <= w.e() && w.e() <= kMaximalTargetExponent);
-  // low, w and high are imprecise, but by less than one ulp (unit in the last
-  // place).
-  // If we remove (resp. add) 1 ulp from low (resp. high) we are certain that
-  // the new numbers are outside of the interval we want the final
-  // representation to lie in.
-  // Inversely adding (resp. removing) 1 ulp from low (resp. high) would yield
-  // numbers that are certain to lie in the interval. We will use this fact
-  // later on.
-  // We will now start by generating the digits within the uncertain
-  // interval. Later we will weed out representations that lie outside the safe
-  // interval and thus _might_ lie outside the correct interval.
-  uint64_t unit = 1;
-  DiyFp too_low = DiyFp(low.f() - unit, low.e());
-  DiyFp too_high = DiyFp(high.f() + unit, high.e());
-  // too_low and too_high are guaranteed to lie outside the interval we want the
-  // generated number in.
-  DiyFp unsafe_interval = DiyFp::Minus(too_high, too_low);
-  // We now cut the input number into two parts: the integral digits and the
-  // fractionals. We will not write any decimal separator though, but adapt
-  // kappa instead.
-  // Reminder: we are currently computing the digits (stored inside the buffer)
-  // such that:   too_low < buffer * 10^kappa < too_high
-  // We use too_high for the digit_generation and stop as soon as possible.
-  // If we stop early we effectively round down.
-  DiyFp one = DiyFp(static_cast<uint64_t>(1) << -w.e(), w.e());
-  // Division by one is a shift.
-  uint32_t integrals = static_cast<uint32_t>(too_high.f() >> -one.e());
-  // Modulo by one is an and.
-  uint64_t fractionals = too_high.f() & (one.f() - 1);
-  uint32_t divisor;
-  int divisor_exponent_plus_one;
-  BiggestPowerTen(integrals, DiyFp::kSignificandSize - (-one.e()),
-                  &divisor, &divisor_exponent_plus_one);
-  *kappa = divisor_exponent_plus_one;
-  *length = 0;
-  // Loop invariant: buffer = too_high / 10^kappa  (integer division)
-  // The invariant holds for the first iteration: kappa has been initialized
-  // with the divisor exponent + 1. And the divisor is the biggest power of ten
-  // that is smaller than integrals.
-  while (*kappa > 0) {
-    int digit = integrals / divisor;
-    ASSERT(digit <= 9);
-    buffer[*length] = static_cast<char>('0' + digit);
-    (*length)++;
-    integrals %= divisor;
-    (*kappa)--;
-    // Note that kappa now equals the exponent of the divisor and that the
-    // invariant thus holds again.
-    uint64_t rest =
-        (static_cast<uint64_t>(integrals) << -one.e()) + fractionals;
-    // Invariant: too_high = buffer * 10^kappa + DiyFp(rest, one.e())
-    // Reminder: unsafe_interval.e() == one.e()
-    if (rest < unsafe_interval.f()) {
-      // Rounding down (by not emitting the remaining digits) yields a number
-      // that lies within the unsafe interval.
-      return RoundWeed(buffer, *length, DiyFp::Minus(too_high, w).f(),
-                       unsafe_interval.f(), rest,
-                       static_cast<uint64_t>(divisor) << -one.e(), unit);
-    }
-    divisor /= 10;
-  }
-
-  // The integrals have been generated. We are at the point of the decimal
-  // separator. In the following loop we simply multiply the remaining digits by
-  // 10 and divide by one. We just need to pay attention to multiply associated
-  // data (like the interval or 'unit'), too.
-  // Note that the multiplication by 10 does not overflow, because w.e >= -60
-  // and thus one.e >= -60.
-  ASSERT(one.e() >= -60);
-  ASSERT(fractionals < one.f());
-  ASSERT(UINT64_2PART_C(0xFFFFFFFF, FFFFFFFF) / 10 >= one.f());
-  for (;;) {
-    fractionals *= 10;
-    unit *= 10;
-    unsafe_interval.set_f(unsafe_interval.f() * 10);
-    // Integer division by one.
-    int digit = static_cast<int>(fractionals >> -one.e());
-    ASSERT(digit <= 9);
-    buffer[*length] = static_cast<char>('0' + digit);
-    (*length)++;
-    fractionals &= one.f() - 1;  // Modulo by one.
-    (*kappa)--;
-    if (fractionals < unsafe_interval.f()) {
-      return RoundWeed(buffer, *length, DiyFp::Minus(too_high, w).f() * unit,
-                       unsafe_interval.f(), fractionals, one.f(), unit);
-    }
-  }
-}
-
-
-
-// Generates (at most) requested_digits digits of input number w.
-// w is a floating-point number (DiyFp), consisting of a significand and an
-// exponent. Its exponent is bounded by kMinimalTargetExponent and
-// kMaximalTargetExponent.
-//       Hence -60 <= w.e() <= -32.
-//
-// Returns false if it fails, in which case the generated digits in the buffer
-// should not be used.
-// Preconditions:
-//  * w is correct up to 1 ulp (unit in the last place). That
-//    is, its error must be strictly less than a unit of its last digit.
-//  * kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent
-//
-// Postconditions: returns false if procedure fails.
-//   otherwise:
-//     * buffer is not null-terminated, but length contains the number of
-//       digits.
-//     * the representation in buffer is the most precise representation of
-//       requested_digits digits.
-//     * buffer contains at most requested_digits digits of w. If there are less
-//       than requested_digits digits then some trailing '0's have been removed.
-//     * kappa is such that
-//            w = buffer * 10^kappa + eps with |eps| < 10^kappa / 2.
-//
-// Remark: This procedure takes into account the imprecision of its input
-//   numbers. If the precision is not enough to guarantee all the postconditions
-//   then false is returned. This usually happens rarely, but the failure-rate
-//   increases with higher requested_digits.
-static bool DigitGenCounted(DiyFp w,
-                            int requested_digits,
-                            Vector<char> buffer,
-                            int* length,
-                            int* kappa) {
-  ASSERT(kMinimalTargetExponent <= w.e() && w.e() <= kMaximalTargetExponent);
-  ASSERT(kMinimalTargetExponent >= -60);
-  ASSERT(kMaximalTargetExponent <= -32);
-  // w is assumed to have an error less than 1 unit. Whenever w is scaled we
-  // also scale its error.
-  uint64_t w_error = 1;
-  // We cut the input number into two parts: the integral digits and the
-  // fractional digits. We don't emit any decimal separator, but adapt kappa
-  // instead. Example: instead of writing "1.2" we put "12" into the buffer and
-  // increase kappa by 1.
-  DiyFp one = DiyFp(static_cast<uint64_t>(1) << -w.e(), w.e());
-  // Division by one is a shift.
-  uint32_t integrals = static_cast<uint32_t>(w.f() >> -one.e());
-  // Modulo by one is an and.
-  uint64_t fractionals = w.f() & (one.f() - 1);
-  uint32_t divisor;
-  int divisor_exponent_plus_one;
-  BiggestPowerTen(integrals, DiyFp::kSignificandSize - (-one.e()),
-                  &divisor, &divisor_exponent_plus_one);
-  *kappa = divisor_exponent_plus_one;
-  *length = 0;
-
-  // Loop invariant: buffer = w / 10^kappa  (integer division)
-  // The invariant holds for the first iteration: kappa has been initialized
-  // with the divisor exponent + 1. And the divisor is the biggest power of ten
-  // that is smaller than 'integrals'.
-  while (*kappa > 0) {
-    int digit = integrals / divisor;
-    ASSERT(digit <= 9);
-    buffer[*length] = static_cast<char>('0' + digit);
-    (*length)++;
-    requested_digits--;
-    integrals %= divisor;
-    (*kappa)--;
-    // Note that kappa now equals the exponent of the divisor and that the
-    // invariant thus holds again.
-    if (requested_digits == 0) break;
-    divisor /= 10;
-  }
-
-  if (requested_digits == 0) {
-    uint64_t rest =
-        (static_cast<uint64_t>(integrals) << -one.e()) + fractionals;
-    return RoundWeedCounted(buffer, *length, rest,
-                            static_cast<uint64_t>(divisor) << -one.e(), w_error,
-                            kappa);
-  }
-
-  // The integrals have been generated. We are at the point of the decimal
-  // separator. In the following loop we simply multiply the remaining digits by
-  // 10 and divide by one. We just need to pay attention to multiply associated
-  // data (the 'unit'), too.
-  // Note that the multiplication by 10 does not overflow, because w.e >= -60
-  // and thus one.e >= -60.
-  ASSERT(one.e() >= -60);
-  ASSERT(fractionals < one.f());
-  ASSERT(UINT64_2PART_C(0xFFFFFFFF, FFFFFFFF) / 10 >= one.f());
-  while (requested_digits > 0 && fractionals > w_error) {
-    fractionals *= 10;
-    w_error *= 10;
-    // Integer division by one.
-    int digit = static_cast<int>(fractionals >> -one.e());
-    ASSERT(digit <= 9);
-    buffer[*length] = static_cast<char>('0' + digit);
-    (*length)++;
-    requested_digits--;
-    fractionals &= one.f() - 1;  // Modulo by one.
-    (*kappa)--;
-  }
-  if (requested_digits != 0) return false;
-  return RoundWeedCounted(buffer, *length, fractionals, one.f(), w_error,
-                          kappa);
-}
-
-
-// Provides a decimal representation of v.
-// Returns true if it succeeds, otherwise the result cannot be trusted.
-// There will be *length digits inside the buffer (not null-terminated).
-// If the function returns true then
-//        v == (double) (buffer * 10^decimal_exponent).
-// The digits in the buffer are the shortest representation possible: no
-// 0.09999999999999999 instead of 0.1. The shorter representation will even be
-// chosen even if the longer one would be closer to v.
-// The last digit will be closest to the actual v. That is, even if several
-// digits might correctly yield 'v' when read again, the closest will be
-// computed.
-static bool Grisu3(double v,
-                   FastDtoaMode mode,
-                   Vector<char> buffer,
-                   int* length,
-                   int* decimal_exponent) {
-  DiyFp w = Double(v).AsNormalizedDiyFp();
-  // boundary_minus and boundary_plus are the boundaries between v and its
-  // closest floating-point neighbors. Any number strictly between
-  // boundary_minus and boundary_plus will round to v when convert to a double.
-  // Grisu3 will never output representations that lie exactly on a boundary.
-  DiyFp boundary_minus, boundary_plus;
-  if (mode == FAST_DTOA_SHORTEST) {
-    Double(v).NormalizedBoundaries(&boundary_minus, &boundary_plus);
-  } else {
-    ASSERT(mode == FAST_DTOA_SHORTEST_SINGLE);
-    float single_v = static_cast<float>(v);
-    Single(single_v).NormalizedBoundaries(&boundary_minus, &boundary_plus);
-  }
-  ASSERT(boundary_plus.e() == w.e());
-  DiyFp ten_mk;  // Cached power of ten: 10^-k
-  int mk;        // -k
-  int ten_mk_minimal_binary_exponent =
-     kMinimalTargetExponent - (w.e() + DiyFp::kSignificandSize);
-  int ten_mk_maximal_binary_exponent =
-     kMaximalTargetExponent - (w.e() + DiyFp::kSignificandSize);
-  PowersOfTenCache::GetCachedPowerForBinaryExponentRange(
-      ten_mk_minimal_binary_exponent,
-      ten_mk_maximal_binary_exponent,
-      &ten_mk, &mk);
-  ASSERT((kMinimalTargetExponent <= w.e() + ten_mk.e() +
-          DiyFp::kSignificandSize) &&
-         (kMaximalTargetExponent >= w.e() + ten_mk.e() +
-          DiyFp::kSignificandSize));
-  // Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a
-  // 64 bit significand and ten_mk is thus only precise up to 64 bits.
-
-  // The DiyFp::Times procedure rounds its result, and ten_mk is approximated
-  // too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now
-  // off by a small amount.
-  // In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w.
-  // In other words: let f = scaled_w.f() and e = scaled_w.e(), then
-  //           (f-1) * 2^e < w*10^k < (f+1) * 2^e
-  DiyFp scaled_w = DiyFp::Times(w, ten_mk);
-  ASSERT(scaled_w.e() ==
-         boundary_plus.e() + ten_mk.e() + DiyFp::kSignificandSize);
-  // In theory it would be possible to avoid some recomputations by computing
-  // the difference between w and boundary_minus/plus (a power of 2) and to
-  // compute scaled_boundary_minus/plus by subtracting/adding from
-  // scaled_w. However the code becomes much less readable and the speed
-  // enhancements are not terriffic.
-  DiyFp scaled_boundary_minus = DiyFp::Times(boundary_minus, ten_mk);
-  DiyFp scaled_boundary_plus  = DiyFp::Times(boundary_plus,  ten_mk);
-
-  // DigitGen will generate the digits of scaled_w. Therefore we have
-  // v == (double) (scaled_w * 10^-mk).
-  // Set decimal_exponent == -mk and pass it to DigitGen. If scaled_w is not an
-  // integer than it will be updated. For instance if scaled_w == 1.23 then
-  // the buffer will be filled with "123" und the decimal_exponent will be
-  // decreased by 2.
-  int kappa;
-  bool result = DigitGen(scaled_boundary_minus, scaled_w, scaled_boundary_plus,
-                         buffer, length, &kappa);
-  *decimal_exponent = -mk + kappa;
-  return result;
-}
-
-
-// The "counted" version of grisu3 (see above) only generates requested_digits
-// number of digits. This version does not generate the shortest representation,
-// and with enough requested digits 0.1 will at some point print as 0.9999999...
-// Grisu3 is too imprecise for real halfway cases (1.5 will not work) and
-// therefore the rounding strategy for halfway cases is irrelevant.
-static bool Grisu3Counted(double v,
-                          int requested_digits,
-                          Vector<char> buffer,
-                          int* length,
-                          int* decimal_exponent) {
-  DiyFp w = Double(v).AsNormalizedDiyFp();
-  DiyFp ten_mk;  // Cached power of ten: 10^-k
-  int mk;        // -k
-  int ten_mk_minimal_binary_exponent =
-     kMinimalTargetExponent - (w.e() + DiyFp::kSignificandSize);
-  int ten_mk_maximal_binary_exponent =
-     kMaximalTargetExponent - (w.e() + DiyFp::kSignificandSize);
-  PowersOfTenCache::GetCachedPowerForBinaryExponentRange(
-      ten_mk_minimal_binary_exponent,
-      ten_mk_maximal_binary_exponent,
-      &ten_mk, &mk);
-  ASSERT((kMinimalTargetExponent <= w.e() + ten_mk.e() +
-          DiyFp::kSignificandSize) &&
-         (kMaximalTargetExponent >= w.e() + ten_mk.e() +
-          DiyFp::kSignificandSize));
-  // Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a
-  // 64 bit significand and ten_mk is thus only precise up to 64 bits.
-
-  // The DiyFp::Times procedure rounds its result, and ten_mk is approximated
-  // too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now
-  // off by a small amount.
-  // In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w.
-  // In other words: let f = scaled_w.f() and e = scaled_w.e(), then
-  //           (f-1) * 2^e < w*10^k < (f+1) * 2^e
-  DiyFp scaled_w = DiyFp::Times(w, ten_mk);
-
-  // We now have (double) (scaled_w * 10^-mk).
-  // DigitGen will generate the first requested_digits digits of scaled_w and
-  // return together with a kappa such that scaled_w ~= buffer * 10^kappa. (It
-  // will not always be exactly the same since DigitGenCounted only produces a
-  // limited number of digits.)
-  int kappa;
-  bool result = DigitGenCounted(scaled_w, requested_digits,
-                                buffer, length, &kappa);
-  *decimal_exponent = -mk + kappa;
-  return result;
-}
-
-
-bool FastDtoa(double v,
-              FastDtoaMode mode,
-              int requested_digits,
-              Vector<char> buffer,
-              int* length,
-              int* decimal_point) {
-  ASSERT(v > 0);
-  ASSERT(!Double(v).IsSpecial());
-
-  bool result = false;
-  int decimal_exponent = 0;
-  switch (mode) {
-    case FAST_DTOA_SHORTEST:
-    case FAST_DTOA_SHORTEST_SINGLE:
-      result = Grisu3(v, mode, buffer, length, &decimal_exponent);
-      break;
-    case FAST_DTOA_PRECISION:
-      result = Grisu3Counted(v, requested_digits,
-                             buffer, length, &decimal_exponent);
-      break;
-    default:
-      UNREACHABLE();
-  }
-  if (result) {
-    *decimal_point = *length + decimal_exponent;
-    buffer[*length] = '\0';
-  }
-  return result;
-}
-
-}  // namespace double_conversion
diff --git a/c_src/double-conversion/fast-dtoa.h b/c_src/double-conversion/fast-dtoa.h
deleted file mode 100644
index 5f1e8ee..0000000
--- a/c_src/double-conversion/fast-dtoa.h
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef DOUBLE_CONVERSION_FAST_DTOA_H_
-#define DOUBLE_CONVERSION_FAST_DTOA_H_
-
-#include "utils.h"
-
-namespace double_conversion {
-
-enum FastDtoaMode {
-  // Computes the shortest representation of the given input. The returned
-  // result will be the most accurate number of this length. Longer
-  // representations might be more accurate.
-  FAST_DTOA_SHORTEST,
-  // Same as FAST_DTOA_SHORTEST but for single-precision floats.
-  FAST_DTOA_SHORTEST_SINGLE,
-  // Computes a representation where the precision (number of digits) is
-  // given as input. The precision is independent of the decimal point.
-  FAST_DTOA_PRECISION
-};
-
-// FastDtoa will produce at most kFastDtoaMaximalLength digits. This does not
-// include the terminating '\0' character.
-static const int kFastDtoaMaximalLength = 17;
-// Same for single-precision numbers.
-static const int kFastDtoaMaximalSingleLength = 9;
-
-// Provides a decimal representation of v.
-// The result should be interpreted as buffer * 10^(point - length).
-//
-// Precondition:
-//   * v must be a strictly positive finite double.
-//
-// Returns true if it succeeds, otherwise the result can not be trusted.
-// There will be *length digits inside the buffer followed by a null terminator.
-// If the function returns true and mode equals
-//   - FAST_DTOA_SHORTEST, then
-//     the parameter requested_digits is ignored.
-//     The result satisfies
-//         v == (double) (buffer * 10^(point - length)).
-//     The digits in the buffer are the shortest representation possible. E.g.
-//     if 0.099999999999 and 0.1 represent the same double then "1" is returned
-//     with point = 0.
-//     The last digit will be closest to the actual v. That is, even if several
-//     digits might correctly yield 'v' when read again, the buffer will contain
-//     the one closest to v.
-//   - FAST_DTOA_PRECISION, then
-//     the buffer contains requested_digits digits.
-//     the difference v - (buffer * 10^(point-length)) is closest to zero for
-//     all possible representations of requested_digits digits.
-//     If there are two values that are equally close, then FastDtoa returns
-//     false.
-// For both modes the buffer must be large enough to hold the result.
-bool FastDtoa(double d,
-              FastDtoaMode mode,
-              int requested_digits,
-              Vector<char> buffer,
-              int* length,
-              int* decimal_point);
-
-}  // namespace double_conversion
-
-#endif  // DOUBLE_CONVERSION_FAST_DTOA_H_
diff --git a/c_src/double-conversion/fixed-dtoa.cc b/c_src/double-conversion/fixed-dtoa.cc
deleted file mode 100644
index 0f989bc..0000000
--- a/c_src/double-conversion/fixed-dtoa.cc
+++ /dev/null
@@ -1,405 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#include <cmath>
-
-#include "fixed-dtoa.h"
-#include "ieee.h"
-
-namespace double_conversion {
-
-// Represents a 128bit type. This class should be replaced by a native type on
-// platforms that support 128bit integers.
-class UInt128 {
- public:
-  UInt128() : high_bits_(0), low_bits_(0) { }
-  UInt128(uint64_t high, uint64_t low) : high_bits_(high), low_bits_(low) { }
-
-  void Multiply(uint32_t multiplicand) {
-    uint64_t accumulator;
-
-    accumulator = (low_bits_ & kMask32) * multiplicand;
-    uint32_t part = static_cast<uint32_t>(accumulator & kMask32);
-    accumulator >>= 32;
-    accumulator = accumulator + (low_bits_ >> 32) * multiplicand;
-    low_bits_ = (accumulator << 32) + part;
-    accumulator >>= 32;
-    accumulator = accumulator + (high_bits_ & kMask32) * multiplicand;
-    part = static_cast<uint32_t>(accumulator & kMask32);
-    accumulator >>= 32;
-    accumulator = accumulator + (high_bits_ >> 32) * multiplicand;
-    high_bits_ = (accumulator << 32) + part;
-    ASSERT((accumulator >> 32) == 0);
-  }
-
-  void Shift(int shift_amount) {
-    ASSERT(-64 <= shift_amount && shift_amount <= 64);
-    if (shift_amount == 0) {
-      return;
-    } else if (shift_amount == -64) {
-      high_bits_ = low_bits_;
-      low_bits_ = 0;
-    } else if (shift_amount == 64) {
-      low_bits_ = high_bits_;
-      high_bits_ = 0;
-    } else if (shift_amount <= 0) {
-      high_bits_ <<= -shift_amount;
-      high_bits_ += low_bits_ >> (64 + shift_amount);
-      low_bits_ <<= -shift_amount;
-    } else {
-      low_bits_ >>= shift_amount;
-      low_bits_ += high_bits_ << (64 - shift_amount);
-      high_bits_ >>= shift_amount;
-    }
-  }
-
-  // Modifies *this to *this MOD (2^power).
-  // Returns *this DIV (2^power).
-  int DivModPowerOf2(int power) {
-    if (power >= 64) {
-      int result = static_cast<int>(high_bits_ >> (power - 64));
-      high_bits_ -= static_cast<uint64_t>(result) << (power - 64);
-      return result;
-    } else {
-      uint64_t part_low = low_bits_ >> power;
-      uint64_t part_high = high_bits_ << (64 - power);
-      int result = static_cast<int>(part_low + part_high);
-      high_bits_ = 0;
-      low_bits_ -= part_low << power;
-      return result;
-    }
-  }
-
-  bool IsZero() const {
-    return high_bits_ == 0 && low_bits_ == 0;
-  }
-
-  int BitAt(int position) const {
-    if (position >= 64) {
-      return static_cast<int>(high_bits_ >> (position - 64)) & 1;
-    } else {
-      return static_cast<int>(low_bits_ >> position) & 1;
-    }
-  }
-
- private:
-  static const uint64_t kMask32 = 0xFFFFFFFF;
-  // Value == (high_bits_ << 64) + low_bits_
-  uint64_t high_bits_;
-  uint64_t low_bits_;
-};
-
-
-static const int kDoubleSignificandSize = 53;  // Includes the hidden bit.
-
-
-static void FillDigits32FixedLength(uint32_t number, int requested_length,
-                                    Vector<char> buffer, int* length) {
-  for (int i = requested_length - 1; i >= 0; --i) {
-    buffer[(*length) + i] = '0' + number % 10;
-    number /= 10;
-  }
-  *length += requested_length;
-}
-
-
-static void FillDigits32(uint32_t number, Vector<char> buffer, int* length) {
-  int number_length = 0;
-  // We fill the digits in reverse order and exchange them afterwards.
-  while (number != 0) {
-    int digit = number % 10;
-    number /= 10;
-    buffer[(*length) + number_length] = static_cast<char>('0' + digit);
-    number_length++;
-  }
-  // Exchange the digits.
-  int i = *length;
-  int j = *length + number_length - 1;
-  while (i < j) {
-    char tmp = buffer[i];
-    buffer[i] = buffer[j];
-    buffer[j] = tmp;
-    i++;
-    j--;
-  }
-  *length += number_length;
-}
-
-
-static void FillDigits64FixedLength(uint64_t number,
-                                    Vector<char> buffer, int* length) {
-  const uint32_t kTen7 = 10000000;
-  // For efficiency cut the number into 3 uint32_t parts, and print those.
-  uint32_t part2 = static_cast<uint32_t>(number % kTen7);
-  number /= kTen7;
-  uint32_t part1 = static_cast<uint32_t>(number % kTen7);
-  uint32_t part0 = static_cast<uint32_t>(number / kTen7);
-
-  FillDigits32FixedLength(part0, 3, buffer, length);
-  FillDigits32FixedLength(part1, 7, buffer, length);
-  FillDigits32FixedLength(part2, 7, buffer, length);
-}
-
-
-static void FillDigits64(uint64_t number, Vector<char> buffer, int* length) {
-  const uint32_t kTen7 = 10000000;
-  // For efficiency cut the number into 3 uint32_t parts, and print those.
-  uint32_t part2 = static_cast<uint32_t>(number % kTen7);
-  number /= kTen7;
-  uint32_t part1 = static_cast<uint32_t>(number % kTen7);
-  uint32_t part0 = static_cast<uint32_t>(number / kTen7);
-
-  if (part0 != 0) {
-    FillDigits32(part0, buffer, length);
-    FillDigits32FixedLength(part1, 7, buffer, length);
-    FillDigits32FixedLength(part2, 7, buffer, length);
-  } else if (part1 != 0) {
-    FillDigits32(part1, buffer, length);
-    FillDigits32FixedLength(part2, 7, buffer, length);
-  } else {
-    FillDigits32(part2, buffer, length);
-  }
-}
-
-
-static void RoundUp(Vector<char> buffer, int* length, int* decimal_point) {
-  // An empty buffer represents 0.
-  if (*length == 0) {
-    buffer[0] = '1';
-    *decimal_point = 1;
-    *length = 1;
-    return;
-  }
-  // Round the last digit until we either have a digit that was not '9' or until
-  // we reached the first digit.
-  buffer[(*length) - 1]++;
-  for (int i = (*length) - 1; i > 0; --i) {
-    if (buffer[i] != '0' + 10) {
-      return;
-    }
-    buffer[i] = '0';
-    buffer[i - 1]++;
-  }
-  // If the first digit is now '0' + 10, we would need to set it to '0' and add
-  // a '1' in front. However we reach the first digit only if all following
-  // digits had been '9' before rounding up. Now all trailing digits are '0' and
-  // we simply switch the first digit to '1' and update the decimal-point
-  // (indicating that the point is now one digit to the right).
-  if (buffer[0] == '0' + 10) {
-    buffer[0] = '1';
-    (*decimal_point)++;
-  }
-}
-
-
-// The given fractionals number represents a fixed-point number with binary
-// point at bit (-exponent).
-// Preconditions:
-//   -128 <= exponent <= 0.
-//   0 <= fractionals * 2^exponent < 1
-//   The buffer holds the result.
-// The function will round its result. During the rounding-process digits not
-// generated by this function might be updated, and the decimal-point variable
-// might be updated. If this function generates the digits 99 and the buffer
-// already contained "199" (thus yielding a buffer of "19999") then a
-// rounding-up will change the contents of the buffer to "20000".
-static void FillFractionals(uint64_t fractionals, int exponent,
-                            int fractional_count, Vector<char> buffer,
-                            int* length, int* decimal_point) {
-  ASSERT(-128 <= exponent && exponent <= 0);
-  // 'fractionals' is a fixed-point number, with binary point at bit
-  // (-exponent). Inside the function the non-converted remainder of fractionals
-  // is a fixed-point number, with binary point at bit 'point'.
-  if (-exponent <= 64) {
-    // One 64 bit number is sufficient.
-    ASSERT(fractionals >> 56 == 0);
-    int point = -exponent;
-    for (int i = 0; i < fractional_count; ++i) {
-      if (fractionals == 0) break;
-      // Instead of multiplying by 10 we multiply by 5 and adjust the point
-      // location. This way the fractionals variable will not overflow.
-      // Invariant at the beginning of the loop: fractionals < 2^point.
-      // Initially we have: point <= 64 and fractionals < 2^56
-      // After each iteration the point is decremented by one.
-      // Note that 5^3 = 125 < 128 = 2^7.
-      // Therefore three iterations of this loop will not overflow fractionals
-      // (even without the subtraction at the end of the loop body). At this
-      // time point will satisfy point <= 61 and therefore fractionals < 2^point
-      // and any further multiplication of fractionals by 5 will not overflow.
-      fractionals *= 5;
-      point--;
-      int digit = static_cast<int>(fractionals >> point);
-      ASSERT(digit <= 9);
-      buffer[*length] = static_cast<char>('0' + digit);
-      (*length)++;
-      fractionals -= static_cast<uint64_t>(digit) << point;
-    }
-    // If the first bit after the point is set we have to round up.
-    ASSERT(fractionals == 0 || point - 1 >= 0);
-    if ((fractionals != 0) && ((fractionals >> (point - 1)) & 1) == 1) {
-      RoundUp(buffer, length, decimal_point);
-    }
-  } else {  // We need 128 bits.
-    ASSERT(64 < -exponent && -exponent <= 128);
-    UInt128 fractionals128 = UInt128(fractionals, 0);
-    fractionals128.Shift(-exponent - 64);
-    int point = 128;
-    for (int i = 0; i < fractional_count; ++i) {
-      if (fractionals128.IsZero()) break;
-      // As before: instead of multiplying by 10 we multiply by 5 and adjust the
-      // point location.
-      // This multiplication will not overflow for the same reasons as before.
-      fractionals128.Multiply(5);
-      point--;
-      int digit = fractionals128.DivModPowerOf2(point);
-      ASSERT(digit <= 9);
-      buffer[*length] = static_cast<char>('0' + digit);
-      (*length)++;
-    }
-    if (fractionals128.BitAt(point - 1) == 1) {
-      RoundUp(buffer, length, decimal_point);
-    }
-  }
-}
-
-
-// Removes leading and trailing zeros.
-// If leading zeros are removed then the decimal point position is adjusted.
-static void TrimZeros(Vector<char> buffer, int* length, int* decimal_point) {
-  while (*length > 0 && buffer[(*length) - 1] == '0') {
-    (*length)--;
-  }
-  int first_non_zero = 0;
-  while (first_non_zero < *length && buffer[first_non_zero] == '0') {
-    first_non_zero++;
-  }
-  if (first_non_zero != 0) {
-    for (int i = first_non_zero; i < *length; ++i) {
-      buffer[i - first_non_zero] = buffer[i];
-    }
-    *length -= first_non_zero;
-    *decimal_point -= first_non_zero;
-  }
-}
-
-
-bool FastFixedDtoa(double v,
-                   int fractional_count,
-                   Vector<char> buffer,
-                   int* length,
-                   int* decimal_point) {
-  const uint32_t kMaxUInt32 = 0xFFFFFFFF;
-  uint64_t significand = Double(v).Significand();
-  int exponent = Double(v).Exponent();
-  // v = significand * 2^exponent (with significand a 53bit integer).
-  // If the exponent is larger than 20 (i.e. we may have a 73bit number) then we
-  // don't know how to compute the representation. 2^73 ~= 9.5*10^21.
-  // If necessary this limit could probably be increased, but we don't need
-  // more.
-  if (exponent > 20) return false;
-  if (fractional_count > 20) return false;
-  *length = 0;
-  // At most kDoubleSignificandSize bits of the significand are non-zero.
-  // Given a 64 bit integer we have 11 0s followed by 53 potentially non-zero
-  // bits:  0..11*..0xxx..53*..xx
-  if (exponent + kDoubleSignificandSize > 64) {
-    // The exponent must be > 11.
-    //
-    // We know that v = significand * 2^exponent.
-    // And the exponent > 11.
-    // We simplify the task by dividing v by 10^17.
-    // The quotient delivers the first digits, and the remainder fits into a 64
-    // bit number.
-    // Dividing by 10^17 is equivalent to dividing by 5^17*2^17.
-    const uint64_t kFive17 = UINT64_2PART_C(0xB1, A2BC2EC5);  // 5^17
-    uint64_t divisor = kFive17;
-    int divisor_power = 17;
-    uint64_t dividend = significand;
-    uint32_t quotient;
-    uint64_t remainder;
-    // Let v = f * 2^e with f == significand and e == exponent.
-    // Then need q (quotient) and r (remainder) as follows:
-    //   v            = q * 10^17       + r
-    //   f * 2^e      = q * 10^17       + r
-    //   f * 2^e      = q * 5^17 * 2^17 + r
-    // If e > 17 then
-    //   f * 2^(e-17) = q * 5^17        + r/2^17
-    // else
-    //   f  = q * 5^17 * 2^(17-e) + r/2^e
-    if (exponent > divisor_power) {
-      // We only allow exponents of up to 20 and therefore (17 - e) <= 3
-      dividend <<= exponent - divisor_power;
-      quotient = static_cast<uint32_t>(dividend / divisor);
-      remainder = (dividend % divisor) << divisor_power;
-    } else {
-      divisor <<= divisor_power - exponent;
-      quotient = static_cast<uint32_t>(dividend / divisor);
-      remainder = (dividend % divisor) << exponent;
-    }
-    FillDigits32(quotient, buffer, length);
-    FillDigits64FixedLength(remainder, buffer, length);
-    *decimal_point = *length;
-  } else if (exponent >= 0) {
-    // 0 <= exponent <= 11
-    significand <<= exponent;
-    FillDigits64(significand, buffer, length);
-    *decimal_point = *length;
-  } else if (exponent > -kDoubleSignificandSize) {
-    // We have to cut the number.
-    uint64_t integrals = significand >> -exponent;
-    uint64_t fractionals = significand - (integrals << -exponent);
-    if (integrals > kMaxUInt32) {
-      FillDigits64(integrals, buffer, length);
-    } else {
-      FillDigits32(static_cast<uint32_t>(integrals), buffer, length);
-    }
-    *decimal_point = *length;
-    FillFractionals(fractionals, exponent, fractional_count,
-                    buffer, length, decimal_point);
-  } else if (exponent < -128) {
-    // This configuration (with at most 20 digits) means that all digits must be
-    // 0.
-    ASSERT(fractional_count <= 20);
-    buffer[0] = '\0';
-    *length = 0;
-    *decimal_point = -fractional_count;
-  } else {
-    *decimal_point = 0;
-    FillFractionals(significand, exponent, fractional_count,
-                    buffer, length, decimal_point);
-  }
-  TrimZeros(buffer, length, decimal_point);
-  buffer[*length] = '\0';
-  if ((*length) == 0) {
-    // The string is empty and the decimal_point thus has no importance. Mimick
-    // Gay's dtoa and and set it to -fractional_count.
-    *decimal_point = -fractional_count;
-  }
-  return true;
-}
-
-}  // namespace double_conversion
diff --git a/c_src/double-conversion/fixed-dtoa.h b/c_src/double-conversion/fixed-dtoa.h
deleted file mode 100644
index 3bdd08e..0000000
--- a/c_src/double-conversion/fixed-dtoa.h
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef DOUBLE_CONVERSION_FIXED_DTOA_H_
-#define DOUBLE_CONVERSION_FIXED_DTOA_H_
-
-#include "utils.h"
-
-namespace double_conversion {
-
-// Produces digits necessary to print a given number with
-// 'fractional_count' digits after the decimal point.
-// The buffer must be big enough to hold the result plus one terminating null
-// character.
-//
-// The produced digits might be too short in which case the caller has to fill
-// the gaps with '0's.
-// Example: FastFixedDtoa(0.001, 5, ...) is allowed to return buffer = "1", and
-// decimal_point = -2.
-// Halfway cases are rounded towards +/-Infinity (away from 0). The call
-// FastFixedDtoa(0.15, 2, ...) thus returns buffer = "2", decimal_point = 0.
-// The returned buffer may contain digits that would be truncated from the
-// shortest representation of the input.
-//
-// This method only works for some parameters. If it can't handle the input it
-// returns false. The output is null-terminated when the function succeeds.
-bool FastFixedDtoa(double v, int fractional_count,
-                   Vector<char> buffer, int* length, int* decimal_point);
-
-}  // namespace double_conversion
-
-#endif  // DOUBLE_CONVERSION_FIXED_DTOA_H_
diff --git a/c_src/double-conversion/ieee.h b/c_src/double-conversion/ieee.h
deleted file mode 100644
index 4a5fe8f..0000000
--- a/c_src/double-conversion/ieee.h
+++ /dev/null
@@ -1,402 +0,0 @@
-// Copyright 2012 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef DOUBLE_CONVERSION_DOUBLE_H_
-#define DOUBLE_CONVERSION_DOUBLE_H_
-
-#include "diy-fp.h"
-
-namespace double_conversion {
-
-// We assume that doubles and uint64_t have the same endianness.
-static uint64_t double_to_uint64(double d) { return BitCast<uint64_t>(d); }
-static double uint64_to_double(uint64_t d64) { return BitCast<double>(d64); }
-static uint32_t float_to_uint32(float f) { return BitCast<uint32_t>(f); }
-static float uint32_to_float(uint32_t d32) { return BitCast<float>(d32); }
-
-// Helper functions for doubles.
-class Double {
- public:
-  static const uint64_t kSignMask = UINT64_2PART_C(0x80000000, 00000000);
-  static const uint64_t kExponentMask = UINT64_2PART_C(0x7FF00000, 00000000);
-  static const uint64_t kSignificandMask = UINT64_2PART_C(0x000FFFFF, FFFFFFFF);
-  static const uint64_t kHiddenBit = UINT64_2PART_C(0x00100000, 00000000);
-  static const int kPhysicalSignificandSize = 52;  // Excludes the hidden bit.
-  static const int kSignificandSize = 53;
-
-  Double() : d64_(0) {}
-  explicit Double(double d) : d64_(double_to_uint64(d)) {}
-  explicit Double(uint64_t d64) : d64_(d64) {}
-  explicit Double(DiyFp diy_fp)
-    : d64_(DiyFpToUint64(diy_fp)) {}
-
-  // The value encoded by this Double must be greater or equal to +0.0.
-  // It must not be special (infinity, or NaN).
-  DiyFp AsDiyFp() const {
-    ASSERT(Sign() > 0);
-    ASSERT(!IsSpecial());
-    return DiyFp(Significand(), Exponent());
-  }
-
-  // The value encoded by this Double must be strictly greater than 0.
-  DiyFp AsNormalizedDiyFp() const {
-    ASSERT(value() > 0.0);
-    uint64_t f = Significand();
-    int e = Exponent();
-
-    // The current double could be a denormal.
-    while ((f & kHiddenBit) == 0) {
-      f <<= 1;
-      e--;
-    }
-    // Do the final shifts in one go.
-    f <<= DiyFp::kSignificandSize - kSignificandSize;
-    e -= DiyFp::kSignificandSize - kSignificandSize;
-    return DiyFp(f, e);
-  }
-
-  // Returns the double's bit as uint64.
-  uint64_t AsUint64() const {
-    return d64_;
-  }
-
-  // Returns the next greater double. Returns +infinity on input +infinity.
-  double NextDouble() const {
-    if (d64_ == kInfinity) return Double(kInfinity).value();
-    if (Sign() < 0 && Significand() == 0) {
-      // -0.0
-      return 0.0;
-    }
-    if (Sign() < 0) {
-      return Double(d64_ - 1).value();
-    } else {
-      return Double(d64_ + 1).value();
-    }
-  }
-
-  double PreviousDouble() const {
-    if (d64_ == (kInfinity | kSignMask)) return -Infinity();
-    if (Sign() < 0) {
-      return Double(d64_ + 1).value();
-    } else {
-      if (Significand() == 0) return -0.0;
-      return Double(d64_ - 1).value();
-    }
-  }
-
-  int Exponent() const {
-    if (IsDenormal()) return kDenormalExponent;
-
-    uint64_t d64 = AsUint64();
-    int biased_e =
-        static_cast<int>((d64 & kExponentMask) >> kPhysicalSignificandSize);
-    return biased_e - kExponentBias;
-  }
-
-  uint64_t Significand() const {
-    uint64_t d64 = AsUint64();
-    uint64_t significand = d64 & kSignificandMask;
-    if (!IsDenormal()) {
-      return significand + kHiddenBit;
-    } else {
-      return significand;
-    }
-  }
-
-  // Returns true if the double is a denormal.
-  bool IsDenormal() const {
-    uint64_t d64 = AsUint64();
-    return (d64 & kExponentMask) == 0;
-  }
-
-  // We consider denormals not to be special.
-  // Hence only Infinity and NaN are special.
-  bool IsSpecial() const {
-    uint64_t d64 = AsUint64();
-    return (d64 & kExponentMask) == kExponentMask;
-  }
-
-  bool IsNan() const {
-    uint64_t d64 = AsUint64();
-    return ((d64 & kExponentMask) == kExponentMask) &&
-        ((d64 & kSignificandMask) != 0);
-  }
-
-  bool IsInfinite() const {
-    uint64_t d64 = AsUint64();
-    return ((d64 & kExponentMask) == kExponentMask) &&
-        ((d64 & kSignificandMask) == 0);
-  }
-
-  int Sign() const {
-    uint64_t d64 = AsUint64();
-    return (d64 & kSignMask) == 0? 1: -1;
-  }
-
-  // Precondition: the value encoded by this Double must be greater or equal
-  // than +0.0.
-  DiyFp UpperBoundary() const {
-    ASSERT(Sign() > 0);
-    return DiyFp(Significand() * 2 + 1, Exponent() - 1);
-  }
-
-  // Computes the two boundaries of this.
-  // The bigger boundary (m_plus) is normalized. The lower boundary has the same
-  // exponent as m_plus.
-  // Precondition: the value encoded by this Double must be greater than 0.
-  void NormalizedBoundaries(DiyFp* out_m_minus, DiyFp* out_m_plus) const {
-    ASSERT(value() > 0.0);
-    DiyFp v = this->AsDiyFp();
-    DiyFp m_plus = DiyFp::Normalize(DiyFp((v.f() << 1) + 1, v.e() - 1));
-    DiyFp m_minus;
-    if (LowerBoundaryIsCloser()) {
-      m_minus = DiyFp((v.f() << 2) - 1, v.e() - 2);
-    } else {
-      m_minus = DiyFp((v.f() << 1) - 1, v.e() - 1);
-    }
-    m_minus.set_f(m_minus.f() << (m_minus.e() - m_plus.e()));
-    m_minus.set_e(m_plus.e());
-    *out_m_plus = m_plus;
-    *out_m_minus = m_minus;
-  }
-
-  bool LowerBoundaryIsCloser() const {
-    // The boundary is closer if the significand is of the form f == 2^p-1 then
-    // the lower boundary is closer.
-    // Think of v = 1000e10 and v- = 9999e9.
-    // Then the boundary (== (v - v-)/2) is not just at a distance of 1e9 but
-    // at a distance of 1e8.
-    // The only exception is for the smallest normal: the largest denormal is
-    // at the same distance as its successor.
-    // Note: denormals have the same exponent as the smallest normals.
-    bool physical_significand_is_zero = ((AsUint64() & kSignificandMask) == 0);
-    return physical_significand_is_zero && (Exponent() != kDenormalExponent);
-  }
-
-  double value() const { return uint64_to_double(d64_); }
-
-  // Returns the significand size for a given order of magnitude.
-  // If v = f*2^e with 2^p-1 <= f <= 2^p then p+e is v's order of magnitude.
-  // This function returns the number of significant binary digits v will have
-  // once it's encoded into a double. In almost all cases this is equal to
-  // kSignificandSize. The only exceptions are denormals. They start with
-  // leading zeroes and their effective significand-size is hence smaller.
-  static int SignificandSizeForOrderOfMagnitude(int order) {
-    if (order >= (kDenormalExponent + kSignificandSize)) {
-      return kSignificandSize;
-    }
-    if (order <= kDenormalExponent) return 0;
-    return order - kDenormalExponent;
-  }
-
-  static double Infinity() {
-    return Double(kInfinity).value();
-  }
-
-  static double NaN() {
-    return Double(kNaN).value();
-  }
-
- private:
-  static const int kExponentBias = 0x3FF + kPhysicalSignificandSize;
-  static const int kDenormalExponent = -kExponentBias + 1;
-  static const int kMaxExponent = 0x7FF - kExponentBias;
-  static const uint64_t kInfinity = UINT64_2PART_C(0x7FF00000, 00000000);
-  static const uint64_t kNaN = UINT64_2PART_C(0x7FF80000, 00000000);
-
-  const uint64_t d64_;
-
-  static uint64_t DiyFpToUint64(DiyFp diy_fp) {
-    uint64_t significand = diy_fp.f();
-    int exponent = diy_fp.e();
-    while (significand > kHiddenBit + kSignificandMask) {
-      significand >>= 1;
-      exponent++;
-    }
-    if (exponent >= kMaxExponent) {
-      return kInfinity;
-    }
-    if (exponent < kDenormalExponent) {
-      return 0;
-    }
-    while (exponent > kDenormalExponent && (significand & kHiddenBit) == 0) {
-      significand <<= 1;
-      exponent--;
-    }
-    uint64_t biased_exponent;
-    if (exponent == kDenormalExponent && (significand & kHiddenBit) == 0) {
-      biased_exponent = 0;
-    } else {
-      biased_exponent = static_cast<uint64_t>(exponent + kExponentBias);
-    }
-    return (significand & kSignificandMask) |
-        (biased_exponent << kPhysicalSignificandSize);
-  }
-
-  DC_DISALLOW_COPY_AND_ASSIGN(Double);
-};
-
-class Single {
- public:
-  static const uint32_t kSignMask = 0x80000000;
-  static const uint32_t kExponentMask = 0x7F800000;
-  static const uint32_t kSignificandMask = 0x007FFFFF;
-  static const uint32_t kHiddenBit = 0x00800000;
-  static const int kPhysicalSignificandSize = 23;  // Excludes the hidden bit.
-  static const int kSignificandSize = 24;
-
-  Single() : d32_(0) {}
-  explicit Single(float f) : d32_(float_to_uint32(f)) {}
-  explicit Single(uint32_t d32) : d32_(d32) {}
-
-  // The value encoded by this Single must be greater or equal to +0.0.
-  // It must not be special (infinity, or NaN).
-  DiyFp AsDiyFp() const {
-    ASSERT(Sign() > 0);
-    ASSERT(!IsSpecial());
-    return DiyFp(Significand(), Exponent());
-  }
-
-  // Returns the single's bit as uint64.
-  uint32_t AsUint32() const {
-    return d32_;
-  }
-
-  int Exponent() const {
-    if (IsDenormal()) return kDenormalExponent;
-
-    uint32_t d32 = AsUint32();
-    int biased_e =
-        static_cast<int>((d32 & kExponentMask) >> kPhysicalSignificandSize);
-    return biased_e - kExponentBias;
-  }
-
-  uint32_t Significand() const {
-    uint32_t d32 = AsUint32();
-    uint32_t significand = d32 & kSignificandMask;
-    if (!IsDenormal()) {
-      return significand + kHiddenBit;
-    } else {
-      return significand;
-    }
-  }
-
-  // Returns true if the single is a denormal.
-  bool IsDenormal() const {
-    uint32_t d32 = AsUint32();
-    return (d32 & kExponentMask) == 0;
-  }
-
-  // We consider denormals not to be special.
-  // Hence only Infinity and NaN are special.
-  bool IsSpecial() const {
-    uint32_t d32 = AsUint32();
-    return (d32 & kExponentMask) == kExponentMask;
-  }
-
-  bool IsNan() const {
-    uint32_t d32 = AsUint32();
-    return ((d32 & kExponentMask) == kExponentMask) &&
-        ((d32 & kSignificandMask) != 0);
-  }
-
-  bool IsInfinite() const {
-    uint32_t d32 = AsUint32();
-    return ((d32 & kExponentMask) == kExponentMask) &&
-        ((d32 & kSignificandMask) == 0);
-  }
-
-  int Sign() const {
-    uint32_t d32 = AsUint32();
-    return (d32 & kSignMask) == 0? 1: -1;
-  }
-
-  // Computes the two boundaries of this.
-  // The bigger boundary (m_plus) is normalized. The lower boundary has the same
-  // exponent as m_plus.
-  // Precondition: the value encoded by this Single must be greater than 0.
-  void NormalizedBoundaries(DiyFp* out_m_minus, DiyFp* out_m_plus) const {
-    ASSERT(value() > 0.0);
-    DiyFp v = this->AsDiyFp();
-    DiyFp m_plus = DiyFp::Normalize(DiyFp((v.f() << 1) + 1, v.e() - 1));
-    DiyFp m_minus;
-    if (LowerBoundaryIsCloser()) {
-      m_minus = DiyFp((v.f() << 2) - 1, v.e() - 2);
-    } else {
-      m_minus = DiyFp((v.f() << 1) - 1, v.e() - 1);
-    }
-    m_minus.set_f(m_minus.f() << (m_minus.e() - m_plus.e()));
-    m_minus.set_e(m_plus.e());
-    *out_m_plus = m_plus;
-    *out_m_minus = m_minus;
-  }
-
-  // Precondition: the value encoded by this Single must be greater or equal
-  // than +0.0.
-  DiyFp UpperBoundary() const {
-    ASSERT(Sign() > 0);
-    return DiyFp(Significand() * 2 + 1, Exponent() - 1);
-  }
-
-  bool LowerBoundaryIsCloser() const {
-    // The boundary is closer if the significand is of the form f == 2^p-1 then
-    // the lower boundary is closer.
-    // Think of v = 1000e10 and v- = 9999e9.
-    // Then the boundary (== (v - v-)/2) is not just at a distance of 1e9 but
-    // at a distance of 1e8.
-    // The only exception is for the smallest normal: the largest denormal is
-    // at the same distance as its successor.
-    // Note: denormals have the same exponent as the smallest normals.
-    bool physical_significand_is_zero = ((AsUint32() & kSignificandMask) == 0);
-    return physical_significand_is_zero && (Exponent() != kDenormalExponent);
-  }
-
-  float value() const { return uint32_to_float(d32_); }
-
-  static float Infinity() {
-    return Single(kInfinity).value();
-  }
-
-  static float NaN() {
-    return Single(kNaN).value();
-  }
-
- private:
-  static const int kExponentBias = 0x7F + kPhysicalSignificandSize;
-  static const int kDenormalExponent = -kExponentBias + 1;
-  static const int kMaxExponent = 0xFF - kExponentBias;
-  static const uint32_t kInfinity = 0x7F800000;
-  static const uint32_t kNaN = 0x7FC00000;
-
-  const uint32_t d32_;
-
-  DC_DISALLOW_COPY_AND_ASSIGN(Single);
-};
-
-}  // namespace double_conversion
-
-#endif  // DOUBLE_CONVERSION_DOUBLE_H_
diff --git a/c_src/double-conversion/strtod.cc b/c_src/double-conversion/strtod.cc
deleted file mode 100644
index a75cf5d..0000000
--- a/c_src/double-conversion/strtod.cc
+++ /dev/null
@@ -1,580 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#include <climits>
-#include <cstdarg>
-
-#include "bignum.h"
-#include "cached-powers.h"
-#include "ieee.h"
-#include "strtod.h"
-
-namespace double_conversion {
-
-// 2^53 = 9007199254740992.
-// Any integer with at most 15 decimal digits will hence fit into a double
-// (which has a 53bit significand) without loss of precision.
-static const int kMaxExactDoubleIntegerDecimalDigits = 15;
-// 2^64 = 18446744073709551616 > 10^19
-static const int kMaxUint64DecimalDigits = 19;
-
-// Max double: 1.7976931348623157 x 10^308
-// Min non-zero double: 4.9406564584124654 x 10^-324
-// Any x >= 10^309 is interpreted as +infinity.
-// Any x <= 10^-324 is interpreted as 0.
-// Note that 2.5e-324 (despite being smaller than the min double) will be read
-// as non-zero (equal to the min non-zero double).
-static const int kMaxDecimalPower = 309;
-static const int kMinDecimalPower = -324;
-
-// 2^64 = 18446744073709551616
-static const uint64_t kMaxUint64 = UINT64_2PART_C(0xFFFFFFFF, FFFFFFFF);
-
-
-static const double exact_powers_of_ten[] = {
-  1.0,  // 10^0
-  10.0,
-  100.0,
-  1000.0,
-  10000.0,
-  100000.0,
-  1000000.0,
-  10000000.0,
-  100000000.0,
-  1000000000.0,
-  10000000000.0,  // 10^10
-  100000000000.0,
-  1000000000000.0,
-  10000000000000.0,
-  100000000000000.0,
-  1000000000000000.0,
-  10000000000000000.0,
-  100000000000000000.0,
-  1000000000000000000.0,
-  10000000000000000000.0,
-  100000000000000000000.0,  // 10^20
-  1000000000000000000000.0,
-  // 10^22 = 0x21e19e0c9bab2400000 = 0x878678326eac9 * 2^22
-  10000000000000000000000.0
-};
-static const int kExactPowersOfTenSize = ARRAY_SIZE(exact_powers_of_ten);
-
-// Maximum number of significant digits in the decimal representation.
-// In fact the value is 772 (see conversions.cc), but to give us some margin
-// we round up to 780.
-static const int kMaxSignificantDecimalDigits = 780;
-
-static Vector<const char> TrimLeadingZeros(Vector<const char> buffer) {
-  for (int i = 0; i < buffer.length(); i++) {
-    if (buffer[i] != '0') {
-      return buffer.SubVector(i, buffer.length());
-    }
-  }
-  return Vector<const char>(buffer.start(), 0);
-}
-
-
-static Vector<const char> TrimTrailingZeros(Vector<const char> buffer) {
-  for (int i = buffer.length() - 1; i >= 0; --i) {
-    if (buffer[i] != '0') {
-      return buffer.SubVector(0, i + 1);
-    }
-  }
-  return Vector<const char>(buffer.start(), 0);
-}
-
-
-static void CutToMaxSignificantDigits(Vector<const char> buffer,
-                                       int exponent,
-                                       char* significant_buffer,
-                                       int* significant_exponent) {
-  for (int i = 0; i < kMaxSignificantDecimalDigits - 1; ++i) {
-    significant_buffer[i] = buffer[i];
-  }
-  // The input buffer has been trimmed. Therefore the last digit must be
-  // different from '0'.
-  ASSERT(buffer[buffer.length() - 1] != '0');
-  // Set the last digit to be non-zero. This is sufficient to guarantee
-  // correct rounding.
-  significant_buffer[kMaxSignificantDecimalDigits - 1] = '1';
-  *significant_exponent =
-      exponent + (buffer.length() - kMaxSignificantDecimalDigits);
-}
-
-
-// Trims the buffer and cuts it to at most kMaxSignificantDecimalDigits.
-// If possible the input-buffer is reused, but if the buffer needs to be
-// modified (due to cutting), then the input needs to be copied into the
-// buffer_copy_space.
-static void TrimAndCut(Vector<const char> buffer, int exponent,
-                       char* buffer_copy_space, int space_size,
-                       Vector<const char>* trimmed, int* updated_exponent) {
-  Vector<const char> left_trimmed = TrimLeadingZeros(buffer);
-  Vector<const char> right_trimmed = TrimTrailingZeros(left_trimmed);
-  exponent += left_trimmed.length() - right_trimmed.length();
-  if (right_trimmed.length() > kMaxSignificantDecimalDigits) {
-    (void) space_size;  // Mark variable as used.
-    ASSERT(space_size >= kMaxSignificantDecimalDigits);
-    CutToMaxSignificantDigits(right_trimmed, exponent,
-                              buffer_copy_space, updated_exponent);
-    *trimmed = Vector<const char>(buffer_copy_space,
-                                 kMaxSignificantDecimalDigits);
-  } else {
-    *trimmed = right_trimmed;
-    *updated_exponent = exponent;
-  }
-}
-
-
-// Reads digits from the buffer and converts them to a uint64.
-// Reads in as many digits as fit into a uint64.
-// When the string starts with "1844674407370955161" no further digit is read.
-// Since 2^64 = 18446744073709551616 it would still be possible read another
-// digit if it was less or equal than 6, but this would complicate the code.
-static uint64_t ReadUint64(Vector<const char> buffer,
-                           int* number_of_read_digits) {
-  uint64_t result = 0;
-  int i = 0;
-  while (i < buffer.length() && result <= (kMaxUint64 / 10 - 1)) {
-    int digit = buffer[i++] - '0';
-    ASSERT(0 <= digit && digit <= 9);
-    result = 10 * result + digit;
-  }
-  *number_of_read_digits = i;
-  return result;
-}
-
-
-// Reads a DiyFp from the buffer.
-// The returned DiyFp is not necessarily normalized.
-// If remaining_decimals is zero then the returned DiyFp is accurate.
-// Otherwise it has been rounded and has error of at most 1/2 ulp.
-static void ReadDiyFp(Vector<const char> buffer,
-                      DiyFp* result,
-                      int* remaining_decimals) {
-  int read_digits;
-  uint64_t significand = ReadUint64(buffer, &read_digits);
-  if (buffer.length() == read_digits) {
-    *result = DiyFp(significand, 0);
-    *remaining_decimals = 0;
-  } else {
-    // Round the significand.
-    if (buffer[read_digits] >= '5') {
-      significand++;
-    }
-    // Compute the binary exponent.
-    int exponent = 0;
-    *result = DiyFp(significand, exponent);
-    *remaining_decimals = buffer.length() - read_digits;
-  }
-}
-
-
-static bool DoubleStrtod(Vector<const char> trimmed,
-                         int exponent,
-                         double* result) {
-#if !defined(DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS)
-  // On x86 the floating-point stack can be 64 or 80 bits wide. If it is
-  // 80 bits wide (as is the case on Linux) then double-rounding occurs and the
-  // result is not accurate.
-  // We know that Windows32 uses 64 bits and is therefore accurate.
-  // Note that the ARM simulator is compiled for 32bits. It therefore exhibits
-  // the same problem.
-  return false;
-#else
-  if (trimmed.length() <= kMaxExactDoubleIntegerDecimalDigits) {
-    int read_digits;
-    // The trimmed input fits into a double.
-    // If the 10^exponent (resp. 10^-exponent) fits into a double too then we
-    // can compute the result-double simply by multiplying (resp. dividing) the
-    // two numbers.
-    // This is possible because IEEE guarantees that floating-point operations
-    // return the best possible approximation.
-    if (exponent < 0 && -exponent < kExactPowersOfTenSize) {
-      // 10^-exponent fits into a double.
-      *result = static_cast<double>(ReadUint64(trimmed, &read_digits));
-      ASSERT(read_digits == trimmed.length());
-      *result /= exact_powers_of_ten[-exponent];
-      return true;
-    }
-    if (0 <= exponent && exponent < kExactPowersOfTenSize) {
-      // 10^exponent fits into a double.
-      *result = static_cast<double>(ReadUint64(trimmed, &read_digits));
-      ASSERT(read_digits == trimmed.length());
-      *result *= exact_powers_of_ten[exponent];
-      return true;
-    }
-    int remaining_digits =
-        kMaxExactDoubleIntegerDecimalDigits - trimmed.length();
-    if ((0 <= exponent) &&
-        (exponent - remaining_digits < kExactPowersOfTenSize)) {
-      // The trimmed string was short and we can multiply it with
-      // 10^remaining_digits. As a result the remaining exponent now fits
-      // into a double too.
-      *result = static_cast<double>(ReadUint64(trimmed, &read_digits));
-      ASSERT(read_digits == trimmed.length());
-      *result *= exact_powers_of_ten[remaining_digits];
-      *result *= exact_powers_of_ten[exponent - remaining_digits];
-      return true;
-    }
-  }
-  return false;
-#endif
-}
-
-
-// Returns 10^exponent as an exact DiyFp.
-// The given exponent must be in the range [1; kDecimalExponentDistance[.
-static DiyFp AdjustmentPowerOfTen(int exponent) {
-  ASSERT(0 < exponent);
-  ASSERT(exponent < PowersOfTenCache::kDecimalExponentDistance);
-  // Simply hardcode the remaining powers for the given decimal exponent
-  // distance.
-  ASSERT(PowersOfTenCache::kDecimalExponentDistance == 8);
-  switch (exponent) {
-    case 1: return DiyFp(UINT64_2PART_C(0xa0000000, 00000000), -60);
-    case 2: return DiyFp(UINT64_2PART_C(0xc8000000, 00000000), -57);
-    case 3: return DiyFp(UINT64_2PART_C(0xfa000000, 00000000), -54);
-    case 4: return DiyFp(UINT64_2PART_C(0x9c400000, 00000000), -50);
-    case 5: return DiyFp(UINT64_2PART_C(0xc3500000, 00000000), -47);
-    case 6: return DiyFp(UINT64_2PART_C(0xf4240000, 00000000), -44);
-    case 7: return DiyFp(UINT64_2PART_C(0x98968000, 00000000), -40);
-    default:
-      UNREACHABLE();
-  }
-}
-
-
-// If the function returns true then the result is the correct double.
-// Otherwise it is either the correct double or the double that is just below
-// the correct double.
-static bool DiyFpStrtod(Vector<const char> buffer,
-                        int exponent,
-                        double* result) {
-  DiyFp input;
-  int remaining_decimals;
-  ReadDiyFp(buffer, &input, &remaining_decimals);
-  // Since we may have dropped some digits the input is not accurate.
-  // If remaining_decimals is different than 0 than the error is at most
-  // .5 ulp (unit in the last place).
-  // We don't want to deal with fractions and therefore keep a common
-  // denominator.
-  const int kDenominatorLog = 3;
-  const int kDenominator = 1 << kDenominatorLog;
-  // Move the remaining decimals into the exponent.
-  exponent += remaining_decimals;
-  uint64_t error = (remaining_decimals == 0 ? 0 : kDenominator / 2);
-
-  int old_e = input.e();
-  input.Normalize();
-  error <<= old_e - input.e();
-
-  ASSERT(exponent <= PowersOfTenCache::kMaxDecimalExponent);
-  if (exponent < PowersOfTenCache::kMinDecimalExponent) {
-    *result = 0.0;
-    return true;
-  }
-  DiyFp cached_power;
-  int cached_decimal_exponent;
-  PowersOfTenCache::GetCachedPowerForDecimalExponent(exponent,
-                                                     &cached_power,
-                                                     &cached_decimal_exponent);
-
-  if (cached_decimal_exponent != exponent) {
-    int adjustment_exponent = exponent - cached_decimal_exponent;
-    DiyFp adjustment_power = AdjustmentPowerOfTen(adjustment_exponent);
-    input.Multiply(adjustment_power);
-    if (kMaxUint64DecimalDigits - buffer.length() >= adjustment_exponent) {
-      // The product of input with the adjustment power fits into a 64 bit
-      // integer.
-      ASSERT(DiyFp::kSignificandSize == 64);
-    } else {
-      // The adjustment power is exact. There is hence only an error of 0.5.
-      error += kDenominator / 2;
-    }
-  }
-
-  input.Multiply(cached_power);
-  // The error introduced by a multiplication of a*b equals
-  //   error_a + error_b + error_a*error_b/2^64 + 0.5
-  // Substituting a with 'input' and b with 'cached_power' we have
-  //   error_b = 0.5  (all cached powers have an error of less than 0.5 ulp),
-  //   error_ab = 0 or 1 / kDenominator > error_a*error_b/ 2^64
-  int error_b = kDenominator / 2;
-  int error_ab = (error == 0 ? 0 : 1);  // We round up to 1.
-  int fixed_error = kDenominator / 2;
-  error += error_b + error_ab + fixed_error;
-
-  old_e = input.e();
-  input.Normalize();
-  error <<= old_e - input.e();
-
-  // See if the double's significand changes if we add/subtract the error.
-  int order_of_magnitude = DiyFp::kSignificandSize + input.e();
-  int effective_significand_size =
-      Double::SignificandSizeForOrderOfMagnitude(order_of_magnitude);
-  int precision_digits_count =
-      DiyFp::kSignificandSize - effective_significand_size;
-  if (precision_digits_count + kDenominatorLog >= DiyFp::kSignificandSize) {
-    // This can only happen for very small denormals. In this case the
-    // half-way multiplied by the denominator exceeds the range of an uint64.
-    // Simply shift everything to the right.
-    int shift_amount = (precision_digits_count + kDenominatorLog) -
-        DiyFp::kSignificandSize + 1;
-    input.set_f(input.f() >> shift_amount);
-    input.set_e(input.e() + shift_amount);
-    // We add 1 for the lost precision of error, and kDenominator for
-    // the lost precision of input.f().
-    error = (error >> shift_amount) + 1 + kDenominator;
-    precision_digits_count -= shift_amount;
-  }
-  // We use uint64_ts now. This only works if the DiyFp uses uint64_ts too.
-  ASSERT(DiyFp::kSignificandSize == 64);
-  ASSERT(precision_digits_count < 64);
-  uint64_t one64 = 1;
-  uint64_t precision_bits_mask = (one64 << precision_digits_count) - 1;
-  uint64_t precision_bits = input.f() & precision_bits_mask;
-  uint64_t half_way = one64 << (precision_digits_count - 1);
-  precision_bits *= kDenominator;
-  half_way *= kDenominator;
-  DiyFp rounded_input(input.f() >> precision_digits_count,
-                      input.e() + precision_digits_count);
-  if (precision_bits >= half_way + error) {
-    rounded_input.set_f(rounded_input.f() + 1);
-  }
-  // If the last_bits are too close to the half-way case than we are too
-  // inaccurate and round down. In this case we return false so that we can
-  // fall back to a more precise algorithm.
-
-  *result = Double(rounded_input).value();
-  if (half_way - error < precision_bits && precision_bits < half_way + error) {
-    // Too imprecise. The caller will have to fall back to a slower version.
-    // However the returned number is guaranteed to be either the correct
-    // double, or the next-lower double.
-    return false;
-  } else {
-    return true;
-  }
-}
-
-
-// Returns
-//   - -1 if buffer*10^exponent < diy_fp.
-//   -  0 if buffer*10^exponent == diy_fp.
-//   - +1 if buffer*10^exponent > diy_fp.
-// Preconditions:
-//   buffer.length() + exponent <= kMaxDecimalPower + 1
-//   buffer.length() + exponent > kMinDecimalPower
-//   buffer.length() <= kMaxDecimalSignificantDigits
-static int CompareBufferWithDiyFp(Vector<const char> buffer,
-                                  int exponent,
-                                  DiyFp diy_fp) {
-  ASSERT(buffer.length() + exponent <= kMaxDecimalPower + 1);
-  ASSERT(buffer.length() + exponent > kMinDecimalPower);
-  ASSERT(buffer.length() <= kMaxSignificantDecimalDigits);
-  // Make sure that the Bignum will be able to hold all our numbers.
-  // Our Bignum implementation has a separate field for exponents. Shifts will
-  // consume at most one bigit (< 64 bits).
-  // ln(10) == 3.3219...
-  ASSERT(((kMaxDecimalPower + 1) * 333 / 100) < Bignum::kMaxSignificantBits);
-  Bignum buffer_bignum;
-  Bignum diy_fp_bignum;
-  buffer_bignum.AssignDecimalString(buffer);
-  diy_fp_bignum.AssignUInt64(diy_fp.f());
-  if (exponent >= 0) {
-    buffer_bignum.MultiplyByPowerOfTen(exponent);
-  } else {
-    diy_fp_bignum.MultiplyByPowerOfTen(-exponent);
-  }
-  if (diy_fp.e() > 0) {
-    diy_fp_bignum.ShiftLeft(diy_fp.e());
-  } else {
-    buffer_bignum.ShiftLeft(-diy_fp.e());
-  }
-  return Bignum::Compare(buffer_bignum, diy_fp_bignum);
-}
-
-
-// Returns true if the guess is the correct double.
-// Returns false, when guess is either correct or the next-lower double.
-static bool ComputeGuess(Vector<const char> trimmed, int exponent,
-                         double* guess) {
-  if (trimmed.length() == 0) {
-    *guess = 0.0;
-    return true;
-  }
-  if (exponent + trimmed.length() - 1 >= kMaxDecimalPower) {
-    *guess = Double::Infinity();
-    return true;
-  }
-  if (exponent + trimmed.length() <= kMinDecimalPower) {
-    *guess = 0.0;
-    return true;
-  }
-
-  if (DoubleStrtod(trimmed, exponent, guess) ||
-      DiyFpStrtod(trimmed, exponent, guess)) {
-    return true;
-  }
-  if (*guess == Double::Infinity()) {
-    return true;
-  }
-  return false;
-}
-
-double Strtod(Vector<const char> buffer, int exponent) {
-  char copy_buffer[kMaxSignificantDecimalDigits];
-  Vector<const char> trimmed;
-  int updated_exponent;
-  TrimAndCut(buffer, exponent, copy_buffer, kMaxSignificantDecimalDigits,
-             &trimmed, &updated_exponent);
-  exponent = updated_exponent;
-
-  double guess;
-  bool is_correct = ComputeGuess(trimmed, exponent, &guess);
-  if (is_correct) return guess;
-
-  DiyFp upper_boundary = Double(guess).UpperBoundary();
-  int comparison = CompareBufferWithDiyFp(trimmed, exponent, upper_boundary);
-  if (comparison < 0) {
-    return guess;
-  } else if (comparison > 0) {
-    return Double(guess).NextDouble();
-  } else if ((Double(guess).Significand() & 1) == 0) {
-    // Round towards even.
-    return guess;
-  } else {
-    return Double(guess).NextDouble();
-  }
-}
-
-static float SanitizedDoubletof(double d) {
-  ASSERT(d >= 0.0);
-  // ASAN has a sanitize check that disallows casting doubles to floats if
-  // they are too big.
-  // https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#available-checks
-  // The behavior should be covered by IEEE 754, but some projects use this
-  // flag, so work around it.
-  float max_finite = 3.4028234663852885981170418348451692544e+38;
-  // The half-way point between the max-finite and infinity value.
-  // Since infinity has an even significand everything equal or greater than
-  // this value should become infinity.
-  double half_max_finite_infinity =
-      3.40282356779733661637539395458142568448e+38;
-  if (d >= max_finite) {
-    if (d >= half_max_finite_infinity) {
-      return Single::Infinity();
-    } else {
-      return max_finite;
-    }
-  } else {
-    return static_cast<float>(d);
-  }
-}
-
-float Strtof(Vector<const char> buffer, int exponent) {
-  char copy_buffer[kMaxSignificantDecimalDigits];
-  Vector<const char> trimmed;
-  int updated_exponent;
-  TrimAndCut(buffer, exponent, copy_buffer, kMaxSignificantDecimalDigits,
-             &trimmed, &updated_exponent);
-  exponent = updated_exponent;
-
-  double double_guess;
-  bool is_correct = ComputeGuess(trimmed, exponent, &double_guess);
-
-  float float_guess = SanitizedDoubletof(double_guess);
-  if (float_guess == double_guess) {
-    // This shortcut triggers for integer values.
-    return float_guess;
-  }
-
-  // We must catch double-rounding. Say the double has been rounded up, and is
-  // now a boundary of a float, and rounds up again. This is why we have to
-  // look at previous too.
-  // Example (in decimal numbers):
-  //    input: 12349
-  //    high-precision (4 digits): 1235
-  //    low-precision (3 digits):
-  //       when read from input: 123
-  //       when rounded from high precision: 124.
-  // To do this we simply look at the neigbors of the correct result and see
-  // if they would round to the same float. If the guess is not correct we have
-  // to look at four values (since two different doubles could be the correct
-  // double).
-
-  double double_next = Double(double_guess).NextDouble();
-  double double_previous = Double(double_guess).PreviousDouble();
-
-  float f1 = SanitizedDoubletof(double_previous);
-  float f2 = float_guess;
-  float f3 = SanitizedDoubletof(double_next);
-  float f4;
-  if (is_correct) {
-    f4 = f3;
-  } else {
-    double double_next2 = Double(double_next).NextDouble();
-    f4 = SanitizedDoubletof(double_next2);
-  }
-  (void) f2;  // Mark variable as used.
-  ASSERT(f1 <= f2 && f2 <= f3 && f3 <= f4);
-
-  // If the guess doesn't lie near a single-precision boundary we can simply
-  // return its float-value.
-  if (f1 == f4) {
-    return float_guess;
-  }
-
-  ASSERT((f1 != f2 && f2 == f3 && f3 == f4) ||
-         (f1 == f2 && f2 != f3 && f3 == f4) ||
-         (f1 == f2 && f2 == f3 && f3 != f4));
-
-  // guess and next are the two possible candidates (in the same way that
-  // double_guess was the lower candidate for a double-precision guess).
-  float guess = f1;
-  float next = f4;
-  DiyFp upper_boundary;
-  if (guess == 0.0f) {
-    float min_float = 1e-45f;
-    upper_boundary = Double(static_cast<double>(min_float) / 2).AsDiyFp();
-  } else {
-    upper_boundary = Single(guess).UpperBoundary();
-  }
-  int comparison = CompareBufferWithDiyFp(trimmed, exponent, upper_boundary);
-  if (comparison < 0) {
-    return guess;
-  } else if (comparison > 0) {
-    return next;
-  } else if ((Single(guess).Significand() & 1) == 0) {
-    // Round towards even.
-    return guess;
-  } else {
-    return next;
-  }
-}
-
-}  // namespace double_conversion
diff --git a/c_src/double-conversion/strtod.h b/c_src/double-conversion/strtod.h
deleted file mode 100644
index ed0293b..0000000
--- a/c_src/double-conversion/strtod.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef DOUBLE_CONVERSION_STRTOD_H_
-#define DOUBLE_CONVERSION_STRTOD_H_
-
-#include "utils.h"
-
-namespace double_conversion {
-
-// The buffer must only contain digits in the range [0-9]. It must not
-// contain a dot or a sign. It must not start with '0', and must not be empty.
-double Strtod(Vector<const char> buffer, int exponent);
-
-// The buffer must only contain digits in the range [0-9]. It must not
-// contain a dot or a sign. It must not start with '0', and must not be empty.
-float Strtof(Vector<const char> buffer, int exponent);
-
-}  // namespace double_conversion
-
-#endif  // DOUBLE_CONVERSION_STRTOD_H_
diff --git a/c_src/double-conversion/utils.h b/c_src/double-conversion/utils.h
deleted file mode 100644
index 4328344..0000000
--- a/c_src/double-conversion/utils.h
+++ /dev/null
@@ -1,367 +0,0 @@
-// Copyright 2010 the V8 project authors. All rights reserved.
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-//       notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-//       copyright notice, this list of conditions and the following
-//       disclaimer in the documentation and/or other materials provided
-//       with the distribution.
-//     * Neither the name of Google Inc. nor the names of its
-//       contributors may be used to endorse or promote products derived
-//       from this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-#ifndef DOUBLE_CONVERSION_UTILS_H_
-#define DOUBLE_CONVERSION_UTILS_H_
-
-#include <cstdlib>
-#include <cstring>
-
-#include <cassert>
-#ifndef ASSERT
-#define ASSERT(condition)         \
-    assert(condition);
-#endif
-#ifndef UNIMPLEMENTED
-#define UNIMPLEMENTED() (abort())
-#endif
-#ifndef DOUBLE_CONVERSION_NO_RETURN
-#ifdef _MSC_VER
-#define DOUBLE_CONVERSION_NO_RETURN __declspec(noreturn)
-#else
-#define DOUBLE_CONVERSION_NO_RETURN __attribute__((noreturn))
-#endif
-#endif
-#ifndef UNREACHABLE
-#ifdef _MSC_VER
-void DOUBLE_CONVERSION_NO_RETURN abort_noreturn();
-inline void abort_noreturn() { abort(); }
-#define UNREACHABLE()   (abort_noreturn())
-#else
-#define UNREACHABLE()   (abort())
-#endif
-#endif
-
-#ifndef DOUBLE_CONVERSION_UNUSED
-#ifdef __GNUC__
-#define DOUBLE_CONVERSION_UNUSED __attribute__((unused))
-#else
-#define DOUBLE_CONVERSION_UNUSED
-#endif
-#endif
-
-// Double operations detection based on target architecture.
-// Linux uses a 80bit wide floating point stack on x86. This induces double
-// rounding, which in turn leads to wrong results.
-// An easy way to test if the floating-point operations are correct is to
-// evaluate: 89255.0/1e22. If the floating-point stack is 64 bits wide then
-// the result is equal to 89255e-22.
-// The best way to test this, is to create a division-function and to compare
-// the output of the division with the expected result. (Inlining must be
-// disabled.)
-// On Linux,x86 89255e-22 != Div_double(89255.0/1e22)
-//
-// For example:
-/*
-// -- in div.c
-double Div_double(double x, double y) { return x / y; }
-
-// -- in main.c
-double Div_double(double x, double y);  // Forward declaration.
-
-int main(int argc, char** argv) {
-  return Div_double(89255.0, 1e22) == 89255e-22;
-}
-*/
-// Run as follows ./main || echo "correct"
-//
-// If it prints "correct" then the architecture should be here, in the "correct" section.
-#if defined(_M_X64) || defined(__x86_64__) || \
-    defined(__ARMEL__) || defined(__avr32__) || defined(_M_ARM) || defined(_M_ARM64) || \
-    defined(__hppa__) || defined(__ia64__) || \
-    defined(__mips__) || \
-    defined(__powerpc__) || defined(__ppc__) || defined(__ppc64__) || \
-    defined(_POWER) || defined(_ARCH_PPC) || defined(_ARCH_PPC64) || \
-    defined(__sparc__) || defined(__sparc) || defined(__s390__) || \
-    defined(__SH4__) || defined(__alpha__) || \
-    defined(_MIPS_ARCH_MIPS32R2) || defined(__ARMEB__) ||\
-    defined(__AARCH64EL__) || defined(__aarch64__) || defined(__AARCH64EB__) || \
-    defined(__riscv) || \
-    defined(__or1k__) || defined(__arc__) || \
-    defined(__EMSCRIPTEN__)
-#define DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS 1
-#elif defined(__mc68000__) || \
-    defined(__pnacl__) || defined(__native_client__)
-#undef DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS
-#elif defined(_M_IX86) || defined(__i386__) || defined(__i386)
-#if defined(_WIN32)
-// Windows uses a 64bit wide floating point stack.
-#define DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS 1
-#else
-#undef DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS
-#endif  // _WIN32
-#else
-#error Target architecture was not detected as supported by Double-Conversion.
-#endif
-
-#if defined(_WIN32) && !defined(__MINGW32__)
-
-typedef signed char int8_t;
-typedef unsigned char uint8_t;
-typedef short int16_t;  // NOLINT
-typedef unsigned short uint16_t;  // NOLINT
-typedef int int32_t;
-typedef unsigned int uint32_t;
-typedef __int64 int64_t;
-typedef unsigned __int64 uint64_t;
-// intptr_t and friends are defined in crtdefs.h through stdio.h.
-
-#else
-
-#include <stdint.h>
-
-#endif
-
-typedef uint16_t uc16;
-
-// The following macro works on both 32 and 64-bit platforms.
-// Usage: instead of writing 0x1234567890123456
-//      write UINT64_2PART_C(0x12345678,90123456);
-#define UINT64_2PART_C(a, b) (((static_cast<uint64_t>(a) << 32) + 0x##b##u))
-
-
-// The expression ARRAY_SIZE(a) is a compile-time constant of type
-// size_t which represents the number of elements of the given
-// array. You should only use ARRAY_SIZE on statically allocated
-// arrays.
-#ifndef ARRAY_SIZE
-#define ARRAY_SIZE(a)                                   \
-  ((sizeof(a) / sizeof(*(a))) /                         \
-  static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
-#endif
-
-// A macro to disallow the evil copy constructor and operator= functions
-// This should be used in the private: declarations for a class
-#ifndef DC_DISALLOW_COPY_AND_ASSIGN
-#define DC_DISALLOW_COPY_AND_ASSIGN(TypeName)      \
-  TypeName(const TypeName&);                    \
-  void operator=(const TypeName&)
-#endif
-
-// A macro to disallow all the implicit constructors, namely the
-// default constructor, copy constructor and operator= functions.
-//
-// This should be used in the private: declarations for a class
-// that wants to prevent anyone from instantiating it. This is
-// especially useful for classes containing only static methods.
-#ifndef DC_DISALLOW_IMPLICIT_CONSTRUCTORS
-#define DC_DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
-  TypeName();                                    \
-  DC_DISALLOW_COPY_AND_ASSIGN(TypeName)
-#endif
-
-namespace double_conversion {
-
-static const int kCharSize = sizeof(char);
-
-// Returns the maximum of the two parameters.
-template <typename T>
-static T Max(T a, T b) {
-  return a < b ? b : a;
-}
-
-
-// Returns the minimum of the two parameters.
-template <typename T>
-static T Min(T a, T b) {
-  return a < b ? a : b;
-}
-
-
-inline int StrLength(const char* string) {
-  size_t length = strlen(string);
-  ASSERT(length == static_cast<size_t>(static_cast<int>(length)));
-  return static_cast<int>(length);
-}
-
-// This is a simplified version of V8's Vector class.
-template <typename T>
-class Vector {
- public:
-  Vector() : start_(NULL), length_(0) {}
-  Vector(T* data, int len) : start_(data), length_(len) {
-    ASSERT(len == 0 || (len > 0 && data != NULL));
-  }
-
-  // Returns a vector using the same backing storage as this one,
-  // spanning from and including 'from', to but not including 'to'.
-  Vector<T> SubVector(int from, int to) {
-    ASSERT(to <= length_);
-    ASSERT(from < to);
-    ASSERT(0 <= from);
-    return Vector<T>(start() + from, to - from);
-  }
-
-  // Returns the length of the vector.
-  int length() const { return length_; }
-
-  // Returns whether or not the vector is empty.
-  bool is_empty() const { return length_ == 0; }
-
-  // Returns the pointer to the start of the data in the vector.
-  T* start() const { return start_; }
-
-  // Access individual vector elements - checks bounds in debug mode.
-  T& operator[](int index) const {
-    ASSERT(0 <= index && index < length_);
-    return start_[index];
-  }
-
-  T& first() { return start_[0]; }
-
-  T& last() { return start_[length_ - 1]; }
-
- private:
-  T* start_;
-  int length_;
-};
-
-
-// Helper class for building result strings in a character buffer. The
-// purpose of the class is to use safe operations that checks the
-// buffer bounds on all operations in debug mode.
-class StringBuilder {
- public:
-  StringBuilder(char* buffer, int buffer_size)
-      : buffer_(buffer, buffer_size), position_(0) { }
-
-  ~StringBuilder() { if (!is_finalized()) Finalize(); }
-
-  int size() const { return buffer_.length(); }
-
-  // Get the current position in the builder.
-  int position() const {
-    ASSERT(!is_finalized());
-    return position_;
-  }
-
-  // Reset the position.
-  void Reset() { position_ = 0; }
-
-  // Add a single character to the builder. It is not allowed to add
-  // 0-characters; use the Finalize() method to terminate the string
-  // instead.
-  void AddCharacter(char c) {
-    ASSERT(c != '\0');
-    ASSERT(!is_finalized() && position_ < buffer_.length());
-    buffer_[position_++] = c;
-  }
-
-  // Add an entire string to the builder. Uses strlen() internally to
-  // compute the length of the input string.
-  void AddString(const char* s) {
-    AddSubstring(s, StrLength(s));
-  }
-
-  // Add the first 'n' characters of the given string 's' to the
-  // builder. The input string must have enough characters.
-  void AddSubstring(const char* s, int n) {
-    ASSERT(!is_finalized() && position_ + n < buffer_.length());
-    ASSERT(static_cast<size_t>(n) <= strlen(s));
-    memmove(&buffer_[position_], s, n * kCharSize);
-    position_ += n;
-  }
-
-
-  // Add character padding to the builder. If count is non-positive,
-  // nothing is added to the builder.
-  void AddPadding(char c, int count) {
-    for (int i = 0; i < count; i++) {
-      AddCharacter(c);
-    }
-  }
-
-  // Finalize the string by 0-terminating it and returning the buffer.
-  char* Finalize() {
-    ASSERT(!is_finalized() && position_ < buffer_.length());
-    buffer_[position_] = '\0';
-    // Make sure nobody managed to add a 0-character to the
-    // buffer while building the string.
-    ASSERT(strlen(buffer_.start()) == static_cast<size_t>(position_));
-    position_ = -1;
-    ASSERT(is_finalized());
-    return buffer_.start();
-  }
-
- private:
-  Vector<char> buffer_;
-  int position_;
-
-  bool is_finalized() const { return position_ < 0; }
-
-  DC_DISALLOW_IMPLICIT_CONSTRUCTORS(StringBuilder);
-};
-
-// The type-based aliasing rule allows the compiler to assume that pointers of
-// different types (for some definition of different) never alias each other.
-// Thus the following code does not work:
-//
-// float f = foo();
-// int fbits = *(int*)(&f);
-//
-// The compiler 'knows' that the int pointer can't refer to f since the types
-// don't match, so the compiler may cache f in a register, leaving random data
-// in fbits.  Using C++ style casts makes no difference, however a pointer to
-// char data is assumed to alias any other pointer.  This is the 'memcpy
-// exception'.
-//
-// Bit_cast uses the memcpy exception to move the bits from a variable of one
-// type of a variable of another type.  Of course the end result is likely to
-// be implementation dependent.  Most compilers (gcc-4.2 and MSVC 2005)
-// will completely optimize BitCast away.
-//
-// There is an additional use for BitCast.
-// Recent gccs will warn when they see casts that may result in breakage due to
-// the type-based aliasing rule.  If you have checked that there is no breakage
-// you can use BitCast to cast one pointer type to another.  This confuses gcc
-// enough that it can no longer see that you have cast one pointer type to
-// another thus avoiding the warning.
-template <class Dest, class Source>
-inline Dest BitCast(const Source& source) {
-  // Compile time assertion: sizeof(Dest) == sizeof(Source)
-  // A compile error here means your Dest and Source have different sizes.
-#if __cplusplus >= 201103L
-  static_assert(sizeof(Dest) == sizeof(Source),
-                "source and destination size mismatch");
-#else
-  DOUBLE_CONVERSION_UNUSED
-  typedef char VerifySizesAreEqual[sizeof(Dest) == sizeof(Source) ? 1 : -1];
-#endif
-
-  Dest dest;
-  memmove(&dest, &source, sizeof(dest));
-  return dest;
-}
-
-template <class Dest, class Source>
-inline Dest BitCast(Source* source) {
-  return BitCast<Dest>(reinterpret_cast<uintptr_t>(source));
-}
-
-}  // namespace double_conversion
-
-#endif  // DOUBLE_CONVERSION_UTILS_H_
diff --git a/c_src/doubles.cc b/c_src/doubles.cc
deleted file mode 100644
index 05c6443..0000000
--- a/c_src/doubles.cc
+++ /dev/null
@@ -1,33 +0,0 @@
-#include "double-conversion/double-conversion.h"
-
-#define BEGIN_C extern "C" {
-#define END_C }
-
-
-namespace dc = double_conversion;
-
-
-BEGIN_C
-
-int
-double_to_shortest(unsigned char* buf, size_t size, size_t* len, double val)
-{
-    int flags = dc::DoubleToStringConverter::UNIQUE_ZERO |
-                dc::DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN |
-                dc::DoubleToStringConverter::EMIT_TRAILING_DECIMAL_POINT |
-                dc::DoubleToStringConverter::EMIT_TRAILING_ZERO_AFTER_POINT;
-
-    dc::StringBuilder builder(reinterpret_cast<char*>(buf), size);
-    dc::DoubleToStringConverter conv(flags, NULL, NULL, 'e', -6, 21, 6, 0);
-
-    if(!conv.ToShortest(val, &builder)) {
-        return 0;
-    }
-
-    *len = (size_t) builder.position();
-    builder.Finalize();
-
-    return 1;
-}
-
-END_C
diff --git a/c_src/encoder.c b/c_src/encoder.c
index f331290..d952652 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -6,6 +6,7 @@
 #include <string.h>
 
 #include "jiffy.h"
+#include "ryu/ryu.h"
 
 #define BIN_INC_SIZE 2048
 
@@ -624,20 +625,14 @@ enc_long(Encoder* e, ErlNifSInt64 val)
 static inline int
 enc_double(Encoder* e, double val)
 {
-    unsigned char* start;
-    size_t len;
-
     if(!enc_ensure(e, 32)) {
         return 0;
     }
 
-    start = &(e->p[e->i]);
+    // Normalize -0.0 to 0.0 for JSON output
+    if(val == 0.0) val = 0.0;
 
-    if(!double_to_shortest(start, e->buffer.size, &len, val)) {
-        return 0;
-    }
-
-    e->i += len;
+    e->i += d2s_buffered_n(val, (char*)&(e->p[e->i]));
     e->count++;
     return 1;
 }
diff --git a/c_src/jiffy.h b/c_src/jiffy.h
index 19f8365..daf4bd2 100644
--- a/c_src/jiffy.h
+++ b/c_src/jiffy.h
@@ -85,6 +85,5 @@ int utf8_to_unicode(unsigned char* buf, size_t size);
 int unicode_to_utf8(int c, unsigned char* buf);
 int unicode_from_pair(int hi, int lo);
 int unicode_uescape(int c, unsigned char* buf);
-int double_to_shortest(unsigned char *buf, size_t size, size_t* len, double val);
 
 #endif // Included JIFFY_H
diff --git a/c_src/ryu/LICENSE-Apache2 b/c_src/ryu/LICENSE-Apache2
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/c_src/ryu/LICENSE-Apache2
@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/c_src/ryu/LICENSE-Boost b/c_src/ryu/LICENSE-Boost
new file mode 100644
index 0000000..36b7cd9
--- /dev/null
+++ b/c_src/ryu/LICENSE-Boost
@@ -0,0 +1,23 @@
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/c_src/ryu/common.h b/c_src/ryu/common.h
new file mode 100644
index 0000000..9bec061
--- /dev/null
+++ b/c_src/ryu/common.h
@@ -0,0 +1,116 @@
+// Copyright 2018 Ulf Adams
+// Copyright 2021 Thomas Depierre
+//
+// The contents of this file may be used under the terms of the Apache License,
+// Version 2.0.
+//
+//    (See accompanying file LICENSE-Apache or copy at
+//     http://www.apache.org/licenses/LICENSE-2.0)
+//
+// Alternatively, the contents of this file may be used under the terms of
+// the Boost Software License, Version 1.0.
+//    (See accompanying file LICENSE-Boost or copy at
+//     https://www.boost.org/LICENSE_1_0.txt)
+//
+// Unless required by applicable law or agreed to in writing, this software
+// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.
+#ifndef RYU_COMMON_H
+#define RYU_COMMON_H
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+
+#if defined(_M_IX86) || defined(_M_ARM)
+#define RYU_32_BIT_PLATFORM
+#endif
+
+// Returns the number of decimal digits in v, which must not contain more than 9 digits.
+static inline uint32_t decimalLength9(const uint32_t v) {
+  // Function precondition: v is not a 10-digit number.
+  // (f2s: 9 digits are sufficient for round-tripping.)
+  // (d2fixed: We print 9-digit blocks.)
+  assert(v < 1000000000);
+  if (v >= 100000000) { return 9; }
+  if (v >= 10000000) { return 8; }
+  if (v >= 1000000) { return 7; }
+  if (v >= 100000) { return 6; }
+  if (v >= 10000) { return 5; }
+  if (v >= 1000) { return 4; }
+  if (v >= 100) { return 3; }
+  if (v >= 10) { return 2; }
+  return 1;
+}
+
+// Returns e == 0 ? 1 : [log_2(5^e)]; requires 0 <= e <= 3528.
+static inline int32_t log2pow5(const int32_t e) {
+  // This approximation works up to the point that the multiplication overflows at e = 3529.
+  // If the multiplication were done in 64 bits, it would fail at 5^4004 which is just greater
+  // than 2^9297.
+  assert(e >= 0);
+  assert(e <= 3528);
+  return (int32_t) ((((uint32_t) e) * 1217359) >> 19);
+}
+
+// Returns e == 0 ? 1 : ceil(log_2(5^e)); requires 0 <= e <= 3528.
+static inline int32_t pow5bits(const int32_t e) {
+  // This approximation works up to the point that the multiplication overflows at e = 3529.
+  // If the multiplication were done in 64 bits, it would fail at 5^4004 which is just greater
+  // than 2^9297.
+  assert(e >= 0);
+  assert(e <= 3528);
+  return (int32_t) (((((uint32_t) e) * 1217359) >> 19) + 1);
+}
+
+// Returns e == 0 ? 1 : ceil(log_2(5^e)); requires 0 <= e <= 3528.
+static inline int32_t ceil_log2pow5(const int32_t e) {
+  return log2pow5(e) + 1;
+}
+
+// Returns floor(log_10(2^e)); requires 0 <= e <= 1650.
+static inline uint32_t log10Pow2(const int32_t e) {
+  // The first value this approximation fails for is 2^1651 which is just greater than 10^297.
+  assert(e >= 0);
+  assert(e <= 1650);
+  return (((uint32_t) e) * 78913) >> 18;
+}
+
+// Returns floor(log_10(5^e)); requires 0 <= e <= 2620.
+static inline uint32_t log10Pow5(const int32_t e) {
+  // The first value this approximation fails for is 5^2621 which is just greater than 10^1832.
+  assert(e >= 0);
+  assert(e <= 2620);
+  return (((uint32_t) e) * 732923) >> 20;
+}
+
+static inline int copy_special_str(char * const result, const bool sign, const bool exponent, const bool mantissa) {
+  if (mantissa) {
+    memcpy(result, "NaN", 3);
+    return 3;
+  }
+  if (sign) {
+    result[0] = '-';
+  }
+  if (exponent) {
+    memcpy(result + sign, "Infinity", 8);
+    return sign + 8;
+  }
+  // CHANGE_FOR_ERLANG we use "0.0" as the 0 and not "0E0"
+  memcpy(result + sign, "0.0", 3);
+  return sign + 3;
+}
+
+static inline uint32_t float_to_bits(const float f) {
+  uint32_t bits = 0;
+  memcpy(&bits, &f, sizeof(float));
+  return bits;
+}
+
+static inline uint64_t double_to_bits(const double d) {
+  uint64_t bits = 0;
+  memcpy(&bits, &d, sizeof(double));
+  return bits;
+}
+
+#endif // RYU_COMMON_H
diff --git a/c_src/ryu/d2s.c b/c_src/ryu/d2s.c
new file mode 100644
index 0000000..ef15e60
--- /dev/null
+++ b/c_src/ryu/d2s.c
@@ -0,0 +1,391 @@
+// Copyright 2018 Ulf Adams
+// Copyright 2021 Thomas Depierre
+// Copyright 2025 Copyright Ericsson AB 2025. All Rights Reserved.
+//
+// The contents of this file may be used under the terms of the Apache License,
+// Version 2.0.
+//
+//    (See accompanying file LICENSE-Apache or copy at
+//     http://www.apache.org/licenses/LICENSE-2.0)
+//
+// Alternatively, the contents of this file may be used under the terms of
+// the Boost Software License, Version 1.0.
+//    (See accompanying file LICENSE-Boost or copy at
+//     https://www.boost.org/LICENSE_1_0.txt)
+//
+// Unless required by applicable law or agreed to in writing, this software
+// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.
+
+// Runtime compiler options:
+// -DRYU_DEBUG Generate verbose debugging output to stdout.
+//
+// -DRYU_ONLY_64_BIT_OPS Avoid using uint128_t or 64-bit intrinsics. Slower,
+//     depending on your compiler.
+//
+
+// CHANGE_FOR_ERLANG: "ryu/ryu.h" -> "ryu.h"
+#include "ryu.h"
+// END CHANGE_FOR_ERLANG
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef RYU_DEBUG
+#include <inttypes.h>
+#include <stdio.h>
+#endif
+
+// CHANGE_FOR_ERLANG: "ryu/*.h" -> "*.h"
+#include "common.h"
+#include "digit_table.h"
+#include "d2s_intrinsics.h"
+// END CHANGE_FOR_ERLANG
+
+// CHANGE_FOR_ERLANG we got rid of the small_table. Also namespace as above
+#include "d2s_full_table.h"
+// END CHANGE_FOR_ERLANG
+
+#define DOUBLE_MANTISSA_BITS 52
+#define DOUBLE_EXPONENT_BITS 11
+#define DOUBLE_BIAS 1023
+
+static inline uint32_t decimalLength17(const uint64_t v) {
+  // This is slightly faster than a loop.
+  // The average output length is 16.38 digits, so we check high-to-low.
+  // Function precondition: v is not an 18, 19, or 20-digit number.
+  // (17 digits are sufficient for round-tripping.)
+  assert(v < 100000000000000000L);
+  if (v >= 10000000000000000L) { return 17; }
+  if (v >= 1000000000000000L) { return 16; }
+  if (v >= 100000000000000L) { return 15; }
+  if (v >= 10000000000000L) { return 14; }
+  if (v >= 1000000000000L) { return 13; }
+  if (v >= 100000000000L) { return 12; }
+  if (v >= 10000000000L) { return 11; }
+  if (v >= 1000000000L) { return 10; }
+  if (v >= 100000000L) { return 9; }
+  if (v >= 10000000L) { return 8; }
+  if (v >= 1000000L) { return 7; }
+  if (v >= 100000L) { return 6; }
+  if (v >= 10000L) { return 5; }
+  if (v >= 1000L) { return 4; }
+  if (v >= 100L) { return 3; }
+  if (v >= 10L) { return 2; }
+  return 1;
+}
+
+// A floating decimal representing m * 10^e.
+typedef struct floating_decimal_64 {
+  uint64_t mantissa;
+  // Decimal exponent's range is -324 to 308
+  // inclusive, and can fit in a short if needed.
+  int32_t exponent;
+} floating_decimal_64;
+
+// CHANGE_FOR_ERLANG include STL to_chars function
+#include "to_chars.h"
+// END CHANGE_FOR_ERLANG
+
+static inline floating_decimal_64 d2d(const uint64_t ieeeMantissa, const uint32_t ieeeExponent) {
+  int32_t e2;
+  uint64_t m2;
+  if (ieeeExponent == 0) {
+    // We subtract 2 so that the bounds computation has 2 additional bits.
+    e2 = 1 - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS - 2;
+    m2 = ieeeMantissa;
+  } else {
+    e2 = (int32_t) ieeeExponent - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS - 2;
+    m2 = (1ull << DOUBLE_MANTISSA_BITS) | ieeeMantissa;
+  }
+  const bool even = (m2 & 1) == 0;
+  const bool acceptBounds = even;
+
+#ifdef RYU_DEBUG
+  printf("-> %" PRIu64 " * 2^%d\n", m2, e2 + 2);
+#endif
+
+  // Step 2: Determine the interval of valid decimal representations.
+  const uint64_t mv = 4 * m2;
+  // Implicit bool -> int conversion. True is 1, false is 0.
+  const uint32_t mmShift = ieeeMantissa != 0 || ieeeExponent <= 1;
+  // We would compute mp and mm like this:
+  // uint64_t mp = 4 * m2 + 2;
+  // uint64_t mm = mv - 1 - mmShift;
+
+  // Step 3: Convert to a decimal power base using 128-bit arithmetic.
+  uint64_t vr, vp, vm;
+  int32_t e10;
+  bool vmIsTrailingZeros = false;
+  bool vrIsTrailingZeros = false;
+  if (e2 >= 0) {
+    // I tried special-casing q == 0, but there was no effect on performance.
+    // This expression is slightly faster than max(0, log10Pow2(e2) - 1).
+    const uint32_t q = log10Pow2(e2) - (e2 > 3);
+    e10 = (int32_t) q;
+    const int32_t k = DOUBLE_POW5_INV_BITCOUNT + pow5bits((int32_t) q) - 1;
+    const int32_t i = -e2 + (int32_t) q + k;
+    vr = mulShiftAll64(m2, DOUBLE_POW5_INV_SPLIT[q], i, &vp, &vm, mmShift);
+#ifdef RYU_DEBUG
+    printf("%" PRIu64 " * 2^%d / 10^%u\n", mv, e2, q);
+    printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm);
+#endif
+    if (q <= 21) {
+      // This should use q <= 22, but I think 21 is also safe. Smaller values
+      // may still be safe, but it's more difficult to reason about them.
+      // Only one of mp, mv, and mm can be a multiple of 5, if any.
+      const uint32_t mvMod5 = ((uint32_t) mv) - 5 * ((uint32_t) div5(mv));
+      if (mvMod5 == 0) {
+        vrIsTrailingZeros = multipleOfPowerOf5(mv, q);
+      } else if (acceptBounds) {
+        // Same as min(e2 + (~mm & 1), pow5Factor(mm)) >= q
+        // <=> e2 + (~mm & 1) >= q && pow5Factor(mm) >= q
+        // <=> true && pow5Factor(mm) >= q, since e2 >= q.
+        vmIsTrailingZeros = multipleOfPowerOf5(mv - 1 - mmShift, q);
+      } else {
+        // Same as min(e2 + 1, pow5Factor(mp)) >= q.
+        vp -= multipleOfPowerOf5(mv + 2, q);
+      }
+    }
+  } else {
+    // This expression is slightly faster than max(0, log10Pow5(-e2) - 1).
+    const uint32_t q = log10Pow5(-e2) - (-e2 > 1);
+    e10 = (int32_t) q + e2;
+    const int32_t i = -e2 - (int32_t) q;
+    const int32_t k = pow5bits(i) - DOUBLE_POW5_BITCOUNT;
+    const int32_t j = (int32_t) q - k;
+    vr = mulShiftAll64(m2, DOUBLE_POW5_SPLIT[i], j, &vp, &vm, mmShift);
+#ifdef RYU_DEBUG
+    printf("%" PRIu64 " * 5^%d / 10^%u\n", mv, -e2, q);
+    printf("%u %d %d %d\n", q, i, k, j);
+    printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm);
+#endif
+    if (q <= 1) {
+      // {vr,vp,vm} is trailing zeros if {mv,mp,mm} has at least q trailing 0 bits.
+      // mv = 4 * m2, so it always has at least two trailing 0 bits.
+      vrIsTrailingZeros = true;
+      if (acceptBounds) {
+        // mm = mv - 1 - mmShift, so it has 1 trailing 0 bit iff mmShift == 1.
+        vmIsTrailingZeros = mmShift == 1;
+      } else {
+        // mp = mv + 2, so it always has at least one trailing 0 bit.
+        --vp;
+      }
+    } else if (q < 63) { // TODO(ulfjack): Use a tighter bound here.
+      // We want to know if the full product has at least q trailing zeros.
+      // We need to compute min(p2(mv), p5(mv) - e2) >= q
+      // <=> p2(mv) >= q && p5(mv) - e2 >= q
+      // <=> p2(mv) >= q (because -e2 >= q)
+      vrIsTrailingZeros = multipleOfPowerOf2(mv, q);
+#ifdef RYU_DEBUG
+      printf("vr is trailing zeros=%s\n", vrIsTrailingZeros ? "true" : "false");
+#endif
+    }
+  }
+#ifdef RYU_DEBUG
+  printf("e10=%d\n", e10);
+  printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm);
+  printf("vm is trailing zeros=%s\n", vmIsTrailingZeros ? "true" : "false");
+  printf("vr is trailing zeros=%s\n", vrIsTrailingZeros ? "true" : "false");
+#endif
+
+  // Step 4: Find the shortest decimal representation in the interval of valid representations.
+  int32_t removed = 0;
+  uint8_t lastRemovedDigit = 0;
+  uint64_t output;
+  // On average, we remove ~2 digits.
+  if (vmIsTrailingZeros || vrIsTrailingZeros) {
+    // General case, which happens rarely (~0.7%).
+    for (;;) {
+      const uint64_t vpDiv10 = div10(vp);
+      const uint64_t vmDiv10 = div10(vm);
+      if (vpDiv10 <= vmDiv10) {
+        break;
+      }
+      const uint32_t vmMod10 = ((uint32_t) vm) - 10 * ((uint32_t) vmDiv10);
+      const uint64_t vrDiv10 = div10(vr);
+      const uint32_t vrMod10 = ((uint32_t) vr) - 10 * ((uint32_t) vrDiv10);
+      vmIsTrailingZeros &= vmMod10 == 0;
+      vrIsTrailingZeros &= lastRemovedDigit == 0;
+      lastRemovedDigit = (uint8_t) vrMod10;
+      vr = vrDiv10;
+      vp = vpDiv10;
+      vm = vmDiv10;
+      ++removed;
+    }
+#ifdef RYU_DEBUG
+    printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm);
+    printf("d-10=%s\n", vmIsTrailingZeros ? "true" : "false");
+#endif
+    if (vmIsTrailingZeros) {
+      for (;;) {
+        const uint64_t vmDiv10 = div10(vm);
+        const uint32_t vmMod10 = ((uint32_t) vm) - 10 * ((uint32_t) vmDiv10);
+        if (vmMod10 != 0) {
+          break;
+        }
+        const uint64_t vpDiv10 = div10(vp);
+        const uint64_t vrDiv10 = div10(vr);
+        const uint32_t vrMod10 = ((uint32_t) vr) - 10 * ((uint32_t) vrDiv10);
+        vrIsTrailingZeros &= lastRemovedDigit == 0;
+        lastRemovedDigit = (uint8_t) vrMod10;
+        vr = vrDiv10;
+        vp = vpDiv10;
+        vm = vmDiv10;
+        ++removed;
+      }
+    }
+#ifdef RYU_DEBUG
+    printf("%" PRIu64 " %d\n", vr, lastRemovedDigit);
+    printf("vr is trailing zeros=%s\n", vrIsTrailingZeros ? "true" : "false");
+#endif
+    if (vrIsTrailingZeros && lastRemovedDigit == 5 && vr % 2 == 0) {
+      // Round even if the exact number is .....50..0.
+      lastRemovedDigit = 4;
+    }
+    // We need to take vr + 1 if vr is outside bounds or we need to round up.
+    output = vr + ((vr == vm && (!acceptBounds || !vmIsTrailingZeros)) || lastRemovedDigit >= 5);
+  } else {
+    // Specialized for the common case (~99.3%). Percentages below are relative to this.
+    bool roundUp = false;
+    const uint64_t vpDiv100 = div100(vp);
+    const uint64_t vmDiv100 = div100(vm);
+    if (vpDiv100 > vmDiv100) { // Optimization: remove two digits at a time (~86.2%).
+      const uint64_t vrDiv100 = div100(vr);
+      const uint32_t vrMod100 = ((uint32_t) vr) - 100 * ((uint32_t) vrDiv100);
+      roundUp = vrMod100 >= 50;
+      vr = vrDiv100;
+      vp = vpDiv100;
+      vm = vmDiv100;
+      removed += 2;
+    }
+    // Loop iterations below (approximately), without optimization above:
+    // 0: 0.03%, 1: 13.8%, 2: 70.6%, 3: 14.0%, 4: 1.40%, 5: 0.14%, 6+: 0.02%
+    // Loop iterations below (approximately), with optimization above:
+    // 0: 70.6%, 1: 27.8%, 2: 1.40%, 3: 0.14%, 4+: 0.02%
+    for (;;) {
+      const uint64_t vpDiv10 = div10(vp);
+      const uint64_t vmDiv10 = div10(vm);
+      if (vpDiv10 <= vmDiv10) {
+        break;
+      }
+      const uint64_t vrDiv10 = div10(vr);
+      const uint32_t vrMod10 = ((uint32_t) vr) - 10 * ((uint32_t) vrDiv10);
+      roundUp = vrMod10 >= 5;
+      vr = vrDiv10;
+      vp = vpDiv10;
+      vm = vmDiv10;
+      ++removed;
+    }
+#ifdef RYU_DEBUG
+    printf("%" PRIu64 " roundUp=%s\n", vr, roundUp ? "true" : "false");
+    printf("vr is trailing zeros=%s\n", vrIsTrailingZeros ? "true" : "false");
+#endif
+    // We need to take vr + 1 if vr is outside bounds or we need to round up.
+    output = vr + (vr == vm || roundUp);
+  }
+  const int32_t exp = e10 + removed;
+
+#ifdef RYU_DEBUG
+  printf("V+=%" PRIu64 "\nV =%" PRIu64 "\nV-=%" PRIu64 "\n", vp, vr, vm);
+  printf("O=%" PRIu64 "\n", output);
+  printf("EXP=%d\n", exp);
+#endif
+
+  floating_decimal_64 fd;
+  fd.exponent = exp;
+  fd.mantissa = output;
+  return fd;
+}
+
+static inline bool d2d_small_int(const uint64_t ieeeMantissa, const uint32_t ieeeExponent,
+  floating_decimal_64* const v) {
+  const uint64_t m2 = (1ull << DOUBLE_MANTISSA_BITS) | ieeeMantissa;
+  const int32_t e2 = (int32_t) ieeeExponent - DOUBLE_BIAS - DOUBLE_MANTISSA_BITS;
+
+  if (e2 > 0) {
+    // f = m2 * 2^e2 >= 2^53 is an integer.
+    // Ignore this case for now.
+    return false;
+  }
+
+  if (e2 < -52) {
+    // f < 1.
+    return false;
+  }
+
+  // Since 2^52 <= m2 < 2^53 and 0 <= -e2 <= 52: 1 <= f = m2 / 2^-e2 < 2^53.
+  // Test if the lower -e2 bits of the significand are 0, i.e. whether the fraction is 0.
+  const uint64_t mask = (1ull << -e2) - 1;
+  const uint64_t fraction = m2 & mask;
+  if (fraction != 0) {
+    return false;
+  }
+
+  // f is an integer in the range [1, 2^53).
+  // Note: mantissa might contain trailing (decimal) 0's.
+  // Note: since 2^53 < 10^16, there is no need to adjust decimalLength17().
+  v->mantissa = m2 >> -e2;
+  v->exponent = 0;
+  return true;
+}
+
+int d2s_buffered_n(double f, char* result) {
+  // Step 1: Decode the floating-point number, and unify normalized and subnormal cases.
+  const uint64_t bits = double_to_bits(f);
+
+#ifdef RYU_DEBUG
+  printf("IN=");
+  for (int32_t bit = 63; bit >= 0; --bit) {
+    printf("%d", (int) ((bits >> bit) & 1));
+  }
+  printf("\n");
+#endif
+
+  // Decode bits into sign, mantissa, and exponent.
+  const bool ieeeSign = ((bits >> (DOUBLE_MANTISSA_BITS + DOUBLE_EXPONENT_BITS)) & 1) != 0;
+  const uint64_t ieeeMantissa = bits & ((1ull << DOUBLE_MANTISSA_BITS) - 1);
+  const uint32_t ieeeExponent = (uint32_t) ((bits >> DOUBLE_MANTISSA_BITS) & ((1u << DOUBLE_EXPONENT_BITS) - 1));
+  // Case distinction; exit early for the easy cases.
+  if (ieeeExponent == ((1u << DOUBLE_EXPONENT_BITS) - 1u) || (ieeeExponent == 0 && ieeeMantissa == 0)) {
+    return copy_special_str(result, ieeeSign, ieeeExponent, ieeeMantissa);
+  }
+
+  floating_decimal_64 v;
+  const bool isSmallInt = d2d_small_int(ieeeMantissa, ieeeExponent, &v);
+  if (isSmallInt) {
+    // For small integers in the range [1, 2^53), v.mantissa might contain trailing (decimal) zeros.
+    // For scientific notation we need to move these zeros into the exponent.
+    // (This is not needed for fixed-point notation, so it might be beneficial to trim
+    // trailing zeros in to_chars only if needed - once fixed-point notation output is implemented.)
+    for (;;) {
+      const uint64_t q = div10(v.mantissa);
+      const uint32_t r = ((uint32_t) v.mantissa) - 10 * ((uint32_t) q);
+      if (r != 0) {
+        break;
+      }
+      v.mantissa = q;
+      ++v.exponent;
+    }
+  } else {
+    v = d2d(ieeeMantissa, ieeeExponent);
+  }
+
+  return to_chars(v, ieeeSign, result);
+}
+
+void d2s_buffered(double f, char* result) {
+  const int index = d2s_buffered_n(f, result);
+
+  // Terminate the string.
+  result[index] = '\0';
+}
+
+char* d2s(double f) {
+  char* const result = (char*) malloc(25);
+  d2s_buffered(f, result);
+  return result;
+}
diff --git a/c_src/ryu/d2s_full_table.h b/c_src/ryu/d2s_full_table.h
new file mode 100644
index 0000000..c8629ee
--- /dev/null
+++ b/c_src/ryu/d2s_full_table.h
@@ -0,0 +1,367 @@
+// Copyright 2018 Ulf Adams
+//
+// The contents of this file may be used under the terms of the Apache License,
+// Version 2.0.
+//
+//    (See accompanying file LICENSE-Apache or copy at
+//     http://www.apache.org/licenses/LICENSE-2.0)
+//
+// Alternatively, the contents of this file may be used under the terms of
+// the Boost Software License, Version 1.0.
+//    (See accompanying file LICENSE-Boost or copy at
+//     https://www.boost.org/LICENSE_1_0.txt)
+//
+// Unless required by applicable law or agreed to in writing, this software
+// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.
+#ifndef RYU_D2S_FULL_TABLE_H
+#define RYU_D2S_FULL_TABLE_H
+
+// These tables are generated by PrintDoubleLookupTable.
+#define DOUBLE_POW5_INV_BITCOUNT 125
+#define DOUBLE_POW5_BITCOUNT 125
+
+#define DOUBLE_POW5_INV_TABLE_SIZE 342
+#define DOUBLE_POW5_TABLE_SIZE 326
+
+static const uint64_t DOUBLE_POW5_INV_SPLIT[DOUBLE_POW5_INV_TABLE_SIZE][2] = {
+  {                    1u, 2305843009213693952u }, { 11068046444225730970u, 1844674407370955161u },
+  {  5165088340638674453u, 1475739525896764129u }, {  7821419487252849886u, 1180591620717411303u },
+  {  8824922364862649494u, 1888946593147858085u }, {  7059937891890119595u, 1511157274518286468u },
+  { 13026647942995916322u, 1208925819614629174u }, {  9774590264567735146u, 1934281311383406679u },
+  { 11509021026396098440u, 1547425049106725343u }, { 16585914450600699399u, 1237940039285380274u },
+  { 15469416676735388068u, 1980704062856608439u }, { 16064882156130220778u, 1584563250285286751u },
+  {  9162556910162266299u, 1267650600228229401u }, {  7281393426775805432u, 2028240960365167042u },
+  { 16893161185646375315u, 1622592768292133633u }, {  2446482504291369283u, 1298074214633706907u },
+  {  7603720821608101175u, 2076918743413931051u }, {  2393627842544570617u, 1661534994731144841u },
+  { 16672297533003297786u, 1329227995784915872u }, { 11918280793837635165u, 2126764793255865396u },
+  {  5845275820328197809u, 1701411834604692317u }, { 15744267100488289217u, 1361129467683753853u },
+  {  3054734472329800808u, 2177807148294006166u }, { 17201182836831481939u, 1742245718635204932u },
+  {  6382248639981364905u, 1393796574908163946u }, {  2832900194486363201u, 2230074519853062314u },
+  {  5955668970331000884u, 1784059615882449851u }, {  1075186361522890384u, 1427247692705959881u },
+  { 12788344622662355584u, 2283596308329535809u }, { 13920024512871794791u, 1826877046663628647u },
+  {  3757321980813615186u, 1461501637330902918u }, { 10384555214134712795u, 1169201309864722334u },
+  {  5547241898389809503u, 1870722095783555735u }, {  4437793518711847602u, 1496577676626844588u },
+  { 10928932444453298728u, 1197262141301475670u }, { 17486291911125277965u, 1915619426082361072u },
+  {  6610335899416401726u, 1532495540865888858u }, { 12666966349016942027u, 1225996432692711086u },
+  { 12888448528943286597u, 1961594292308337738u }, { 17689456452638449924u, 1569275433846670190u },
+  { 14151565162110759939u, 1255420347077336152u }, {  7885109000409574610u, 2008672555323737844u },
+  {  9997436015069570011u, 1606938044258990275u }, {  7997948812055656009u, 1285550435407192220u },
+  { 12796718099289049614u, 2056880696651507552u }, {  2858676849947419045u, 1645504557321206042u },
+  { 13354987924183666206u, 1316403645856964833u }, { 17678631863951955605u, 2106245833371143733u },
+  {  3074859046935833515u, 1684996666696914987u }, { 13527933681774397782u, 1347997333357531989u },
+  { 10576647446613305481u, 2156795733372051183u }, { 15840015586774465031u, 1725436586697640946u },
+  {  8982663654677661702u, 1380349269358112757u }, { 18061610662226169046u, 2208558830972980411u },
+  { 10759939715039024913u, 1766847064778384329u }, { 12297300586773130254u, 1413477651822707463u },
+  { 15986332124095098083u, 2261564242916331941u }, {  9099716884534168143u, 1809251394333065553u },
+  { 14658471137111155161u, 1447401115466452442u }, {  4348079280205103483u, 1157920892373161954u },
+  { 14335624477811986218u, 1852673427797059126u }, {  7779150767507678651u, 1482138742237647301u },
+  {  2533971799264232598u, 1185710993790117841u }, { 15122401323048503126u, 1897137590064188545u },
+  { 12097921058438802501u, 1517710072051350836u }, {  5988988032009131678u, 1214168057641080669u },
+  { 16961078480698431330u, 1942668892225729070u }, { 13568862784558745064u, 1554135113780583256u },
+  {  7165741412905085728u, 1243308091024466605u }, { 11465186260648137165u, 1989292945639146568u },
+  { 16550846638002330379u, 1591434356511317254u }, { 16930026125143774626u, 1273147485209053803u },
+  {  4951948911778577463u, 2037035976334486086u }, {   272210314680951647u, 1629628781067588869u },
+  {  3907117066486671641u, 1303703024854071095u }, {  6251387306378674625u, 2085924839766513752u },
+  { 16069156289328670670u, 1668739871813211001u }, {  9165976216721026213u, 1334991897450568801u },
+  {  7286864317269821294u, 2135987035920910082u }, { 16897537898041588005u, 1708789628736728065u },
+  { 13518030318433270404u, 1367031702989382452u }, {  6871453250525591353u, 2187250724783011924u },
+  {  9186511415162383406u, 1749800579826409539u }, { 11038557946871817048u, 1399840463861127631u },
+  { 10282995085511086630u, 2239744742177804210u }, {  8226396068408869304u, 1791795793742243368u },
+  { 13959814484210916090u, 1433436634993794694u }, { 11267656730511734774u, 2293498615990071511u },
+  {  5324776569667477496u, 1834798892792057209u }, {  7949170070475892320u, 1467839114233645767u },
+  { 17427382500606444826u, 1174271291386916613u }, {  5747719112518849781u, 1878834066219066582u },
+  { 15666221734240810795u, 1503067252975253265u }, { 12532977387392648636u, 1202453802380202612u },
+  {  5295368560860596524u, 1923926083808324180u }, {  4236294848688477220u, 1539140867046659344u },
+  {  7078384693692692099u, 1231312693637327475u }, { 11325415509908307358u, 1970100309819723960u },
+  {  9060332407926645887u, 1576080247855779168u }, { 14626963555825137356u, 1260864198284623334u },
+  { 12335095245094488799u, 2017382717255397335u }, {  9868076196075591040u, 1613906173804317868u },
+  { 15273158586344293478u, 1291124939043454294u }, { 13369007293925138595u, 2065799902469526871u },
+  {  7005857020398200553u, 1652639921975621497u }, { 16672732060544291412u, 1322111937580497197u },
+  { 11918976037903224966u, 2115379100128795516u }, {  5845832015580669650u, 1692303280103036413u },
+  { 12055363241948356366u, 1353842624082429130u }, {   841837113407818570u, 2166148198531886609u },
+  {  4362818505468165179u, 1732918558825509287u }, { 14558301248600263113u, 1386334847060407429u },
+  { 12225235553534690011u, 2218135755296651887u }, {  2401490813343931363u, 1774508604237321510u },
+  {  1921192650675145090u, 1419606883389857208u }, { 17831303500047873437u, 2271371013423771532u },
+  {  6886345170554478103u, 1817096810739017226u }, {  1819727321701672159u, 1453677448591213781u },
+  { 16213177116328979020u, 1162941958872971024u }, { 14873036941900635463u, 1860707134196753639u },
+  { 15587778368262418694u, 1488565707357402911u }, {  8780873879868024632u, 1190852565885922329u },
+  {  2981351763563108441u, 1905364105417475727u }, { 13453127855076217722u, 1524291284333980581u },
+  {  7073153469319063855u, 1219433027467184465u }, { 11317045550910502167u, 1951092843947495144u },
+  { 12742985255470312057u, 1560874275157996115u }, { 10194388204376249646u, 1248699420126396892u },
+  {  1553625868034358140u, 1997919072202235028u }, {  8621598323911307159u, 1598335257761788022u },
+  { 17965325103354776697u, 1278668206209430417u }, { 13987124906400001422u, 2045869129935088668u },
+  {   121653480894270168u, 1636695303948070935u }, {    97322784715416134u, 1309356243158456748u },
+  { 14913111714512307107u, 2094969989053530796u }, {  8241140556867935363u, 1675975991242824637u },
+  { 17660958889720079260u, 1340780792994259709u }, { 17189487779326395846u, 2145249268790815535u },
+  { 13751590223461116677u, 1716199415032652428u }, { 18379969808252713988u, 1372959532026121942u },
+  { 14650556434236701088u, 2196735251241795108u }, {   652398703163629901u, 1757388200993436087u },
+  { 11589965406756634890u, 1405910560794748869u }, {  7475898206584884855u, 2249456897271598191u },
+  {  2291369750525997561u, 1799565517817278553u }, {  9211793429904618695u, 1439652414253822842u },
+  { 18428218302589300235u, 2303443862806116547u }, {  7363877012587619542u, 1842755090244893238u },
+  { 13269799239553916280u, 1474204072195914590u }, { 10615839391643133024u, 1179363257756731672u },
+  {  2227947767661371545u, 1886981212410770676u }, { 16539753473096738529u, 1509584969928616540u },
+  { 13231802778477390823u, 1207667975942893232u }, {  6413489186596184024u, 1932268761508629172u },
+  { 16198837793502678189u, 1545815009206903337u }, {  5580372605318321905u, 1236652007365522670u },
+  {  8928596168509315048u, 1978643211784836272u }, { 18210923379033183008u, 1582914569427869017u },
+  {  7190041073742725760u, 1266331655542295214u }, {   436019273762630246u, 2026130648867672343u },
+  {  7727513048493924843u, 1620904519094137874u }, {  9871359253537050198u, 1296723615275310299u },
+  {  4726128361433549347u, 2074757784440496479u }, {  7470251503888749801u, 1659806227552397183u },
+  { 13354898832594820487u, 1327844982041917746u }, { 13989140502667892133u, 2124551971267068394u },
+  { 14880661216876224029u, 1699641577013654715u }, { 11904528973500979224u, 1359713261610923772u },
+  {  4289851098633925465u, 2175541218577478036u }, { 18189276137874781665u, 1740432974861982428u },
+  {  3483374466074094362u, 1392346379889585943u }, {  1884050330976640656u, 2227754207823337509u },
+  {  5196589079523222848u, 1782203366258670007u }, { 15225317707844309248u, 1425762693006936005u },
+  {  5913764258841343181u, 2281220308811097609u }, {  8420360221814984868u, 1824976247048878087u },
+  { 17804334621677718864u, 1459980997639102469u }, { 17932816512084085415u, 1167984798111281975u },
+  { 10245762345624985047u, 1868775676978051161u }, {  4507261061758077715u, 1495020541582440929u },
+  {  7295157664148372495u, 1196016433265952743u }, {  7982903447895485668u, 1913626293225524389u },
+  { 10075671573058298858u, 1530901034580419511u }, {  4371188443704728763u, 1224720827664335609u },
+  { 14372599139411386667u, 1959553324262936974u }, { 15187428126271019657u, 1567642659410349579u },
+  { 15839291315758726049u, 1254114127528279663u }, {  3206773216762499739u, 2006582604045247462u },
+  { 13633465017635730761u, 1605266083236197969u }, { 14596120828850494932u, 1284212866588958375u },
+  {  4907049252451240275u, 2054740586542333401u }, {   236290587219081897u, 1643792469233866721u },
+  { 14946427728742906810u, 1315033975387093376u }, { 16535586736504830250u, 2104054360619349402u },
+  {  5849771759720043554u, 1683243488495479522u }, { 15747863852001765813u, 1346594790796383617u },
+  { 10439186904235184007u, 2154551665274213788u }, { 15730047152871967852u, 1723641332219371030u },
+  { 12584037722297574282u, 1378913065775496824u }, {  9066413911450387881u, 2206260905240794919u },
+  { 10942479943902220628u, 1765008724192635935u }, {  8753983955121776503u, 1412006979354108748u },
+  { 10317025513452932081u, 2259211166966573997u }, {   874922781278525018u, 1807368933573259198u },
+  {  8078635854506640661u, 1445895146858607358u }, { 13841606313089133175u, 1156716117486885886u },
+  { 14767872471458792434u, 1850745787979017418u }, {   746251532941302978u, 1480596630383213935u },
+  {   597001226353042382u, 1184477304306571148u }, { 15712597221132509104u, 1895163686890513836u },
+  {  8880728962164096960u, 1516130949512411069u }, { 10793931984473187891u, 1212904759609928855u },
+  { 17270291175157100626u, 1940647615375886168u }, {  2748186495899949531u, 1552518092300708935u },
+  {  2198549196719959625u, 1242014473840567148u }, { 18275073973719576693u, 1987223158144907436u },
+  { 10930710364233751031u, 1589778526515925949u }, { 12433917106128911148u, 1271822821212740759u },
+  {  8826220925580526867u, 2034916513940385215u }, {  7060976740464421494u, 1627933211152308172u },
+  { 16716827836597268165u, 1302346568921846537u }, { 11989529279587987770u, 2083754510274954460u },
+  {  9591623423670390216u, 1667003608219963568u }, { 15051996368420132820u, 1333602886575970854u },
+  { 13015147745246481542u, 2133764618521553367u }, {  3033420566713364587u, 1707011694817242694u },
+  {  6116085268112601993u, 1365609355853794155u }, {  9785736428980163188u, 2184974969366070648u },
+  { 15207286772667951197u, 1747979975492856518u }, {  1097782973908629988u, 1398383980394285215u },
+  {  1756452758253807981u, 2237414368630856344u }, {  5094511021344956708u, 1789931494904685075u },
+  {  4075608817075965366u, 1431945195923748060u }, {  6520974107321544586u, 2291112313477996896u },
+  {  1527430471115325346u, 1832889850782397517u }, { 12289990821117991246u, 1466311880625918013u },
+  { 17210690286378213644u, 1173049504500734410u }, {  9090360384495590213u, 1876879207201175057u },
+  { 18340334751822203140u, 1501503365760940045u }, { 14672267801457762512u, 1201202692608752036u },
+  { 16096930852848599373u, 1921924308174003258u }, {  1809498238053148529u, 1537539446539202607u },
+  { 12515645034668249793u, 1230031557231362085u }, {  1578287981759648052u, 1968050491570179337u },
+  { 12330676829633449412u, 1574440393256143469u }, { 13553890278448669853u, 1259552314604914775u },
+  {  3239480371808320148u, 2015283703367863641u }, { 17348979556414297411u, 1612226962694290912u },
+  {  6500486015647617283u, 1289781570155432730u }, { 10400777625036187652u, 2063650512248692368u },
+  { 15699319729512770768u, 1650920409798953894u }, { 16248804598352126938u, 1320736327839163115u },
+  {  7551343283653851484u, 2113178124542660985u }, {  6041074626923081187u, 1690542499634128788u },
+  { 12211557331022285596u, 1352433999707303030u }, {  1091747655926105338u, 2163894399531684849u },
+  {  4562746939482794594u, 1731115519625347879u }, {  7339546366328145998u, 1384892415700278303u },
+  {  8053925371383123274u, 2215827865120445285u }, {  6443140297106498619u, 1772662292096356228u },
+  { 12533209867169019542u, 1418129833677084982u }, {  5295740528502789974u, 2269007733883335972u },
+  { 15304638867027962949u, 1815206187106668777u }, {  4865013464138549713u, 1452164949685335022u },
+  { 14960057215536570740u, 1161731959748268017u }, {  9178696285890871890u, 1858771135597228828u },
+  { 14721654658196518159u, 1487016908477783062u }, {  4398626097073393881u, 1189613526782226450u },
+  {  7037801755317430209u, 1903381642851562320u }, {  5630241404253944167u, 1522705314281249856u },
+  {   814844308661245011u, 1218164251424999885u }, {  1303750893857992017u, 1949062802279999816u },
+  { 15800395974054034906u, 1559250241823999852u }, {  5261619149759407279u, 1247400193459199882u },
+  { 12107939454356961969u, 1995840309534719811u }, {  5997002748743659252u, 1596672247627775849u },
+  {  8486951013736837725u, 1277337798102220679u }, {  2511075177753209390u, 2043740476963553087u },
+  { 13076906586428298482u, 1634992381570842469u }, { 14150874083884549109u, 1307993905256673975u },
+  {  4194654460505726958u, 2092790248410678361u }, { 18113118827372222859u, 1674232198728542688u },
+  {  3422448617672047318u, 1339385758982834151u }, { 16543964232501006678u, 2143017214372534641u },
+  {  9545822571258895019u, 1714413771498027713u }, { 15015355686490936662u, 1371531017198422170u },
+  {  5577825024675947042u, 2194449627517475473u }, { 11840957649224578280u, 1755559702013980378u },
+  { 16851463748863483271u, 1404447761611184302u }, { 12204946739213931940u, 2247116418577894884u },
+  { 13453306206113055875u, 1797693134862315907u }, {  3383947335406624054u, 1438154507889852726u },
+  { 16482362180876329456u, 2301047212623764361u }, {  9496540929959153242u, 1840837770099011489u },
+  { 11286581558709232917u, 1472670216079209191u }, {  5339916432225476010u, 1178136172863367353u },
+  {  4854517476818851293u, 1885017876581387765u }, {  3883613981455081034u, 1508014301265110212u },
+  { 14174937629389795797u, 1206411441012088169u }, { 11611853762797942306u, 1930258305619341071u },
+  {  5600134195496443521u, 1544206644495472857u }, { 15548153800622885787u, 1235365315596378285u },
+  {  6430302007287065643u, 1976584504954205257u }, { 16212288050055383484u, 1581267603963364205u },
+  { 12969830440044306787u, 1265014083170691364u }, {  9683682259845159889u, 2024022533073106183u },
+  { 15125643437359948558u, 1619218026458484946u }, {  8411165935146048523u, 1295374421166787957u },
+  { 17147214310975587960u, 2072599073866860731u }, { 10028422634038560045u, 1658079259093488585u },
+  {  8022738107230848036u, 1326463407274790868u }, {  9147032156827446534u, 2122341451639665389u },
+  { 11006974540203867551u, 1697873161311732311u }, {  5116230817421183718u, 1358298529049385849u },
+  { 15564666937357714594u, 2173277646479017358u }, {  1383687105660440706u, 1738622117183213887u },
+  { 12174996128754083534u, 1390897693746571109u }, {  8411947361780802685u, 2225436309994513775u },
+  {  6729557889424642148u, 1780349047995611020u }, {  5383646311539713719u, 1424279238396488816u },
+  {  1235136468979721303u, 2278846781434382106u }, { 15745504434151418335u, 1823077425147505684u },
+  { 16285752362063044992u, 1458461940118004547u }, {  5649904260166615347u, 1166769552094403638u },
+  {  5350498001524674232u, 1866831283351045821u }, {   591049586477829062u, 1493465026680836657u },
+  { 11540886113407994219u, 1194772021344669325u }, {    18673707743239135u, 1911635234151470921u },
+  { 14772334225162232601u, 1529308187321176736u }, {  8128518565387875758u, 1223446549856941389u },
+  {  1937583260394870242u, 1957514479771106223u }, {  8928764237799716840u, 1566011583816884978u },
+  { 14521709019723594119u, 1252809267053507982u }, {  8477339172590109297u, 2004494827285612772u },
+  { 17849917782297818407u, 1603595861828490217u }, {  6901236596354434079u, 1282876689462792174u },
+  { 18420676183650915173u, 2052602703140467478u }, {  3668494502695001169u, 1642082162512373983u },
+  { 10313493231639821582u, 1313665730009899186u }, {  9122891541139893884u, 2101865168015838698u },
+  { 14677010862395735754u, 1681492134412670958u }, {   673562245690857633u, 1345193707530136767u }
+};
+
+static const uint64_t DOUBLE_POW5_SPLIT[DOUBLE_POW5_TABLE_SIZE][2] = {
+  {                    0u, 1152921504606846976u }, {                    0u, 1441151880758558720u },
+  {                    0u, 1801439850948198400u }, {                    0u, 2251799813685248000u },
+  {                    0u, 1407374883553280000u }, {                    0u, 1759218604441600000u },
+  {                    0u, 2199023255552000000u }, {                    0u, 1374389534720000000u },
+  {                    0u, 1717986918400000000u }, {                    0u, 2147483648000000000u },
+  {                    0u, 1342177280000000000u }, {                    0u, 1677721600000000000u },
+  {                    0u, 2097152000000000000u }, {                    0u, 1310720000000000000u },
+  {                    0u, 1638400000000000000u }, {                    0u, 2048000000000000000u },
+  {                    0u, 1280000000000000000u }, {                    0u, 1600000000000000000u },
+  {                    0u, 2000000000000000000u }, {                    0u, 1250000000000000000u },
+  {                    0u, 1562500000000000000u }, {                    0u, 1953125000000000000u },
+  {                    0u, 1220703125000000000u }, {                    0u, 1525878906250000000u },
+  {                    0u, 1907348632812500000u }, {                    0u, 1192092895507812500u },
+  {                    0u, 1490116119384765625u }, {  4611686018427387904u, 1862645149230957031u },
+  {  9799832789158199296u, 1164153218269348144u }, { 12249790986447749120u, 1455191522836685180u },
+  { 15312238733059686400u, 1818989403545856475u }, { 14528612397897220096u, 2273736754432320594u },
+  { 13692068767113150464u, 1421085471520200371u }, { 12503399940464050176u, 1776356839400250464u },
+  { 15629249925580062720u, 2220446049250313080u }, {  9768281203487539200u, 1387778780781445675u },
+  {  7598665485932036096u, 1734723475976807094u }, {   274959820560269312u, 2168404344971008868u },
+  {  9395221924704944128u, 1355252715606880542u }, {  2520655369026404352u, 1694065894508600678u },
+  { 12374191248137781248u, 2117582368135750847u }, { 14651398557727195136u, 1323488980084844279u },
+  { 13702562178731606016u, 1654361225106055349u }, {  3293144668132343808u, 2067951531382569187u },
+  { 18199116482078572544u, 1292469707114105741u }, {  8913837547316051968u, 1615587133892632177u },
+  { 15753982952572452864u, 2019483917365790221u }, { 12152082354571476992u, 1262177448353618888u },
+  { 15190102943214346240u, 1577721810442023610u }, {  9764256642163156992u, 1972152263052529513u },
+  { 17631875447420442880u, 1232595164407830945u }, {  8204786253993389888u, 1540743955509788682u },
+  {  1032610780636961552u, 1925929944387235853u }, {  2951224747111794922u, 1203706215242022408u },
+  {  3689030933889743652u, 1504632769052528010u }, { 13834660704216955373u, 1880790961315660012u },
+  { 17870034976990372916u, 1175494350822287507u }, { 17725857702810578241u, 1469367938527859384u },
+  {  3710578054803671186u, 1836709923159824231u }, {    26536550077201078u, 2295887403949780289u },
+  { 11545800389866720434u, 1434929627468612680u }, { 14432250487333400542u, 1793662034335765850u },
+  {  8816941072311974870u, 2242077542919707313u }, { 17039803216263454053u, 1401298464324817070u },
+  { 12076381983474541759u, 1751623080406021338u }, {  5872105442488401391u, 2189528850507526673u },
+  { 15199280947623720629u, 1368455531567204170u }, {  9775729147674874978u, 1710569414459005213u },
+  { 16831347453020981627u, 2138211768073756516u }, {  1296220121283337709u, 1336382355046097823u },
+  { 15455333206886335848u, 1670477943807622278u }, { 10095794471753144002u, 2088097429759527848u },
+  {  6309871544845715001u, 1305060893599704905u }, { 12499025449484531656u, 1631326116999631131u },
+  { 11012095793428276666u, 2039157646249538914u }, { 11494245889320060820u, 1274473528905961821u },
+  {   532749306367912313u, 1593091911132452277u }, {  5277622651387278295u, 1991364888915565346u },
+  {  7910200175544436838u, 1244603055572228341u }, { 14499436237857933952u, 1555753819465285426u },
+  {  8900923260467641632u, 1944692274331606783u }, { 12480606065433357876u, 1215432671457254239u },
+  { 10989071563364309441u, 1519290839321567799u }, {  9124653435777998898u, 1899113549151959749u },
+  {  8008751406574943263u, 1186945968219974843u }, {  5399253239791291175u, 1483682460274968554u },
+  { 15972438586593889776u, 1854603075343710692u }, {   759402079766405302u, 1159126922089819183u },
+  { 14784310654990170340u, 1448908652612273978u }, {  9257016281882937117u, 1811135815765342473u },
+  { 16182956370781059300u, 2263919769706678091u }, {  7808504722524468110u, 1414949856066673807u },
+  {  5148944884728197234u, 1768687320083342259u }, {  1824495087482858639u, 2210859150104177824u },
+  {  1140309429676786649u, 1381786968815111140u }, {  1425386787095983311u, 1727233711018888925u },
+  {  6393419502297367043u, 2159042138773611156u }, { 13219259225790630210u, 1349401336733506972u },
+  { 16524074032238287762u, 1686751670916883715u }, { 16043406521870471799u, 2108439588646104644u },
+  {   803757039314269066u, 1317774742903815403u }, { 14839754354425000045u, 1647218428629769253u },
+  {  4714634887749086344u, 2059023035787211567u }, {  9864175832484260821u, 1286889397367007229u },
+  { 16941905809032713930u, 1608611746708759036u }, {  2730638187581340797u, 2010764683385948796u },
+  { 10930020904093113806u, 1256727927116217997u }, { 18274212148543780162u, 1570909908895272496u },
+  {  4396021111970173586u, 1963637386119090621u }, {  5053356204195052443u, 1227273366324431638u },
+  { 15540067292098591362u, 1534091707905539547u }, { 14813398096695851299u, 1917614634881924434u },
+  { 13870059828862294966u, 1198509146801202771u }, { 12725888767650480803u, 1498136433501503464u },
+  { 15907360959563101004u, 1872670541876879330u }, { 14553786618154326031u, 1170419088673049581u },
+  {  4357175217410743827u, 1463023860841311977u }, { 10058155040190817688u, 1828779826051639971u },
+  {  7961007781811134206u, 2285974782564549964u }, { 14199001900486734687u, 1428734239102843727u },
+  { 13137066357181030455u, 1785917798878554659u }, { 11809646928048900164u, 2232397248598193324u },
+  { 16604401366885338411u, 1395248280373870827u }, { 16143815690179285109u, 1744060350467338534u },
+  { 10956397575869330579u, 2180075438084173168u }, {  6847748484918331612u, 1362547148802608230u },
+  { 17783057643002690323u, 1703183936003260287u }, { 17617136035325974999u, 2128979920004075359u },
+  { 17928239049719816230u, 1330612450002547099u }, { 17798612793722382384u, 1663265562503183874u },
+  { 13024893955298202172u, 2079081953128979843u }, {  5834715712847682405u, 1299426220705612402u },
+  { 16516766677914378815u, 1624282775882015502u }, { 11422586310538197711u, 2030353469852519378u },
+  { 11750802462513761473u, 1268970918657824611u }, { 10076817059714813937u, 1586213648322280764u },
+  { 12596021324643517422u, 1982767060402850955u }, {  5566670318688504437u, 1239229412751781847u },
+  {  2346651879933242642u, 1549036765939727309u }, {  7545000868343941206u, 1936295957424659136u },
+  {  4715625542714963254u, 1210184973390411960u }, {  5894531928393704067u, 1512731216738014950u },
+  { 16591536947346905892u, 1890914020922518687u }, { 17287239619732898039u, 1181821263076574179u },
+  { 16997363506238734644u, 1477276578845717724u }, {  2799960309088866689u, 1846595723557147156u },
+  { 10973347230035317489u, 1154122327223216972u }, { 13716684037544146861u, 1442652909029021215u },
+  { 12534169028502795672u, 1803316136286276519u }, { 11056025267201106687u, 2254145170357845649u },
+  { 18439230838069161439u, 1408840731473653530u }, { 13825666510731675991u, 1761050914342066913u },
+  {  3447025083132431277u, 2201313642927583642u }, {  6766076695385157452u, 1375821026829739776u },
+  {  8457595869231446815u, 1719776283537174720u }, { 10571994836539308519u, 2149720354421468400u },
+  {  6607496772837067824u, 1343575221513417750u }, { 17482743002901110588u, 1679469026891772187u },
+  { 17241742735199000331u, 2099336283614715234u }, { 15387775227926763111u, 1312085177259197021u },
+  {  5399660979626290177u, 1640106471573996277u }, { 11361262242960250625u, 2050133089467495346u },
+  { 11712474920277544544u, 1281333180917184591u }, { 10028907631919542777u, 1601666476146480739u },
+  {  7924448521472040567u, 2002083095183100924u }, { 14176152362774801162u, 1251301934489438077u },
+  {  3885132398186337741u, 1564127418111797597u }, {  9468101516160310080u, 1955159272639746996u },
+  { 15140935484454969608u, 1221974545399841872u }, {   479425281859160394u, 1527468181749802341u },
+  {  5210967620751338397u, 1909335227187252926u }, { 17091912818251750210u, 1193334516992033078u },
+  { 12141518985959911954u, 1491668146240041348u }, { 15176898732449889943u, 1864585182800051685u },
+  { 11791404716994875166u, 1165365739250032303u }, { 10127569877816206054u, 1456707174062540379u },
+  {  8047776328842869663u, 1820883967578175474u }, {   836348374198811271u, 2276104959472719343u },
+  {  7440246761515338900u, 1422565599670449589u }, { 13911994470321561530u, 1778206999588061986u },
+  {  8166621051047176104u, 2222758749485077483u }, {  2798295147690791113u, 1389224218428173427u },
+  { 17332926989895652603u, 1736530273035216783u }, { 17054472718942177850u, 2170662841294020979u },
+  {  8353202440125167204u, 1356664275808763112u }, { 10441503050156459005u, 1695830344760953890u },
+  {  3828506775840797949u, 2119787930951192363u }, {    86973725686804766u, 1324867456844495227u },
+  { 13943775212390669669u, 1656084321055619033u }, {  3594660960206173375u, 2070105401319523792u },
+  {  2246663100128858359u, 1293815875824702370u }, { 12031700912015848757u, 1617269844780877962u },
+  {  5816254103165035138u, 2021587305976097453u }, {  5941001823691840913u, 1263492066235060908u },
+  {  7426252279614801142u, 1579365082793826135u }, {  4671129331091113523u, 1974206353492282669u },
+  {  5225298841145639904u, 1233878970932676668u }, {  6531623551432049880u, 1542348713665845835u },
+  {  3552843420862674446u, 1927935892082307294u }, { 16055585193321335241u, 1204959932551442058u },
+  { 10846109454796893243u, 1506199915689302573u }, { 18169322836923504458u, 1882749894611628216u },
+  { 11355826773077190286u, 1176718684132267635u }, {  9583097447919099954u, 1470898355165334544u },
+  { 11978871809898874942u, 1838622943956668180u }, { 14973589762373593678u, 2298278679945835225u },
+  {  2440964573842414192u, 1436424174966147016u }, {  3051205717303017741u, 1795530218707683770u },
+  { 13037379183483547984u, 2244412773384604712u }, {  8148361989677217490u, 1402757983365377945u },
+  { 14797138505523909766u, 1753447479206722431u }, { 13884737113477499304u, 2191809349008403039u },
+  { 15595489723564518921u, 1369880843130251899u }, { 14882676136028260747u, 1712351053912814874u },
+  {  9379973133180550126u, 2140438817391018593u }, { 17391698254306313589u, 1337774260869386620u },
+  {  3292878744173340370u, 1672217826086733276u }, {  4116098430216675462u, 2090272282608416595u },
+  {   266718509671728212u, 1306420176630260372u }, {   333398137089660265u, 1633025220787825465u },
+  {  5028433689789463235u, 2041281525984781831u }, { 10060300083759496378u, 1275800953740488644u },
+  { 12575375104699370472u, 1594751192175610805u }, {  1884160825592049379u, 1993438990219513507u },
+  { 17318501580490888525u, 1245899368887195941u }, {  7813068920331446945u, 1557374211108994927u },
+  {  5154650131986920777u, 1946717763886243659u }, {   915813323278131534u, 1216698602428902287u },
+  { 14979824709379828129u, 1520873253036127858u }, {  9501408849870009354u, 1901091566295159823u },
+  { 12855909558809837702u, 1188182228934474889u }, {  2234828893230133415u, 1485227786168093612u },
+  {  2793536116537666769u, 1856534732710117015u }, {  8663489100477123587u, 1160334207943823134u },
+  {  1605989338741628675u, 1450417759929778918u }, { 11230858710281811652u, 1813022199912223647u },
+  {  9426887369424876662u, 2266277749890279559u }, { 12809333633531629769u, 1416423593681424724u },
+  { 16011667041914537212u, 1770529492101780905u }, {  6179525747111007803u, 2213161865127226132u },
+  { 13085575628799155685u, 1383226165704516332u }, { 16356969535998944606u, 1729032707130645415u },
+  { 15834525901571292854u, 2161290883913306769u }, {  2979049660840976177u, 1350806802445816731u },
+  { 17558870131333383934u, 1688508503057270913u }, {  8113529608884566205u, 2110635628821588642u },
+  {  9682642023980241782u, 1319147268013492901u }, { 16714988548402690132u, 1648934085016866126u },
+  { 11670363648648586857u, 2061167606271082658u }, { 11905663298832754689u, 1288229753919426661u },
+  {  1047021068258779650u, 1610287192399283327u }, { 15143834390605638274u, 2012858990499104158u },
+  {  4853210475701136017u, 1258036869061940099u }, {  1454827076199032118u, 1572546086327425124u },
+  {  1818533845248790147u, 1965682607909281405u }, {  3442426662494187794u, 1228551629943300878u },
+  { 13526405364972510550u, 1535689537429126097u }, {  3072948650933474476u, 1919611921786407622u },
+  { 15755650962115585259u, 1199757451116504763u }, { 15082877684217093670u, 1499696813895630954u },
+  {  9630225068416591280u, 1874621017369538693u }, {  8324733676974063502u, 1171638135855961683u },
+  {  5794231077790191473u, 1464547669819952104u }, {  7242788847237739342u, 1830684587274940130u },
+  { 18276858095901949986u, 2288355734093675162u }, { 16034722328366106645u, 1430222333808546976u },
+  {  1596658836748081690u, 1787777917260683721u }, {  6607509564362490017u, 2234722396575854651u },
+  {  1823850468512862308u, 1396701497859909157u }, {  6891499104068465790u, 1745876872324886446u },
+  { 17837745916940358045u, 2182346090406108057u }, {  4231062170446641922u, 1363966306503817536u },
+  {  5288827713058302403u, 1704957883129771920u }, {  6611034641322878003u, 2131197353912214900u },
+  { 13355268687681574560u, 1331998346195134312u }, { 16694085859601968200u, 1664997932743917890u },
+  { 11644235287647684442u, 2081247415929897363u }, {  4971804045566108824u, 1300779634956185852u },
+  {  6214755056957636030u, 1625974543695232315u }, {  3156757802769657134u, 2032468179619040394u },
+  {  6584659645158423613u, 1270292612261900246u }, { 17454196593302805324u, 1587865765327375307u },
+  { 17206059723201118751u, 1984832206659219134u }, {  6142101308573311315u, 1240520129162011959u },
+  {  3065940617289251240u, 1550650161452514949u }, {  8444111790038951954u, 1938312701815643686u },
+  {   665883850346957067u, 1211445438634777304u }, {   832354812933696334u, 1514306798293471630u },
+  { 10263815553021896226u, 1892883497866839537u }, { 17944099766707154901u, 1183052186166774710u },
+  { 13206752671529167818u, 1478815232708468388u }, { 16508440839411459773u, 1848519040885585485u },
+  { 12623618533845856310u, 1155324400553490928u }, { 15779523167307320387u, 1444155500691863660u },
+  {  1277659885424598868u, 1805194375864829576u }, {  1597074856780748586u, 2256492969831036970u },
+  {  5609857803915355770u, 1410308106144398106u }, { 16235694291748970521u, 1762885132680497632u },
+  {  1847873790976661535u, 2203606415850622041u }, { 12684136165428883219u, 1377254009906638775u },
+  { 11243484188358716120u, 1721567512383298469u }, {   219297180166231438u, 2151959390479123087u },
+  {  7054589765244976505u, 1344974619049451929u }, { 13429923224983608535u, 1681218273811814911u },
+  { 12175718012802122765u, 2101522842264768639u }, { 14527352785642408584u, 1313451776415480399u },
+  { 13547504963625622826u, 1641814720519350499u }, { 12322695186104640628u, 2052268400649188124u },
+  { 16925056528170176201u, 1282667750405742577u }, {  7321262604930556539u, 1603334688007178222u },
+  { 18374950293017971482u, 2004168360008972777u }, {  4566814905495150320u, 1252605225005607986u },
+  { 14931890668723713708u, 1565756531257009982u }, {  9441491299049866327u, 1957195664071262478u },
+  {  1289246043478778550u, 1223247290044539049u }, {  6223243572775861092u, 1529059112555673811u },
+  {  3167368447542438461u, 1911323890694592264u }, {  1979605279714024038u, 1194577431684120165u },
+  {  7086192618069917952u, 1493221789605150206u }, { 18081112809442173248u, 1866527237006437757u },
+  { 13606538515115052232u, 1166579523129023598u }, {  7784801107039039482u, 1458224403911279498u },
+  {   507629346944023544u, 1822780504889099373u }, {  5246222702107417334u, 2278475631111374216u },
+  {  3278889188817135834u, 1424047269444608885u }, {  8710297504448807696u, 1780059086805761106u }
+};
+
+#endif // RYU_D2S_FULL_TABLE_H
diff --git a/c_src/ryu/d2s_intrinsics.h b/c_src/ryu/d2s_intrinsics.h
new file mode 100644
index 0000000..4529e6b
--- /dev/null
+++ b/c_src/ryu/d2s_intrinsics.h
@@ -0,0 +1,359 @@
+// Copyright 2018 Ulf Adams
+// Copyright 2021 Thomas Depierre
+// Copyright 2025 Copyright Ericsson AB 2025. All Rights Reserved.
+//
+// The contents of this file may be used under the terms of the Apache License,
+// Version 2.0.
+//
+//    (See accompanying file LICENSE-Apache or copy at
+//     http://www.apache.org/licenses/LICENSE-2.0)
+//
+// Alternatively, the contents of this file may be used under the terms of
+// the Boost Software License, Version 1.0.
+//    (See accompanying file LICENSE-Boost or copy at
+//     https://www.boost.org/LICENSE_1_0.txt)
+//
+// Unless required by applicable law or agreed to in writing, this software
+// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.
+#ifndef RYU_D2S_INTRINSICS_H
+#define RYU_D2S_INTRINSICS_H
+
+#include <assert.h>
+#include <stdint.h>
+
+// Defines RYU_32_BIT_PLATFORM if applicable.
+#include "common.h"
+
+// ABSL avoids uint128_t on Win32 even if __SIZEOF_INT128__ is defined.
+// Let's do the same for now.
+#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER) && !defined(RYU_ONLY_64_BIT_OPS)
+#define HAS_UINT128
+#elif defined(_MSC_VER) && !defined(RYU_ONLY_64_BIT_OPS) && defined(_M_X64)
+#define HAS_64_BIT_INTRINSICS
+#endif
+
+#if defined(HAS_UINT128)
+typedef __uint128_t uint128_t;
+#endif
+
+#if defined(HAS_64_BIT_INTRINSICS)
+
+#include <intrin.h>
+
+static inline uint64_t umul128(const uint64_t a, const uint64_t b, uint64_t* const productHi) {
+  return _umul128(a, b, productHi);
+}
+
+// Returns the lower 64 bits of (hi*2^64 + lo) >> dist, with 0 < dist < 64.
+static inline uint64_t shiftright128(const uint64_t lo, const uint64_t hi, const uint32_t dist) {
+  // For the __shiftright128 intrinsic, the shift value is always
+  // modulo 64.
+  // In the current implementation of the double-precision version
+  // of Ryu, the shift value is always < 64. (In the case
+  // RYU_OPTIMIZE_SIZE == 0, the shift value is in the range [49, 58].
+  // Otherwise in the range [2, 59].)
+  // However, this function is now also called by s2d, which requires supporting
+  // the larger shift range (TODO: what is the actual range?).
+  // Check this here in case a future change requires larger shift
+  // values. In this case this function needs to be adjusted.
+  assert(dist < 64);
+  return __shiftright128(lo, hi, (unsigned char) dist);
+}
+
+#else // defined(HAS_64_BIT_INTRINSICS)
+
+static inline uint64_t umul128(const uint64_t a, const uint64_t b, uint64_t* const productHi) {
+  // The casts here help MSVC to avoid calls to the __allmul library function.
+  const uint32_t aLo = (uint32_t)a;
+  const uint32_t aHi = (uint32_t)(a >> 32);
+  const uint32_t bLo = (uint32_t)b;
+  const uint32_t bHi = (uint32_t)(b >> 32);
+
+  const uint64_t b00 = (uint64_t)aLo * bLo;
+  const uint64_t b01 = (uint64_t)aLo * bHi;
+  const uint64_t b10 = (uint64_t)aHi * bLo;
+  const uint64_t b11 = (uint64_t)aHi * bHi;
+
+  const uint32_t b00Lo = (uint32_t)b00;
+  const uint32_t b00Hi = (uint32_t)(b00 >> 32);
+
+  const uint64_t mid1 = b10 + b00Hi;
+  const uint32_t mid1Lo = (uint32_t)(mid1);
+  const uint32_t mid1Hi = (uint32_t)(mid1 >> 32);
+
+  const uint64_t mid2 = b01 + mid1Lo;
+  const uint32_t mid2Lo = (uint32_t)(mid2);
+  const uint32_t mid2Hi = (uint32_t)(mid2 >> 32);
+
+  const uint64_t pHi = b11 + mid1Hi + mid2Hi;
+  const uint64_t pLo = ((uint64_t)mid2Lo << 32) | b00Lo;
+
+  *productHi = pHi;
+  return pLo;
+}
+
+static inline uint64_t shiftright128(const uint64_t lo, const uint64_t hi, const uint32_t dist) {
+  // We don't need to handle the case dist >= 64 here (see above).
+  assert(dist < 64);
+  assert(dist > 0);
+  return (hi << (64 - dist)) | (lo >> dist);
+}
+
+#endif // defined(HAS_64_BIT_INTRINSICS)
+
+#if defined(RYU_32_BIT_PLATFORM)
+
+// Returns the high 64 bits of the 128-bit product of a and b.
+static inline uint64_t umulh(const uint64_t a, const uint64_t b) {
+  // Reuse the umul128 implementation.
+  // Optimizers will likely eliminate the instructions used to compute the
+  // low part of the product.
+  uint64_t hi;
+  umul128(a, b, &hi);
+  return hi;
+}
+
+// On 32-bit platforms, compilers typically generate calls to library
+// functions for 64-bit divisions, even if the divisor is a constant.
+//
+// E.g.:
+// https://bugs.llvm.org/show_bug.cgi?id=37932
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=17958
+// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=37443
+//
+// The functions here perform division-by-constant using multiplications
+// in the same way as 64-bit compilers would do.
+//
+// NB:
+// The multipliers and shift values are the ones generated by clang x64
+// for expressions like x/5, x/10, etc.
+
+static inline uint64_t div5(const uint64_t x) {
+  return umulh(x, 0xCCCCCCCCCCCCCCCDu) >> 2;
+}
+
+static inline uint64_t div10(const uint64_t x) {
+  return umulh(x, 0xCCCCCCCCCCCCCCCDu) >> 3;
+}
+
+static inline uint64_t div100(const uint64_t x) {
+  return umulh(x >> 2, 0x28F5C28F5C28F5C3u) >> 2;
+}
+
+static inline uint64_t div1e8(const uint64_t x) {
+  return umulh(x, 0xABCC77118461CEFDu) >> 26;
+}
+
+static inline uint64_t div1e9(const uint64_t x) {
+  return umulh(x >> 9, 0x44B82FA09B5A53u) >> 11;
+}
+
+static inline uint32_t mod1e9(const uint64_t x) {
+  // Avoid 64-bit math as much as possible.
+  // Returning (uint32_t) (x - 1000000000 * div1e9(x)) would
+  // perform 32x64-bit multiplication and 64-bit subtraction.
+  // x and 1000000000 * div1e9(x) are guaranteed to differ by
+  // less than 10^9, so their highest 32 bits must be identical,
+  // so we can truncate both sides to uint32_t before subtracting.
+  // We can also simplify (uint32_t) (1000000000 * div1e9(x)).
+  // We can truncate before multiplying instead of after, as multiplying
+  // the highest 32 bits of div1e9(x) can't affect the lowest 32 bits.
+  return ((uint32_t) x) - 1000000000 * ((uint32_t) div1e9(x));
+}
+
+#else // defined(RYU_32_BIT_PLATFORM)
+
+static inline uint64_t div5(const uint64_t x) {
+  return x / 5;
+}
+
+static inline uint64_t div10(const uint64_t x) {
+  return x / 10;
+}
+
+static inline uint64_t div100(const uint64_t x) {
+  return x / 100;
+}
+
+static inline uint64_t div1e8(const uint64_t x) {
+  return x / 100000000;
+}
+
+static inline uint64_t div1e9(const uint64_t x) {
+  return x / 1000000000;
+}
+
+static inline uint32_t mod1e9(const uint64_t x) {
+  return (uint32_t) (x - 1000000000 * div1e9(x));
+}
+
+#endif // defined(RYU_32_BIT_PLATFORM)
+
+static inline uint32_t pow5Factor(uint64_t value) {
+  const uint64_t m_inv_5 = 14757395258967641293u; // 5 * m_inv_5 = 1 (mod 2^64)
+  const uint64_t n_div_5 = 3689348814741910323u;  // #{ n | n = 0 (mod 2^64) } = 2^64 / 5
+  uint32_t count = 0;
+  for (;;) {
+    assert(value != 0);
+    value *= m_inv_5;
+    if (value > n_div_5)
+      break;
+    ++count;
+  }
+  return count;
+}
+
+// Returns true if value is divisible by 5^p.
+static inline bool multipleOfPowerOf5(const uint64_t value, const uint32_t p) {
+  // I tried a case distinction on p, but there was no performance difference.
+  return pow5Factor(value) >= p;
+}
+
+// Returns true if value is divisible by 2^p.
+static inline bool multipleOfPowerOf2(const uint64_t value, const uint32_t p) {
+  assert(value != 0);
+  assert(p < 64);
+  // __builtin_ctzll doesn't appear to be faster here.
+  return (value & ((1ull << p) - 1)) == 0;
+}
+
+// We need a 64x128-bit multiplication and a subsequent 128-bit shift.
+// Multiplication:
+//   The 64-bit factor is variable and passed in, the 128-bit factor comes
+//   from a lookup table. We know that the 64-bit factor only has 55
+//   significant bits (i.e., the 9 topmost bits are zeros). The 128-bit
+//   factor only has 124 significant bits (i.e., the 4 topmost bits are
+//   zeros).
+// Shift:
+//   In principle, the multiplication result requires 55 + 124 = 179 bits to
+//   represent. However, we then shift this value to the right by j, which is
+//   at least j >= 115, so the result is guaranteed to fit into 179 - 115 = 64
+//   bits. This means that we only need the topmost 64 significant bits of
+//   the 64x128-bit multiplication.
+//
+// There are several ways to do this:
+// 1. Best case: the compiler exposes a 128-bit type.
+//    We perform two 64x64-bit multiplications, add the higher 64 bits of the
+//    lower result to the higher result, and shift by j - 64 bits.
+//
+//    We explicitly cast from 64-bit to 128-bit, so the compiler can tell
+//    that these are only 64-bit inputs, and can map these to the best
+//    possible sequence of assembly instructions.
+//    x64 machines happen to have matching assembly instructions for
+//    64x64-bit multiplications and 128-bit shifts.
+//
+// 2. Second best case: the compiler exposes intrinsics for the x64 assembly
+//    instructions mentioned in 1.
+//
+// 3. We only have 64x64 bit instructions that return the lower 64 bits of
+//    the result, i.e., we have to use plain C.
+//    Our inputs are less than the full width, so we have three options:
+//    a. Ignore this fact and just implement the intrinsics manually.
+//    b. Split both into 31-bit pieces, which guarantees no internal overflow,
+//       but requires extra work upfront (unless we change the lookup table).
+//    c. Split only the first factor into 31-bit pieces, which also guarantees
+//       no internal overflow, but requires extra work since the intermediate
+//       results are not perfectly aligned.
+#if defined(HAS_UINT128)
+
+// Best case: use 128-bit type.
+static inline uint64_t mulShift64(const uint64_t m, const uint64_t* const mul, const int32_t j) {
+  const uint128_t b0 = ((uint128_t) m) * mul[0];
+  const uint128_t b2 = ((uint128_t) m) * mul[1];
+  return (uint64_t) (((b0 >> 64) + b2) >> (j - 64));
+}
+
+static inline uint64_t mulShiftAll64(const uint64_t m, const uint64_t* const mul, const int32_t j,
+  uint64_t* const vp, uint64_t* const vm, const uint32_t mmShift) {
+//  m <<= 2;
+//  uint128_t b0 = ((uint128_t) m) * mul[0]; // 0
+//  uint128_t b2 = ((uint128_t) m) * mul[1]; // 64
+//
+//  uint128_t hi = (b0 >> 64) + b2;
+//  uint128_t lo = b0 & 0xffffffffffffffffull;
+//  uint128_t factor = (((uint128_t) mul[1]) << 64) + mul[0];
+//  uint128_t vpLo = lo + (factor << 1);
+//  *vp = (uint64_t) ((hi + (vpLo >> 64)) >> (j - 64));
+//  uint128_t vmLo = lo - (factor << mmShift);
+//  *vm = (uint64_t) ((hi + (vmLo >> 64) - (((uint128_t) 1ull) << 64)) >> (j - 64));
+//  return (uint64_t) (hi >> (j - 64));
+  *vp = mulShift64(4 * m + 2, mul, j);
+  *vm = mulShift64(4 * m - 1 - mmShift, mul, j);
+  return mulShift64(4 * m, mul, j);
+}
+
+#elif defined(HAS_64_BIT_INTRINSICS)
+
+static inline uint64_t mulShift64(const uint64_t m, const uint64_t* const mul, const int32_t j) {
+  // m is maximum 55 bits
+  uint64_t high1;                                   // 128
+  const uint64_t low1 = umul128(m, mul[1], &high1); // 64
+  uint64_t high0;                                   // 64
+  umul128(m, mul[0], &high0);                       // 0
+  const uint64_t sum = high0 + low1;
+  if (sum < high0) {
+    ++high1; // overflow into high1
+  }
+  return shiftright128(sum, high1, j - 64);
+}
+
+static inline uint64_t mulShiftAll64(const uint64_t m, const uint64_t* const mul, const int32_t j,
+  uint64_t* const vp, uint64_t* const vm, const uint32_t mmShift) {
+  *vp = mulShift64(4 * m + 2, mul, j);
+  *vm = mulShift64(4 * m - 1 - mmShift, mul, j);
+  return mulShift64(4 * m, mul, j);
+}
+
+#else // !defined(HAS_UINT128) && !defined(HAS_64_BIT_INTRINSICS)
+
+static inline uint64_t mulShift64(const uint64_t m, const uint64_t* const mul, const int32_t j) {
+  // m is maximum 55 bits
+  uint64_t high1;                                   // 128
+  const uint64_t low1 = umul128(m, mul[1], &high1); // 64
+  uint64_t high0;                                   // 64
+  umul128(m, mul[0], &high0);                       // 0
+  const uint64_t sum = high0 + low1;
+  if (sum < high0) {
+    ++high1; // overflow into high1
+  }
+  return shiftright128(sum, high1, j - 64);
+}
+
+// This is faster if we don't have a 64x64->128-bit multiplication.
+static inline uint64_t mulShiftAll64(uint64_t m, const uint64_t* const mul, const int32_t j,
+  uint64_t* const vp, uint64_t* const vm, const uint32_t mmShift) {
+  m <<= 1;
+  // m is maximum 55 bits
+  uint64_t tmp;
+  const uint64_t lo = umul128(m, mul[0], &tmp);
+  uint64_t hi;
+  const uint64_t mid = tmp + umul128(m, mul[1], &hi);
+  hi += mid < tmp; // overflow into hi
+
+  const uint64_t lo2 = lo + mul[0];
+  const uint64_t mid2 = mid + mul[1] + (lo2 < lo);
+  const uint64_t hi2 = hi + (mid2 < mid);
+  *vp = shiftright128(mid2, hi2, (uint32_t) (j - 64 - 1));
+
+  if (mmShift == 1) {
+    const uint64_t lo3 = lo - mul[0];
+    const uint64_t mid3 = mid - mul[1] - (lo3 > lo);
+    const uint64_t hi3 = hi - (mid3 > mid);
+    *vm = shiftright128(mid3, hi3, (uint32_t) (j - 64 - 1));
+  } else {
+    const uint64_t lo3 = lo + lo;
+    const uint64_t mid3 = mid + mid + (lo3 < lo);
+    const uint64_t hi3 = hi + hi + (mid3 < mid);
+    const uint64_t lo4 = lo3 - mul[0];
+    const uint64_t mid4 = mid3 - mul[1] - (lo4 > lo3);
+    const uint64_t hi4 = hi3 - (mid4 > mid3);
+    *vm = shiftright128(mid4, hi4, (uint32_t) (j - 64));
+  }
+
+  return shiftright128(mid, hi, (uint32_t) (j - 64 - 1));
+}
+
+#endif // HAS_64_BIT_INTRINSICS
+
+#endif // RYU_D2S_INTRINSICS_H
diff --git a/c_src/ryu/digit_table.h b/c_src/ryu/digit_table.h
new file mode 100644
index 0000000..02219bc
--- /dev/null
+++ b/c_src/ryu/digit_table.h
@@ -0,0 +1,35 @@
+// Copyright 2018 Ulf Adams
+//
+// The contents of this file may be used under the terms of the Apache License,
+// Version 2.0.
+//
+//    (See accompanying file LICENSE-Apache or copy at
+//     http://www.apache.org/licenses/LICENSE-2.0)
+//
+// Alternatively, the contents of this file may be used under the terms of
+// the Boost Software License, Version 1.0.
+//    (See accompanying file LICENSE-Boost or copy at
+//     https://www.boost.org/LICENSE_1_0.txt)
+//
+// Unless required by applicable law or agreed to in writing, this software
+// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.
+#ifndef RYU_DIGIT_TABLE_H
+#define RYU_DIGIT_TABLE_H
+
+// A table of all two-digit numbers. This is used to speed up decimal digit
+// generation by copying pairs of digits into the final output.
+static const char DIGIT_TABLE[200] = {
+  '0','0','0','1','0','2','0','3','0','4','0','5','0','6','0','7','0','8','0','9',
+  '1','0','1','1','1','2','1','3','1','4','1','5','1','6','1','7','1','8','1','9',
+  '2','0','2','1','2','2','2','3','2','4','2','5','2','6','2','7','2','8','2','9',
+  '3','0','3','1','3','2','3','3','3','4','3','5','3','6','3','7','3','8','3','9',
+  '4','0','4','1','4','2','4','3','4','4','4','5','4','6','4','7','4','8','4','9',
+  '5','0','5','1','5','2','5','3','5','4','5','5','5','6','5','7','5','8','5','9',
+  '6','0','6','1','6','2','6','3','6','4','6','5','6','6','6','7','6','8','6','9',
+  '7','0','7','1','7','2','7','3','7','4','7','5','7','6','7','7','7','8','7','9',
+  '8','0','8','1','8','2','8','3','8','4','8','5','8','6','8','7','8','8','8','9',
+  '9','0','9','1','9','2','9','3','9','4','9','5','9','6','9','7','9','8','9','9'
+};
+
+#endif // RYU_DIGIT_TABLE_H
diff --git a/c_src/ryu/ryu.h b/c_src/ryu/ryu.h
new file mode 100644
index 0000000..e6cb809
--- /dev/null
+++ b/c_src/ryu/ryu.h
@@ -0,0 +1,37 @@
+// Copyright 2018 Ulf Adams
+// Copyright 2021 Thomas Depierre
+//
+// The contents of this file may be used under the terms of the Apache License,
+// Version 2.0.
+//
+//    (See accompanying file LICENSE-Apache or copy at
+//     http://www.apache.org/licenses/LICENSE-2.0)
+//
+// Alternatively, the contents of this file may be used under the terms of
+// the Boost Software License, Version 1.0.
+//    (See accompanying file LICENSE-Boost or copy at
+//     https://www.boost.org/LICENSE_1_0.txt)
+//
+// Unless required by applicable law or agreed to in writing, this software
+// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.
+#ifndef RYU_H
+#define RYU_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <inttypes.h>
+
+int d2s_buffered_n(double f, char* result);
+void d2s_buffered(double f, char* result);
+char* d2s(double f);
+
+//CHANGE_FOR_ERLANG we dropped all the other functions as not used by us
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // RYU_H
diff --git a/c_src/ryu/to_chars.h b/c_src/ryu/to_chars.h
new file mode 100644
index 0000000..a4ff145
--- /dev/null
+++ b/c_src/ryu/to_chars.h
@@ -0,0 +1,342 @@
+// SPDX-CopyrightText: Copyright (c) Microsoft Corporation.
+// SPDX-CopyrightText: Copyright 2018 Ulf Adams
+// SPDX-CopyrightText: Copyright 2021 Thomas Depierre
+// SPDX-CopyrightText: Copyright 2025 Copyright Ericsson AB 2025. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception AND BSL-1.0
+//h
+// The license information in the original file is not super clear.
+// Microsoft seems to have changed it from Apache-2.0 to Apache-2.0 WITH LLVM-exception
+// which they most likely are not allowed to do as the LLVM exception is less
+// restrictive than Apache-2.0 plain. There is not much we can do about that
+// though so we use their license.
+//
+// Library: STL
+// Git repository: https://github.com/microsoft/STL
+// Commit: e745bad3b1d05b5b19ec652d68abb37865ffa454
+// Original function: https://github.com/microsoft/STL/blob/e745bad3b1d05b5b19ec652d68abb37865ffa454/stl/inc/xcharconv_ryu.h#L1926
+
+//CHANGE_FOR_ERLANG: This format is new, it is here to handle the different format switch used in the STL code
+enum chars_format {
+    FMT_SCIENTIFIC,
+    FMT_FIXED,
+    FMT_GENERAL
+};  
+
+// This is inspired from the MS STL Charconv, under Apache with LLVM exception licence
+// see https://github.com/microsoft/STL/blob/main/LICENSE.txt
+// The inspiration is at https://github.com/microsoft/STL/blob/e745bad3b1d05b5b19ec652d68abb37865ffa454/stl/inc/xcharconv_ryu.h#L1926
+// CHANGE_FOR_ERLANG all the types and typecast have been adapted to C types from Cpp.
+// I have also kept the Ryu original function head as it allows to not impact the rest of the code
+// __v and __mantissa and __exponent have lost their double underscore over the whole function
+// all the test on the lenght of the buffer have been dropped too. This could need change, but
+// we always pass a 256 bytes buffer when we only need 26 bytes maximum.
+static inline int to_chars(const floating_decimal_64 v, const bool sign, char* const result) {
+  // Step 5: Print the decimal representation.
+  uint64_t __output = v.mantissa;
+  const int32_t _Ryu_exponent = v.exponent;
+  const uint32_t __olength = decimalLength17(__output);
+  int32_t _Scientific_exponent = _Ryu_exponent + ((int32_t) __olength) - 1;
+
+  // CHANGE_FOR_ERLANG: we use our chars_format instead of the STL one
+  enum chars_format _Fmt;
+
+  int32_t _Lower;
+  int32_t _Upper;
+
+  if (__olength == 1) {
+    // CHANGE_FOR_ERLANG the format and examples have been adapted to the erlang format
+    // as the original would have not shown a change in format
+    // (erlang always add ".0" to scientific format) and omit the + in the exponent
+    // Value | Fixed    | Scientific
+    // 1e-4  | "0.0001" | "1.0e-4"
+    // 1e2   | "100.0"  | "1.0e2"
+    // CHANGE_FOR_ERLANG the values for a switch, as seen in the example above, for erlang
+    // are different than for STL format.
+    _Lower = -4;
+    _Upper = 2;
+  } else if (_Scientific_exponent >= 10) {
+    // CHANGE_FOR_ERLANG This case does not exist for the STL and is due to the 
+    // negative sign in the exponent.
+    // Value        | Fixed           | Scientific
+    // 123456789e1  | "1234567890.0"  | "1.23456789e9"
+    // 123456789e2  | "12345678900.0" | "1.23456789e10"
+
+    _Lower = - (int32_t) (__olength + 2);
+    _Upper = 2;
+  } else {
+    // CHANGE_FOR_ERLANG the format and examples have been adapted to the erlang format
+    // as the original would have not shown a change in format
+    // (erlang always add ".0" to scientific format) and omit the + in the exponent
+    // Value   | Fixed      | Scientific
+    // 1234e-6 | "0.001234" | "1.234e-4"
+    // 1234e1  | "12340.0"  | "1.234e4"
+    // CHANGE_FOR_ERLANG the values for a switch, as seen in the example above, for erlang
+    // are different than for STL format.
+    _Lower = - (int32_t) (__olength + 2);
+    _Upper = 1;
+  }
+
+  if (_Lower <= _Ryu_exponent && _Ryu_exponent <= _Upper) {
+    // CHANGE_FOR_ERLANG this is added to handle the -2**53, 2**53 range special case
+    // These are edge cases not captured above, all the other are naturally handled
+    // by _Lower nad _Upper
+    if ((__output >= (1ull << 53) && _Ryu_exponent == 0) 
+          || (__output > ((1ull << 52) / 5) && _Ryu_exponent == 1)
+          || (__output > ((1ull << 51) / 25) && _Ryu_exponent == 2)) {
+      _Fmt = FMT_SCIENTIFIC;
+    } else {
+      _Fmt = FMT_FIXED;
+    }
+  } else {
+    // CHANGE_FOR_ERLANG we do not need to handle the %g case here.
+    _Fmt = FMT_SCIENTIFIC;
+  }
+
+  // CHANGE_FOR_ERLANG we handle the sign here as it is handled outside of this in the STL case
+  // and we need it to compute the start of the buffer for the characters after
+  if (sign) {
+    result[0] = '-';
+  }
+
+  // CHANGE_FOR_ERLANG we compute the start of the usable buffer. It is done here
+  // in order to be fixed for both branches of formatting.
+  char* const __result = result + sign;
+
+  if (_Fmt == FMT_FIXED) {
+    // CHANGE_FOR_ERLANG this whole table has been adapted to erlang examples to help
+    // debug and evolve the edge cases
+    // Example: __output == 1729, __olength == 4
+
+    // _Ryu_exponent | Printed  | _Whole_digits | _Total_fixed_length  | Notes
+    // --------------|----------|---------------|----------------------|---------------------------------------
+    //             1 | 17290.0  |  5            |  _Whole_digits + 2   | Unified length cases.
+    //             0 | 1729.0   |  4            |                      |
+    // --------------|----------|---------------|----------------------|---------------------------------------
+    //            -1 | 172.9    |  3            | __olength + 1        | This case can't happen for
+    //            -2 | 17.29    |  2            |                      | __olength == 1, but no additional
+    //            -3 | 1.729    |  1            |                      | code is needed to avoid it.
+    // --------------|----------|---------------|----------------------|---------------------------------------
+    //            -4 | 0.1729   |  0            | 2 - _Ryu_exponent    | If the decimal point appears, we need
+    //            -5 | 0.01729  | -1            |                      | to put the "0" in front
+    //            -6 | 0.001729 | -2            |                      |
+
+    const int32_t _Whole_digits = (int32_t) (__olength) + _Ryu_exponent;
+
+    uint32_t _Total_fixed_length;
+    if (_Ryu_exponent >= 0) { 
+      // CHANGE_FOR_ERLANG the examples and values have been adapted to erlang format one
+      // CHANGE_FOR_ERLANG we also dropped the whole adjustement, as it is only of value
+      // for %f which we do not handle
+      // cases "17290.0" and "1729.0"
+      _Total_fixed_length = (uint32_t) (_Whole_digits) + 2;
+    } else if (_Whole_digits > 0) { // case "17.29"
+      _Total_fixed_length = __olength + 1;
+    } else { // case "0.001729"
+      _Total_fixed_length = (uint32_t) (2 - _Ryu_exponent);
+    }
+
+    char* _Mid;
+    if (_Ryu_exponent >= 0) { // case "172900.0"
+      // CHANGE_FOR_ERLANG we do not need the can_use_ryu, as we are not doing %f
+      // but always shortest round_trip. The whole complexity here is dropped
+      // Print the decimal digits, left-aligned within [result, result + _Total_fixed_length).
+      _Mid = __result + __olength;
+    } else { // cases "1729.0", "17.29", and "0.001729"
+      // Print the decimal digits, right-aligned within [result, result + _Total_fixed_length).
+      _Mid = __result + _Total_fixed_length;
+    }
+
+    // We prefer 32-bit operations, even on 64-bit platforms.
+    // We have at most 17 digits, and uint32_t can store 9 digits.
+    // If __output doesn't fit into uint32_t, we cut off 8 digits,
+    // so the rest will fit into uint32_t.
+    // CHANGE_FOR_ERLANG we consider in this whole thing that memcopy use the same
+    // char has defined in the DIGIT_TABLE
+    // CHANGE_FOR_ERLANG __DIGIT_TABLE became DIGIT_TABLE
+    if ((__output >> 32) != 0) {
+      // Expensive 64-bit division.
+      const uint64_t __q = div1e8(__output);
+      uint32_t __output2 = (uint32_t) (__output - 100000000 * __q);
+      __output = __q;
+
+      const uint32_t __c = __output2 % 10000;
+      __output2 /= 10000;
+      const uint32_t __d = __output2 % 10000;
+      const uint32_t __c0 = (__c % 100) << 1;
+      const uint32_t __c1 = (__c / 100) << 1;
+      const uint32_t __d0 = (__d % 100) << 1;
+      const uint32_t __d1 = (__d / 100) << 1;
+
+      memcpy(_Mid -= 2, DIGIT_TABLE + __c0, 2);
+      memcpy(_Mid -= 2, DIGIT_TABLE + __c1, 2);
+      memcpy(_Mid -= 2, DIGIT_TABLE + __d0, 2);
+      memcpy(_Mid -= 2, DIGIT_TABLE + __d1, 2);
+    }
+    uint32_t __output2 = (uint32_t) __output;
+    while (__output2 >= 10000) {
+#ifdef __clang__ // TRANSITION, LLVM-38217
+      const uint32_t __c = __output2 - 10000 * (__output2 / 10000);
+#else
+      const uint32_t __c = __output2 % 10000;
+#endif
+      __output2 /= 10000;
+      const uint32_t __c0 = (__c % 100) << 1;
+      const uint32_t __c1 = (__c / 100) << 1;
+      memcpy(_Mid -= 2, DIGIT_TABLE + __c0, 2);
+      memcpy(_Mid -= 2, DIGIT_TABLE + __c1, 2);
+    }
+    if (__output2 >= 100) {
+      const uint32_t __c = (__output2 % 100) << 1;
+      __output2 /= 100;
+      memcpy(_Mid -= 2, DIGIT_TABLE + __c, 2);
+    }
+    if (__output2 >= 10) {
+      const uint32_t __c = __output2 << 1;
+      memcpy(_Mid -= 2, DIGIT_TABLE + __c, 2);
+    } else {
+      *--_Mid = (char) ('0' + __output2);
+    }
+
+    if (_Ryu_exponent > 0) { // case "172900.0"
+      // Performance note: it might be more efficient to do this immediately after setting _Mid.
+      // CHANGE_FOR_ERLANG we have different case here, so we have to add the ".0" here
+      // we use memset as we do not have access to fill_n
+      memset(__result + __olength, '0', (size_t) _Ryu_exponent);
+      __result[__olength + (size_t) _Ryu_exponent] = '.';
+      __result[__olength + (size_t) _Ryu_exponent + 1] = '0';
+    } else if (_Ryu_exponent == 0) { // case "1729.0"
+      // CHANGE_FOR_ERLANG we have different case here, so we have to add the ".0" here
+      __result[__olength] = '.';
+      __result[__olength + 1] = '0';
+    } else if (_Whole_digits > 0) { // case "17.29"
+      // Performance note: moving digits might not be optimal.
+      memmove(__result, __result + 1, (size_t) _Whole_digits);
+      __result[_Whole_digits] = '.';
+    } else { // case "0.001729"
+      // CHANGE_FOR_ERLANG we use the memset here as we do not have access to fill_n
+      // Performance note: a larger memset() followed by overwriting '.' might be more efficient.
+      __result[0] = '0';
+      __result[1] = '.';
+      memset(__result + 2, '0', (size_t) (-_Whole_digits));
+    }
+
+    // CHANGE_FOR_ERLANG we do not need the errc and we are only interested in
+    // returning the length, as it is what Ryu and erlang expect. We do add the
+    // sign as we did it here instead of adding it by default as in the STL
+    return _Total_fixed_length + sign;
+  }
+
+  uint32_t _Scientific_exponent_length;
+  // CHANGE_FOR_ERLANG we have to do a little bit more complex logic here because we do not always
+  // print the exponent sign, only if it is negative
+  if (_Scientific_exponent <= -100) { // "e-100"
+    _Scientific_exponent_length = 5;
+  } else if (_Scientific_exponent <= -10 || _Scientific_exponent >= 100) { // "e-10" or "e100"
+    _Scientific_exponent_length = 4;
+  } else if ((_Scientific_exponent > -10 && _Scientific_exponent < 0) || _Scientific_exponent >= 10) { // "e-9" or "e10"
+    _Scientific_exponent_length = 3;
+  } else { // "e1"
+    _Scientific_exponent_length = 2;
+  }
+
+  // CHANGE_FOR_ERLANG we do not need the ternary as we did all the logic above
+  const uint32_t _Total_scientific_length = __olength + 1 +(__olength == 1) // digits + decimal point + possible 0 after decimal point
+    + _Scientific_exponent_length; // + scientific exponent
+
+  // Print the decimal digits.
+  uint32_t __i = 0;
+  // We prefer 32-bit operations, even on 64-bit platforms.
+  // We have at most 17 digits, and uint32_t can store 9 digits.
+  // If __output doesn't fit into uint32_t, we cut off 8 digits,
+  // so the rest will fit into uint32_t.
+    // CHANGE_FOR_ERLANG we consider in this whole thing that memcopy use the same
+    // char has defined in the DIGIT_TABLE
+    // CHANGE_FOR_ERLANG __DIGIT_TABLE became DIGIT_TABLE
+  if ((__output >> 32) != 0) {
+    // Expensive 64-bit division.
+    const uint64_t __q = div1e8(__output);
+    uint32_t __output2 = (uint32_t) (__output) - 100000000 * (uint32_t) (__q);
+    __output = __q;
+
+    const uint32_t __c = __output2 % 10000;
+    __output2 /= 10000;
+    const uint32_t __d = __output2 % 10000;
+    const uint32_t __c0 = (__c % 100) << 1;
+    const uint32_t __c1 = (__c / 100) << 1;
+    const uint32_t __d0 = (__d % 100) << 1;
+    const uint32_t __d1 = (__d / 100) << 1;
+    memcpy(__result + __olength - __i - 1, DIGIT_TABLE + __c0, 2);
+    memcpy(__result + __olength - __i - 3, DIGIT_TABLE + __c1, 2);
+    memcpy(__result + __olength - __i - 5, DIGIT_TABLE + __d0, 2);
+    memcpy(__result + __olength - __i - 7, DIGIT_TABLE + __d1, 2);
+    __i += 8;
+  }
+  uint32_t __output2 = (uint32_t) (__output);
+  while (__output2 >= 10000) {
+#ifdef __clang__ // TRANSITION, LLVM-38217
+    const uint32_t __c = __output2 - 10000 * (__output2 / 10000);
+#else
+    const uint32_t __c = __output2 % 10000;
+#endif
+    __output2 /= 10000;
+    const uint32_t __c0 = (__c % 100) << 1;
+    const uint32_t __c1 = (__c / 100) << 1;
+    memcpy(__result + __olength - __i - 1, DIGIT_TABLE + __c0, 2);
+    memcpy(__result + __olength - __i - 3, DIGIT_TABLE + __c1, 2);
+    __i += 4;
+  }
+  if (__output2 >= 100) {
+    const uint32_t __c = (__output2 % 100) << 1;
+    __output2 /= 100;
+    memcpy(__result + __olength - __i - 1, DIGIT_TABLE + __c, 2);
+    __i += 2;
+  }
+  if (__output2 >= 10) {
+    const uint32_t __c = __output2 << 1;
+    // We can't use memcpy here: the decimal dot goes between these two digits.
+    __result[2] = DIGIT_TABLE[__c + 1];
+    __result[0] = DIGIT_TABLE[__c];
+  } else {
+    __result[0] = (char) ('0' + __output2);
+  }
+
+  // Print decimal point if needed.
+  uint32_t __index;
+  if (__olength > 1) {
+    __result[1] = '.';
+    __index = __olength + 1;
+  } else {
+    // In erlang we _have_ to print the ".0" in the case this is an integer
+    __result[1] = '.';
+    __result[2] = '0';
+    __index = __olength + 2;
+  }
+
+  // Print the exponent.
+  __result[__index++] = 'e';
+  if (_Scientific_exponent < 0) {
+    __result[__index++] = '-';
+    _Scientific_exponent = -_Scientific_exponent;
+  }
+  // CHANGE_FOR_ERLANG no else, as we do not print the positive sign on the exponent
+
+  if (_Scientific_exponent >= 100) {
+    const int32_t __c = _Scientific_exponent % 10;
+    memcpy(__result + __index, DIGIT_TABLE + 2 * (_Scientific_exponent / 10), 2);
+    __result[__index + 2] = (char) ('0' + __c);
+    __index += 3;
+  } else if (_Scientific_exponent >= 10) {
+    // CHANGE_FOR_ERLANG we have to do this only if the exponent is larger than 10
+    memcpy(__result + __index, DIGIT_TABLE + 2 * _Scientific_exponent, 2);
+    __index += 2;
+  } else { 
+  // CHANGE_FOR_ERLANG we can have an exponent under 10, which is not handled by the table
+  // so we handle it here
+    __result[__index++] = (char) ('0' + _Scientific_exponent);
+  }
+
+    // CHANGE_FOR_ERLANG we do not need the errc and we are only interested in
+    // returning the length, as it is what Ryu and erlang expect. We do add the
+    // sign as we did it here instead of adding it by default as in the STL
+  return _Total_scientific_length + sign;
+}
diff --git a/rebar.config b/rebar.config
index 2cd6b3b..e86c145 100644
--- a/rebar.config
+++ b/rebar.config
@@ -4,7 +4,7 @@
     {"priv/jiffy.so", [
         "c_src/*.c",
         "c_src/*.cc",
-        "c_src/double-conversion/*.cc"
+        "c_src/ryu/*.c"
     ]}
 ]}.
 
diff --git a/test/jiffy_03_number_tests.erl b/test/jiffy_03_number_tests.erl
index b19dffd..56759ff 100644
--- a/test/jiffy_03_number_tests.erl
+++ b/test/jiffy_03_number_tests.erl
@@ -55,12 +55,12 @@ cases(ok) ->
         {
             <<"1234567890123456789012345.0">>,
             1.23456789012345678e24,
-            <<"1.2345678901234568e+24">>
+            <<"1.2345678901234568e24">>
         },
         {
             <<"1234567890123456789012345.0E3">>,
             1.2345678901234569e27,
-            <<"1.2345678901234569e+27">>
+            <<"1.2345678901234569e27">>
         },
         {
             <<"1234567890123456789012345012">>,
@@ -71,18 +71,18 @@ cases(ok) ->
         {
             <<"0.000000000000000000000000000000000001">>,
             1.0E-36,
-            <<"1e-36">>
+            <<"1.0e-36">>
         },
         {<<"0.75">>, 0.75},
         {<<"2.0123456789">>, 2.0123456789, <<"2.0123456789">>},
-        {<<"2.4234324E24">>, 2.4234324E24, <<"2.4234324e+24">>},
+        {<<"2.4234324E24">>, 2.4234324E24, <<"2.4234324e24">>},
         {<<"-3.1416">>, -3.1416, <<"-3.1416">>},
-        {<<"1E4">>, 10000.0, <<"10000.0">>},
+        {<<"1E4">>, 10000.0, <<"1.0e4">>},
         {<<"1.0E+01">>, 10.0, <<"10.0">>},
         {<<"1e1">>, 10.0, <<"10.0">>},
         {<<"3.0E2">>, 300.0, <<"300.0">>},
         {<<"0E3">>, 0.0, <<"0.0">>},
-        {<<"1.5E3">>, 1500.0, <<"1500.0">>},
+        {<<"1.5E3">>, 1500.0, <<"1.5e3">>},
         {<<"2.5E-1">>, 0.25, <<"0.25">>},
         {<<"-0.325E+2">>, -32.5, <<"-32.5">>},
         %% Large integers to exercise digits10 (11-12+ digit path)


(couchdb-jiffy) 28/49: Faster map encoding with enif_make_map_from_arrays

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit f84f7845d33fe77d5d69ae351d7c2fe75ff5cb8b
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Fri Apr 3 23:44:49 2026 -0400

    Faster map encoding with enif_make_map_from_arrays
    
    Optimize map encoding: instead of updating the map element by element, use
    `enif_make_map_from_arrays` to create the map in a single API call.
    
    The documentation for the `enif_make_map_from_arrays` indicates `If successful,
    this function sets *map_out to the new map and returns true. Returns false
    there are any duplicate keys.` However, it turns out this API has a bug, at
    least as of the time of this commit (Apr, 2026), where it does not properly
    detect duplicates and instead can return an invalid map object [1]. To work
    around the issue, if we see a large map, we first remove the duplicates as we
    do for props, and only then call `enif_make_map_from_arrays`
    
    Since `enif_make_map_from_arrays` is newer we bump the minimum Erlang version
    by 1 version number: from 20 to 21. That still gives as an 8 years worth of
    Erlang versions support.
    
    And lastly, since the "seen" deduplication set was the only thing keeping us
    from dropping C++, implement a simple open addressing hash table to replace and
    it drop C++ as a dependency. The hash table is very basic since we know its size
    ahead of time we don't have to resize it. We don't remove objects from it and
    we get to use enif_* functions for hashing and comparison and such. So it's a
    tiny bit more code but it's dropping a large dependency.
    
    Overall, this yields a decent 1.5x-2x speed-ups for object-heavy benchmarks, at
    the expense of a slightly messier code and losing OTP 20 support:
    
    ```
    Formatting results...
    ===== With input Blockchain =====
    Name                     ips        average  deviation         median         99th %
    jiffy                 8.72 K      114.73 us    +/-15.02%      106.39 us      186.28 us
    jiffy (master)        4.85 K      206.03 us    +/-65.05%      164.38 us      869.99 us
    Comparison:
    jiffy                 8.72 K
    jiffy (master)        4.85 K - 1.80x slower +91.30 us
    ===== With input Giphy =====
    Name                     ips        average  deviation         median         99th %
    jiffy                 791.56        1.26 ms    +/-48.35%        1.27 ms        2.26 ms
    jiffy (master)        510.21        1.96 ms    +/-62.14%        1.84 ms        3.31 ms
    Comparison:
    jiffy                 791.56
    jiffy (master)        510.21 - 1.55x slower +0.70 ms
    ===== With input GitHub =====
    Name                     ips        average  deviation         median         99th %
    jiffy                 2.99 K      334.18 us    +/-31.88%      292.58 us      672.58 us
    jiffy (master)        1.58 K      634.12 us   +/-107.87%      636.50 us     1112.16 us
    Comparison:
    jiffy                 2.99 K
    jiffy (master)        1.58 K - 1.90x slower +299.94 us
    ===== With input GovTrack =====
    Name                     ips        average  deviation         median         99th %
    jiffy                  23.28       42.96 ms     +/-8.28%       41.99 ms       57.78 ms
    jiffy (master)          9.98      100.21 ms     +/-8.27%       98.87 ms      133.05 ms
    Comparison:
    jiffy                  23.28
    jiffy (master)          9.98 - 2.33x slower +57.25 ms
    ===== With input Issue 90 =====
    Name                     ips        average  deviation         median         99th %
    jiffy                  51.78       19.31 ms    +/-11.90%       18.46 ms       27.40 ms
    jiffy (master)         45.29       22.08 ms     +/-6.48%       21.50 ms       28.60 ms
    Comparison:
    jiffy                  51.78
    jiffy (master)         45.29 - 1.14x slower +2.77 ms
    ===== With input JSON Generator =====
    Name                     ips        average  deviation         median         99th %
    jiffy                 774.15        1.29 ms    +/-34.64%        1.26 ms        2.23 ms
    jiffy (master)        326.05        3.07 ms     +/-9.14%        3.01 ms        4.22 ms
    Comparison:
    jiffy                 774.15
    jiffy (master)        326.05 - 2.37x slower +1.78 ms
    ===== With input JSON Generator (Pretty) =====
    Name                     ips        average  deviation         median         99th %
    jiffy                 843.42        1.19 ms    +/-17.96%        1.21 ms        1.89 ms
    jiffy (master)        429.22        2.33 ms    +/-12.48%        2.23 ms        3.38 ms
    Comparison:
    jiffy                 843.42
    jiffy (master)        429.22 - 1.96x slower +1.14 ms
    ===== With input Large Numbers =====
    Name                     ips        average  deviation         median         99th %
    jiffy (master)        268.80        3.72 ms     +/-9.06%        3.69 ms        4.86 ms
    jiffy                 255.66        3.91 ms    +/-52.92%        3.63 ms       10.46 ms
    Comparison:
    jiffy (master)        268.80
    jiffy                 255.66 - 1.05x slower +0.191 ms
    ===== With input Pokedex =====
    Name                     ips        average  deviation         median         99th %
    jiffy                 881.07        1.13 ms    +/-98.50%        0.99 ms        2.13 ms
    jiffy (master)        417.85        2.39 ms    +/-19.26%        2.43 ms        3.94 ms
    Comparison:
    jiffy                 881.07
    jiffy (master)        417.85 - 2.11x slower +1.26 ms
    ===== With input UTF-8 escaped =====
    Name                     ips        average  deviation         median         99th %
    jiffy (master)        8.80 K      113.69 us    +/-26.59%      109.55 us      186.69 us
    jiffy                 8.12 K      123.12 us    +/-63.53%      115.93 us      234.39 us
    Comparison:
    jiffy (master)        8.80 K
    jiffy                 8.12 K - 1.08x slower +9.42 us
    ===== With input UTF-8 unescaped =====
    Name                     ips        average  deviation         median         99th %
    jiffy                13.05 K       76.63 us    +/-35.80%       72.95 us      140.51 us
    jiffy (master)       12.56 K       79.64 us    +/-12.14%       77.81 us      123.34 us
    Comparison:
    jiffy                13.05 K
    jiffy (master)       12.56 K - 1.04x slower +3.01 us
    ```
    
    [1] https://github.com/erlang/otp/issues/10975
---
 .github/workflows/ci.yml            |   2 +-
 c_src/objects.c                     | 230 ++++++++++++++++++++++++++++++++++++
 c_src/objects.cc                    |  77 ------------
 rebar.config                        |   9 +-
 test/jiffy_06_object_tests.erl      |  54 +++++++++
 test/jiffy_16_dedupe_keys_tests.erl |  37 ++++++
 6 files changed, 324 insertions(+), 85 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 46a1090..f8c89cb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,7 +17,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        otp_version: [20,23,26,27,28]
+        otp_version: [21,23,26,27,28]
         os: [ubuntu-latest]
         rebar: [rebar, rebar3]
 
diff --git a/c_src/objects.c b/c_src/objects.c
new file mode 100644
index 0000000..184606d
--- /dev/null
+++ b/c_src/objects.c
@@ -0,0 +1,230 @@
+// This file is part of Jiffy released under the MIT license.
+// See the LICENSE file for more information.
+
+#include <assert.h>
+#include <string.h>
+
+#include "erl_nif.h"
+
+// Must match MAP_SMALL_MAP_LIMIT in OTP's erts/emulator/beam/erl_map.h.
+// enif_make_map_from_arrays correctly rejects duplicate keys for
+// flatmaps but not for hashmaps (see OTP issue #10975) we rely on
+// detecting when we can skip deduplicating based on the small map
+// size limit.
+#define JIFFY_MAP_SMALL_MAP_LIMIT 32
+
+// Limit up to when we'll allocate the hash table on the stack.
+// Must be a power of 2, since we're using a bitmask. Also, we want
+// to stay well below 4KB on the stack. That can be checked wit
+// something like:
+//  clang -O3 -S -fverbose-asm c_src/objects.c -I$(erlnif_include) -Ic_src && grep rsp objects.s
+//    subq    $2680, %rsp
+//
+#define HT_STACK_SLOTS 128
+
+typedef struct {
+    ERL_NIF_TERM key;
+    int used;
+} ht_slot;
+
+// We're using masks, so get the next power of 2 starting with 16.
+static unsigned int
+ht_size_power_of_2(unsigned int count)
+{
+    unsigned int p = 16;
+    unsigned int target = count * 2;
+    while(p < target) {
+        p <<= 1;
+    }
+    return p;
+}
+
+// Insert key into table. Returns 1 if inserted, 0 if duplicate. For the salt
+// pass the monotonic nanosecond time or something like that.
+static inline int
+ht_insert(ErlNifEnv* env, ht_slot* table, unsigned int mask,  ERL_NIF_TERM key, ErlNifUInt64 salt)
+{
+    ErlNifUInt64 h = enif_hash(ERL_NIF_INTERNAL_HASH, key, salt);
+    unsigned int idx = (unsigned int)(h & mask);
+    while(table[idx].used) {
+        if(enif_compare(table[idx].key, key) == 0) {
+            return 0;
+        }
+        idx = (idx + 1) & mask;
+    }
+    table[idx].key = key;
+    table[idx].used = 1;
+    return 1;
+}
+
+
+static int
+make_map(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out)
+{
+    ERL_NIF_TERM ret;
+    ERL_NIF_TERM key;
+    ERL_NIF_TERM val;
+
+    // Get the item count up front so we can size the arrays and
+    // (if needed) the dedup hash table.
+    unsigned int list_len = 0;
+    int rv = enif_get_list_length(env, pairs, &list_len);
+    assert(rv && "pairs must be a list");
+    assert((list_len % 2) == 0 && "Unbalanced object pairs.");
+    unsigned int count = list_len / 2;
+
+    if(count == 0) {
+        *out = enif_make_new_map(env);
+        return 1;
+    }
+
+    // Stack allocation is a pointer bump. If we don't initialize and use
+    // it, it should not be a performance hit. Using alloca is another
+    // option, but that one is non-standard so avoid it for now.
+    ERL_NIF_TERM small_keys[JIFFY_MAP_SMALL_MAP_LIMIT];
+    ERL_NIF_TERM small_vals[JIFFY_MAP_SMALL_MAP_LIMIT];
+    ERL_NIF_TERM* keys = (count <= JIFFY_MAP_SMALL_MAP_LIMIT)
+        ? small_keys
+        : (ERL_NIF_TERM*) enif_alloc(count * sizeof(ERL_NIF_TERM));
+    ERL_NIF_TERM* vals = (count <= JIFFY_MAP_SMALL_MAP_LIMIT)
+        ? small_vals
+        : (ERL_NIF_TERM*) enif_alloc(count * sizeof(ERL_NIF_TERM));
+
+    // Go in reverse order so that last write wins. It's just the behavior
+    // we had before and we're preserving it here for backward compatibility.
+    unsigned int i = count;
+    while(enif_get_list_cell(env, pairs, &val, &pairs)) {
+        if(!enif_get_list_cell(env, pairs, &key, &pairs)) {
+            assert(0 && "Unbalanced object pairs.");
+        }
+        --i;
+        keys[i] = key;
+        vals[i] = val;
+    }
+
+    // With OTP issue #10975 we know enif_make_map_from_arrays works for
+    // small maps (<=32 keys), so we can safely use it and not dedup first.
+    if(count <= JIFFY_MAP_SMALL_MAP_LIMIT) {
+        rv = enif_make_map_from_arrays(env, keys, vals, count, &ret);
+        if(rv) {
+            *out = ret;
+            if(keys != small_keys) {
+                enif_free(keys);
+            }
+            if(vals != small_vals) {
+                enif_free(vals);
+            }
+            return 1;
+        }
+        // Found dups: fall through, dedup, and try again.
+    }
+
+    // Build a hash table for dedup. Walk arrays from the back so the
+    // last JSON value for each key is seen first (last-write-wins).
+    unsigned int ht_size = ht_size_power_of_2(count);
+    ht_slot stack_table[HT_STACK_SLOTS];
+    ht_slot* table = (ht_size <= HT_STACK_SLOTS)
+        ? stack_table
+        : (ht_slot*) enif_alloc(ht_size * sizeof(ht_slot));
+    memset(table, 0, ht_size * sizeof(ht_slot));
+    unsigned int mask = ht_size - 1;
+    ErlNifUInt64 salt = (ErlNifUInt64) enif_monotonic_time(ERL_NIF_NSEC);
+
+    unsigned int unique = 0;
+    for(int j = (int)count - 1; j >= 0; j--) {
+        if(ht_insert(env, table, mask, keys[j], salt)) {
+            unique++;
+            keys[count - unique] = keys[j];
+            vals[count - unique] = vals[j];
+        }
+    }
+
+    if(table != stack_table) {
+        enif_free(table);
+    }
+
+    rv = enif_make_map_from_arrays(env,
+        keys + count - unique,
+        vals + count - unique,
+        unique, &ret);
+    assert(rv && "enif_make_map_from_arrays failed after dedup");
+
+    if(keys != small_keys) {
+        enif_free(keys);
+    }
+    if(vals != small_vals) {
+        enif_free(vals);
+    }
+
+    *out = ret;
+    return 1;
+}
+
+
+// Build an EJSON proplist {[{key, val}, ...]} from the interleaved pairs list.
+//
+// When dedupe_keys is set, only the last occurrence of each key is kept
+// (last-wins).
+//
+static int
+make_proplist(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out,
+    int dedupe_keys)
+{
+    ERL_NIF_TERM ret;
+    ERL_NIF_TERM key;
+    ERL_NIF_TERM val;
+
+    ht_slot stack_table[HT_STACK_SLOTS];
+    ht_slot* table = NULL;
+    unsigned int mask = 0;
+    ErlNifUInt64 salt = 0;
+
+    if(dedupe_keys) {
+        unsigned int list_len = 0;
+        int rv = enif_get_list_length(env, pairs, &list_len);
+        assert(rv && "pairs must be a list");
+        unsigned int count = list_len / 2;
+        unsigned int ht_size = ht_size_power_of_2(count > 0 ? count : 1);
+        table = (ht_size <= HT_STACK_SLOTS)
+            ? stack_table
+            : (ht_slot*) enif_alloc(ht_size * sizeof(ht_slot));
+        memset(table, 0, ht_size * sizeof(ht_slot));
+        mask = ht_size - 1;
+        salt = (ErlNifUInt64) enif_monotonic_time(ERL_NIF_NSEC);
+    }
+
+    ret = enif_make_list(env, 0);
+    while(enif_get_list_cell(env, pairs, &val, &pairs)) {
+        if(!enif_get_list_cell(env, pairs, &key, &pairs)) {
+            assert(0 && "Unbalanced object pairs.");
+        }
+        if(dedupe_keys) {
+            if(ht_insert(env, table, mask, key, salt)) {
+                val = enif_make_tuple2(env, key, val);
+                ret = enif_make_list_cell(env, val, ret);
+            }
+        } else {
+            val = enif_make_tuple2(env, key, val);
+            ret = enif_make_list_cell(env, val, ret);
+        }
+    }
+
+    if(dedupe_keys && table != stack_table) {
+        enif_free(table);
+    }
+
+    *out = enif_make_tuple1(env, ret);
+    return 1;
+}
+
+
+int
+make_object(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out,
+    int ret_map, int dedupe_keys)
+{
+    if(ret_map) {
+        return make_map(env, pairs, out);
+    } else {
+        return make_proplist(env, pairs, out, dedupe_keys);
+    }
+}
diff --git a/c_src/objects.cc b/c_src/objects.cc
deleted file mode 100644
index 121e062..0000000
--- a/c_src/objects.cc
+++ /dev/null
@@ -1,77 +0,0 @@
-// This file is part of Jiffy released under the MIT license.
-// See the LICENSE file for more information.
-
-#include <string>
-
-#if defined(__cplusplus) && (__cplusplus >= 201103L)
-  #include <unordered_set>
-  typedef std::unordered_set<std::string> string_set;
-#else
-  #include <set>
-  typedef std::set<std::string> string_set;
-#endif
-
-#include <assert.h>
-
-#include "erl_nif.h"
-
-#define BEGIN_C extern "C" {
-#define END_C }
-
-BEGIN_C
-
-int
-make_object(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out,
-	int ret_map, int dedupe_keys)
-{
-    ERL_NIF_TERM ret;
-    ERL_NIF_TERM key;
-    ERL_NIF_TERM val;
-
-    string_set seen;
-
-    ERL_NIF_TERM old_val;
-
-    if(ret_map) {
-        ret = enif_make_new_map(env);
-        while(enif_get_list_cell(env, pairs, &val, &pairs)) {
-            if(!enif_get_list_cell(env, pairs, &key, &pairs)) {
-                assert(0 == 1 && "Unbalanced object pairs.");
-            }
-            if(!enif_get_map_value(env, ret, key, &old_val)) {
-                if(!enif_make_map_put(env, ret, key, val, &ret)) {
-                    return 0;
-                }
-            }
-        }
-        *out = ret;
-        return 1;
-    }
-
-    ret = enif_make_list(env, 0);
-    while(enif_get_list_cell(env, pairs, &val, &pairs)) {
-        if(!enif_get_list_cell(env, pairs, &key, &pairs)) {
-            assert(0 == 1 && "Unbalanced object pairs.");
-        }
-        if(dedupe_keys) {
-            ErlNifBinary bin;
-            if(!enif_inspect_binary(env, key, &bin)) {
-                return 0;
-            }
-            std::string skey((char*) bin.data, bin.size);
-            if(seen.count(skey) == 0) {
-                seen.insert(skey);
-                val = enif_make_tuple2(env, key, val);
-                ret = enif_make_list_cell(env, val, ret);
-            }
-        } else {
-            val = enif_make_tuple2(env, key, val);
-            ret = enif_make_list_cell(env, val, ret);
-        }
-    }
-    *out = enif_make_tuple1(env, ret);
-
-    return 1;
-}
-
-END_C
diff --git a/rebar.config b/rebar.config
index e86c145..960b5ba 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,9 +1,8 @@
-{require_min_otp_vsn, "20"}.
+{require_min_otp_vsn, "21"}.
 
 {port_specs, [
     {"priv/jiffy.so", [
         "c_src/*.c",
-        "c_src/*.cc",
         "c_src/ryu/*.c"
     ]}
 ]}.
@@ -17,13 +16,9 @@
 
     {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)",
         "CFLAGS", "$CFLAGS -Ic_src/ -g -Wall $FLTO_FLAG -Werror -O3 -fvisibility=hidden"},
-    {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)",
-        "CXXFLAGS", "$CXXFLAGS -Ic_src/ -g -Wall $FLTO_FLAG -Werror -O3 -fvisibility=hidden"},
 
     {"(linux|solaris|freebsd|netbsd|openbsd|dragonfly|darwin|gnu)",
-        "LDFLAGS", "$LDFLAGS $FLTO_FLAG -lstdc++"},
-
-    {"win32", "CXXFLAGS", "$CXXFLAGS /O2 /DNDEBUG"}
+        "LDFLAGS", "$LDFLAGS $FLTO_FLAG"}
 ]}.
 
 {eunit_opts, [
diff --git a/test/jiffy_06_object_tests.erl b/test/jiffy_06_object_tests.erl
index 67e2283..db43c92 100644
--- a/test/jiffy_06_object_tests.erl
+++ b/test/jiffy_06_object_tests.erl
@@ -67,3 +67,57 @@ cases(error) ->
         <<"{\"key\": 123 true">>,
         <<"{\"key\": 123,}">>
     ].
+
+
+% We put terms for large objects (> 32 keys) on the heap vs on the stack. So we
+% have to ensure we test large object as well not just smaller ones to exercise
+% that path.
+%
+large_object_maps_test_() ->
+    Opts = [return_maps],
+    [
+        {"20 duplicate keys (10 as 0, 10 as 1)",
+            fun() ->
+                KVs = [{I rem 2, I} || I <- lists:seq(1, 20)],
+                {Json, Expected} = large_obj_kvs(KVs),
+                round_trip(Json, Expected, Opts)
+            end},
+        {"25 keys",
+            fun() ->
+                {Json, Expected} = large_obj(25),
+                round_trip(Json, Expected, Opts)
+            end},
+        {"100 keys",
+            fun() ->
+                {Json, Expected} = large_obj(100),
+                round_trip(Json, Expected, Opts)
+            end},
+        {"1000 keys",
+            fun() ->
+                {Json, Expected} = large_obj(1000),
+                round_trip(Json, Expected, Opts)
+            end},
+        {"100 duplicate keys (50 as 0, 50 as 1)",
+            fun() ->
+                KVs = [{I rem 2, I} || I <- lists:seq(1, 100)],
+                {Json, Expected} = large_obj_kvs(KVs),
+                round_trip(Json, Expected, Opts)
+            end}
+    ].
+
+round_trip(Json, Expected, Opts) ->
+    Decoded = dec(Json, Opts),
+    ?assertEqual(Expected, Decoded),
+    Json1 = enc(Decoded),
+    Decoded1 = dec(Json1, Opts),
+    ?assertEqual(Expected, Decoded1).
+
+large_obj(N) when is_integer(N) ->
+    large_obj_kvs([{I, I} || I <- lists:seq(0, N - 1)]).
+
+large_obj_kvs(KVs) when is_list(KVs) ->
+    KVs1 = [{"k" ++ integer_to_list(K), V} || {K, V} <- KVs],
+    Chunks = [io_lib:format("\"~s\":~B", [K, V]) || {K, V} <- KVs1],
+    Json = iolist_to_binary(["{", lists:join(",", Chunks), "}"]),
+    Expected = maps:from_list([{list_to_binary(K), V} || {K, V} <- KVs1]),
+    {Json, Expected}.
diff --git a/test/jiffy_16_dedupe_keys_tests.erl b/test/jiffy_16_dedupe_keys_tests.erl
index 9719f49..90c2264 100644
--- a/test/jiffy_16_dedupe_keys_tests.erl
+++ b/test/jiffy_16_dedupe_keys_tests.erl
@@ -5,6 +5,43 @@
 
 -include_lib("eunit/include/eunit.hrl").
 
+% Duplicate keys with `return_maps`. We settled on
+% last value wins semantics in such cases so test that
+% we preserve that
+%
+duplicate_keys_maps_test_() ->
+    Opts = [return_maps],
+    Cases = [
+        % Simple duplicate
+        {
+            <<"{\"a\":1,\"a\":2}">>,
+            #{<<"a">> => 2}
+        },
+        % Duplicate with other keys
+        {
+            <<"{\"a\":1,\"b\":2,\"a\":3}">>,
+            #{<<"a">> => 3, <<"b">> => 2}
+        },
+        % Triple duplicate
+        {
+            <<"{\"x\":1,\"x\":2,\"x\":3}">>,
+            #{<<"x">> => 3}
+        },
+        % Duplicate in nested object
+        {
+            <<"{\"outer\":{\"k\":1,\"k\":2}}">>,
+            #{<<"outer">> => #{<<"k">> => 2}}
+        },
+        % No duplicates
+        {
+            <<"{\"a\":1,\"b\":2,\"c\":3}">>,
+            #{<<"a">> => 1, <<"b">> => 2, <<"c">> => 3}
+        }
+    ],
+    {"Test duplicate keys with maps", lists:map(fun({Json, Result}) ->
+        ?_assertEqual(Result, jiffy:decode(Json, Opts))
+    end, Cases)}.
+
 dedupe_keys_test_() ->
     Opts = [dedupe_keys],
     Cases = [


(couchdb-jiffy) 14/49: Plain ASCII scan ahead optimization in string decoding

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit dbd7bcc2804ccf37ee936ff36fbe9fdeff8a3664
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Tue Mar 31 17:26:47 2026 -0400

    Plain ASCII scan ahead optimization in string decoding
    
    Seeing some of the SWAR [1] [2] optimizations going around for json parsing,
    took at look jiffy, and before we get fancy with casting to 64bit ints, and
    doing xors and such, let's try a simple look-ahead parsing which turns out
    already gives us a nice speed boost.
    
    jason/poison bench shows a 40% speed improvement for what they call in the
    benchmark: "Issue 90".
    
    A new thing we're using here is the `restrict` keyword which is a C99 type
    qualifier [3] for pointer to mark object which won't be aliased through any
    other pointer. This lets the compiler do a lot more optimizations to the
    underlying machine code (verified this by staring at the assembly code),
    because it doesn't have always write back to the memory and it can keep more
    state in the registers.
    
    There are likely more of these opportunities around - both lookaheads (for
    whitespace parsing?) and perhaps sprinkling in `restrict` qualifiers. If it
    comes to it can always borrow Dr. Lemire's neat SWAR trick, but for now let's
    see far we can go a simpler approach...
    
    With master branch
    
    ```
    Name               ips        average  deviation         median         99th %
    jiffy            13.97       71.56 ms     �1.65%       71.42 ms       76.51 ms
    OTP json          9.36      106.84 ms     �4.24%      106.98 ms      113.52 ms
    
    Comparison:
    jiffy            13.97
    OTP json          9.36 - 1.49x slower +35.28 ms
    ```
    
    With this PR
    
    ```
    Name               ips        average  deviation         median         99th %
    jiffy            52.05       19.21 ms    �13.54%       18.32 ms       32.48 ms
    OTP json          8.83      113.23 ms     �0.79%      112.96 ms      115.99 ms
    
    Comparison:
    jiffy            52.05
    OTP json          8.83 - 5.89x slower +94.02 ms
    ```
    
    [1] https://lemire.me/blog/2025/04/13/detect-control-characters-quotes-and-backslashes-efficiently-using-swar/
    
    [2] https://github.com/erlang/otp/pull/10938
    
    [3] https://en.cppreference.com/w/c/language/restrict.html
---
 c_src/decoder.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 1f39394..93efd90 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -272,7 +272,18 @@ dec_string(Decoder* d, ERL_NIF_TERM* value)
                     return 0;
             }
         } else if(d->p[d->i] < 0x80) {
-            d->i++;
+            // Scan ahead plain ASCII as an optimization
+            const unsigned char* restrict p = d->p;
+            size_t idx = d->i + 1;
+            const size_t len = d->len;
+            while(idx < len
+                    && p[idx] >= 0x20
+                    && p[idx] < 0x80
+                    && p[idx] != '\"'
+                    && p[idx] != '\\') {
+                idx++;
+            }
+            d->i = idx;
         } else {
             ulen = utf8_validate(&(d->p[d->i]), d->len - d->i);
             if(ulen == 0) {


(couchdb-jiffy) 37/49: Cleanup jiffy erl module

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 5681d76519f539b3ce1a9ac9a316d706b67d1139
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Sat Apr 11 02:17:51 2026 -0400

    Cleanup jiffy erl module
    
    Remove the no_native bit, it's not needed any longer. Also our supported
    versions of Erlang have direct binary_to_float/1 and binary_to_integer/1 so we
    have to proxy through binary_to_list/1. Also we can do direct splits with
    binary:split/2 and not have to turn it into a list so we can use
    string:to_integer/1
---
 src/jiffy.erl | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)

diff --git a/src/jiffy.erl b/src/jiffy.erl
index b1f3b98..b7bf6f6 100644
--- a/src/jiffy.erl
+++ b/src/jiffy.erl
@@ -5,8 +5,6 @@
 -export([decode/1, decode/2, encode/1, encode/2]).
 -define(NOT_LOADED, not_loaded(?LINE)).
 
--compile([no_native]).
-
 -on_load(init/0).
 
 
@@ -101,15 +99,11 @@ encode(Data, Options) ->
 
 
 finish_decode({bignum, Value}) ->
-    list_to_integer(binary_to_list(Value));
+    binary_to_integer(Value);
 finish_decode({bignum_e, Value}) ->
-    {IVal, EVal} = case string:to_integer(binary_to_list(Value)) of
-        {I, [$e | ExpStr]} ->
-            {E, []} = string:to_integer(ExpStr),
-            {I, E};
-        {I, [$E | ExpStr]} ->
-            {E, []} = string:to_integer(ExpStr),
-            {I, E}
+    {IVal, EVal} = case binary:split(Value, [<<$e>>, <<$E>>]) of
+        [IStr, EStr] ->
+            {binary_to_integer(IStr), binary_to_integer(EStr)}
     end,
     try
         IVal * math:pow(10, EVal)
@@ -119,7 +113,7 @@ finish_decode({bignum_e, Value}) ->
     end;
 finish_decode({bigdbl, Value}) ->
     try
-        list_to_float(binary_to_list(Value))
+        binary_to_float(Value)
     catch
         error:badarg ->
             error({range, Value})


(couchdb-jiffy) 20/49: Plain ASCII scan ahead optimization in string encoding

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit f8a7c77b6ba79de515162d7c5207a36e91fa3d4d
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Thu Apr 2 01:02:31 2026 -0400

    Plain ASCII scan ahead optimization in string encoding
    
    What's good for the gander is good for the ... duck?
    
    Apply the same lookahead optimization from string [1] and number decoding [2] to string
    and atom encoding. Hoist the index update (`e->i`) out of the loop and mark the
    source pointer as restricted. Another good perf boost comes from a single final
    `memcpy` instead of updating the buffer a byte at a time.
    
    As a curiosity, while the compiler doesn't do any fancy auto-vectorization in
    the while loop, it manages to use a single bitmask check instruction (bt) to
    check for all three special cases `"`, `/` and '\' which is kind of neat:
    
    ```c
     i++;
     while(i < size && p[i] >= 0x20 && p[i] < 0x80
           && p[i] != '\"' && p[i] != '\\' && p[i] != '/'
      ) {i++;}
    ```
    
    AT&T syntax (sorry)
    ```
    leaq    1(%r15), %rdx               ## i++
    movzbl  %dil, %r8d                  ## r8d contains p[i]
    addl    $-34, %r8d                  ## r8d -= '"'
    cmpl    $58, %r8d                   ## if r8d - '\' > 0
    ja      LBB12_23                    ## then jump away
    movabsq $288230376151719937, %rdi   ## imm = 0x400000000002001
    btq     %r8, %rdi                   ## if bitmask matches " / and \
    jae     LBB12_23                    ## then jump away
    ```
    
    Using the bench benchee benchmark [3], this optimization shows decent results
    for the encoding side. For the long strings case (issue 90) we get a 5x(!)
    speedup. Some like GovTrack and Giphy get about a 30-40% boost. Others are
    lower, and Canada, since it's number heavy (coordinates), unsurprisingly
    doesn't change much.
    
    ===== Master =====
    
    ```
    ===== With input Blockchain =====
    Name               ips        average  deviation         median         99th %
    jiffy           8.58 K      116.54 us    +/-50.23%      109.87 us      199.99 us
    
    ===== With input Canada =====
    Name               ips        average  deviation         median         99th %
    jiffy            32.70       30.58 ms     +/-2.13%       30.51 ms       34.36 ms
    
    ===== With input Giphy =====
    Name               ips        average  deviation         median         99th %
    jiffy           780.56        1.28 ms    +/-14.11%        1.23 ms        1.65 ms
    
    ===== With input GitHub =====
    Name               ips        average  deviation         median         99th %
    jiffy           2.35 K        0.43 ms    +/-14.52%        0.40 ms        0.60 ms
    
    ===== With input GovTrack =====
    Name               ips        average  deviation         median         99th %
    jiffy            26.39       37.89 ms     +/-3.95%       37.77 ms       47.00 ms
    
    ===== With input Issue 90 =====
    Name               ips        average  deviation         median         99th %
    jiffy            15.97       62.60 ms     +/-6.27%       61.84 ms       96.36 ms
    
    ===== With input JSON Generator =====
    Name               ips        average  deviation         median         99th %
    jiffy           775.51        1.29 ms    +/-12.40%        1.32 ms        1.81 ms
    
    ===== With input Pokedex =====
    Name               ips        average  deviation         median         99th %
    jiffy           1.35 K        0.74 ms     +/-9.76%        0.74 ms        0.95 ms
    
    ===== With input UTF-8 unescaped =====
    Name               ips        average  deviation         median         99th %
    jiffy           8.20 K      121.88 us     +/-7.75%      119.79 us      161.74 us
    ```
    
    ===== PR =====
    ```
    ===== With input Blockchain =====
    Name               ips        average  deviation         median         99th %
    jiffy           9.86 K      101.46 us    +/-13.45%       96.60 us      162.25 us
    
    ===== With input Canada =====
    Name               ips        average  deviation         median         99th %
    jiffy            31.41       31.84 ms    +/-10.76%       31.33 ms       58.22 ms
    
    ===== With input Giphy =====
    Name               ips        average  deviation         median         99th %
    jiffy           1.03 K        0.97 ms    +/-17.42%        0.91 ms        1.38 ms
    
    ===== With input GitHub =====
    Name               ips        average  deviation         median         99th %
    jiffy           3.17 K        0.32 ms    +/-21.45%        0.28 ms        0.51 ms
    
    ===== With input GovTrack =====
    Name               ips        average  deviation         median         99th %
    jiffy            39.19       25.51 ms    +/-14.82%       23.80 ms       37.69 ms
    
    ===== With input Issue 90 =====
    Name               ips        average  deviation         median         99th %
    jiffy            84.84       11.79 ms     +/-3.38%       11.71 ms       14.03 ms
    
    ===== With input JSON Generator =====
    Name               ips        average  deviation         median         99th %
    jiffy           1.10 K        0.91 ms    +/-21.32%        0.85 ms        1.55 ms
    
    ===== With input Pokedex =====
    Name               ips        average  deviation         median         99th %
    jiffy           1.44 K        0.70 ms    +/-48.81%        0.63 ms        1.69 ms
    
    ===== With input UTF-8 unescaped =====
    Name               ips        average  deviation         median         99th %
    jiffy           9.19 K      108.82 us     +/-7.33%      107.02 us      141.75 us
    ```
    
    [1] https://github.com/davisp/jiffy/pull/252
    [2] https://github.com/davisp/jiffy/pull/254
    [2] https://github.com/nickva/bench
---
 c_src/encoder.c | 43 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 40 insertions(+), 3 deletions(-)

diff --git a/c_src/encoder.c b/c_src/encoder.c
index 0cf1b08..96f2c24 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -360,6 +360,7 @@ enc_atom(Encoder* e, ERL_NIF_TERM val)
     unsigned char data[512];
 
     size_t size;
+    size_t start;
     size_t i;
 
     if(!enif_get_atom(e->env, val, (char*)data, 512, ERL_NIF_LATIN1)) {
@@ -384,8 +385,25 @@ enc_atom(Encoder* e, ERL_NIF_TERM val)
         if(enc_special_character(e, data[i])) {
             i++;
         } else if(data[i] < 0x80) {
-            e->p[e->i++] = data[i];
+            // Scan ahead for plain ASCII chars which don't need escaping.
+            // Since optionally users could escape forward slashes, too, we
+            // stop on them as well
+            start = i;
             i++;
+            while(i < size
+                    && data[i] >= 0x20
+                    && data[i] < 0x80
+                    && data[i] != '\"'
+                    && data[i] != '\\'
+                    && data[i] != '/') {
+                i++;
+            }
+            size_t run = i - start;
+            if(!enc_ensure(e, run)) {
+                return 0;
+            }
+            memcpy(&(e->p[e->i]), &data[start], run);
+            e->i += run;
         } else if(data[i] >= 0x80) {
             /* The atom encoding is latin1, so we don't need validation
              * as all latin1 characters are valid Unicode codepoints. */
@@ -415,11 +433,12 @@ enc_string(Encoder* e, ERL_NIF_TERM val)
     static const int MAX_ESCAPE_LEN = 12;
     ErlNifBinary bin;
 
-    unsigned char* data;
+    unsigned char* JIFFY_RESTRICT data;
     size_t size;
     int esc_len;
     size_t ulen;
     int uval;
+    size_t start;
     size_t i;
 
     if(!enif_inspect_binary(e->env, val, &bin)) {
@@ -445,7 +464,25 @@ enc_string(Encoder* e, ERL_NIF_TERM val)
         if(enc_special_character(e, data[i])) {
             i++;
         } else if(data[i] < 0x80) {
-            e->p[e->i++] = data[i++];
+            // Scan ahead for plain ASCII char and memcpy them. Stop at quotes,
+            // backslashes, and forward slashes, since users can optionally
+            // choose to escape them too.
+            start = i;
+            i++;
+            while(i < size
+                    && data[i] >= 0x20
+                    && data[i] < 0x80
+                    && data[i] != '\"'
+                    && data[i] != '\\'
+                    && data[i] != '/') {
+                i++;
+            }
+            size_t run = i - start;
+            if(!enc_ensure(e, run)) {
+                return 0;
+            }
+            memcpy(&(e->p[e->i]), &data[start], run);
+            e->i += run;
         } else if(data[i] >= 0x80) {
             ulen = utf8_validate(&(data[i]), size - i);
 


(couchdb-jiffy) 40/49: 100% compliance with RFC 8259

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 34d4b1ef518d8eb55880b02399daf3af996cb997
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Sat Apr 11 21:25:45 2026 -0400

    100% compliance with RFC 8259
    
    Previously Jiffy was a tiny bit more linient and accepted numbers like
    0.3e+ and that's not a valid JSON number, it turns out.
---
 c_src/decoder.c                         | 27 +++++++++++++++++++++++++++
 test/jiffy_03_number_tests.erl          | 16 +++++++++++++++-
 test/jiffy_22_json_test_suite_tests.erl | 25 +++++--------------------
 3 files changed, 47 insertions(+), 21 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 861f32b..d934100 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -33,6 +33,7 @@ enum {
     nst_frac1,
     nst_frac,
     nst_esign,
+    nst_echeck,
     nst_edigit
 } JsonNumState;
 
@@ -549,6 +550,9 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                 switch(p[idx]) {
                     case '-':
                     case '+':
+                        state = nst_echeck;
+                        idx++;
+                        break;
                     case '0':
                     case '1':
                     case '2':
@@ -567,6 +571,28 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                 }
                 break;
 
+             case nst_echeck:
+                switch(p[idx]) {
+                    case '0':
+                    case '1':
+                    case '2':
+                    case '3':
+                    case '4':
+                    case '5':
+                    case '6':
+                    case '7':
+                    case '8':
+                    case '9':
+                        state = nst_edigit;
+                        while(idx < len && p[idx] >= '0' && p[idx] <= '9') {
+                            idx++;
+                        }
+                        break;
+                    default:
+                        goto parse;
+                }
+                break;
+
             case nst_edigit:
                 switch(p[idx]) {
                     case '0':
@@ -601,6 +627,7 @@ parse:
         case nst_sign:
         case nst_frac1:
         case nst_esign:
+        case nst_echeck:
             return 0;
         default:
             break;
diff --git a/test/jiffy_03_number_tests.erl b/test/jiffy_03_number_tests.erl
index 56759ff..2a8fdbb 100644
--- a/test/jiffy_03_number_tests.erl
+++ b/test/jiffy_03_number_tests.erl
@@ -91,7 +91,11 @@ cases(ok) ->
         {<<"-10000000000">>, -10000000000},
         %% Number parsing edge cases
         {<<"-0.0E1">>, -0.0, <<"0.0">>},
-        {<<"1E-1">>, 1.0e-1, <<"0.1">>}
+        {<<"1E-1">>, 1.0e-1, <<"0.1">>},
+        {<<"1e0">>, 1.0, <<"1.0">>},
+        {<<"1e01">>, 10.0, <<"10.0">>},
+        {<<"1e00001">>, 10.0, <<"10.0">>},
+        {<<"1E001">>, 10.0, <<"10.0">>}
     ];
 
 cases(error) ->
@@ -106,6 +110,16 @@ cases(error) ->
         <<"1E">>,
         <<"1-E2">>,
         <<"2E +3">>,
+        <<"2e+">>,
+        <<"2e-">>,
+        <<"3E+">>,
+        <<"3E-">>,
+        <<"0.1e-">>,
+        <<"0.1e+">>,
+        <<"0.1E-">>,
+        <<"0.1E+">>,
+        <<"1e-e">>,
+        <<"1e+e">>,
         <<"1E3000">>,
         <<"0.1E30000">>,
         <<"1EA">>
diff --git a/test/jiffy_22_json_test_suite_tests.erl b/test/jiffy_22_json_test_suite_tests.erl
index 8a71ab1..d3d52d6 100644
--- a/test/jiffy_22_json_test_suite_tests.erl
+++ b/test/jiffy_22_json_test_suite_tests.erl
@@ -60,32 +60,17 @@ gen_whatever({Name, Json}) ->
     end}.
 
 
-% Jiffy is linient here and we deviate from strict RFC 8259:
-%  - accept truncated exponents e.g. 0.3e+ is 0.3 for us so
-%    add an exception for it
-%
-known_deviations() ->
-    [
-        "n_number_0.3e+",
-        "n_number_0_capital_E+",
-        "n_number_0e+",
-        "n_number_1.0e+",
-        "n_number_1.0e-"
-    ].
-
-
 classify(Files) ->
     classify(Files, [], [], []).
 
 classify([], Y, N, I) ->
     {lists:reverse(Y), lists:reverse(N), lists:reverse(I)};
 classify([{Name, _Json} = F | Rest], Y, N, I) ->
-    case {Name, lists:member(Name, known_deviations())} of
-        {_, true}    -> classify(Rest, Y, N, [F | I]);
-        {"y_" ++ _, _} -> classify(Rest, [F | Y], N, I);
-        {"n_" ++ _, _} -> classify(Rest, Y, [F | N], I);
-        {"i_" ++ _, _} -> classify(Rest, Y, N, [F | I]);
-        _              -> classify(Rest, Y, N, I)
+    case Name of
+        "y_" ++ _ -> classify(Rest, [F | Y], N, I);
+        "n_" ++ _ -> classify(Rest, Y, [F | N], I);
+        "i_" ++ _ -> classify(Rest, Y, N, [F | I]);
+        _         -> classify(Rest, Y, N, I)
     end.
 
 


(couchdb-jiffy) 38/49: Another l2b/b2l cleanup

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 34b00c10d5fe0346f40c9e4909cfda6f47389ece
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Sat Apr 11 02:31:40 2026 -0400

    Another l2b/b2l cleanup
---
 src/jiffy.erl | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/jiffy.erl b/src/jiffy.erl
index b7bf6f6..fd9c230 100644
--- a/src/jiffy.erl
+++ b/src/jiffy.erl
@@ -153,8 +153,7 @@ finish_encode([], Acc) ->
 finish_encode([<<_/binary>>=B | Rest], Acc) ->
     finish_encode(Rest, [B | Acc]);
 finish_encode([Val | Rest], Acc) when is_integer(Val) ->
-    Bin = list_to_binary(integer_to_list(Val)),
-    finish_encode(Rest, [Bin | Acc]);
+    finish_encode(Rest, [integer_to_binary(Val) | Acc]);
 finish_encode([InvalidEjson | _], _) ->
     error({invalid_ejson, InvalidEjson});
 finish_encode(_, _) ->


(couchdb-jiffy) 49/49: Speed up UTF8 validation by about 25%

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit a81f9b716d1c56daae6e702708df3e6990fcd488
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Mon Apr 20 00:55:21 2026 -0400

    Speed up UTF8 validation by about 25%
    
    Use a pseudo-simd (SWAR-like) trick to validate UTF8 faster. There are
    essentially two improvements here:
    
       1) Use a table similar to `hexvals` to quicky get the length and knock out
       some of the invalid cases
    
       2) check continuation bytes in parallel by packing them into an int and then
       applying a mask on all of them
    
    All in all, we get a nice perf boost on UTF8-heavy benchmarks and it's not just
    stats padding since some users have data that is UTF8-heavy so it seems worthy
    at the expense of making the code a tiny bit more complicated.
    
    The idea here is not new, and originally was presented by Keiser and Lemire in
    "Validating UTF-8 In Less Than One Instruction Per
    Byte" (https://arxiv.org/pdf/2010.03090) and a practical C implementation of
    loading all bytes into an unsigned is form
    [yyjson](https://github.com/ibireme/yyjson).
---
 c_src/jiffy_utf8.h | 125 ++++++++++++++++++++++++++++++++---------------------
 1 file changed, 76 insertions(+), 49 deletions(-)

diff --git a/c_src/jiffy_utf8.h b/c_src/jiffy_utf8.h
index b75b9ad..f6891e9 100644
--- a/c_src/jiffy_utf8.h
+++ b/c_src/jiffy_utf8.h
@@ -145,68 +145,95 @@ utf8_to_unicode(unsigned char* buf, size_t size)
     return ret;
 }
 
+// Lead-byte length table
+//   0     : invalid lead byte, len 2 overlongs, > U+10FFFF [F5..FF]
+//   1     : ASCII
+//   2,3,4 : lead byte of that sequence
+//
+// Note: we mark some overlongs here right away (2 char ones and greater than
+// F4). Below we'll only need continuation byte checks and two boundary checks
+// (b0 and b1) for overlong 3 and 4, surrogate 3 and over-long 4.
+//
+// The idea is originally from https://arxiv.org/pdf/2010.03090 "Validating
+// UTF-8 In Less Than One Instruction Per Byte" with an accompanying blog
+// https://lemire.me/blog/2020/10/20/ridiculously-fast-unicode-utf-8-validation
+//
+static const unsigned char utf8_seq_len[256] = {
+    /* 0x00 */ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+    /* 0x10 */ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+    /* 0x20 */ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+    /* 0x30 */ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+    /* 0x40 */ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+    /* 0x50 */ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+    /* 0x60 */ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+    /* 0x70 */ 1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,
+    /* 0x80 */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+    /* 0x90 */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+    /* 0xA0 */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+    /* 0xB0 */ 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
+    /* 0xC0 */ 0,0,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
+    /* 0xD0 */ 2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,
+    /* 0xE0 */ 3,3,3,3,3,3,3,3, 3,3,3,3,3,3,3,3,
+    /* 0xF0 */ 4,4,4,4,4,0,0,0, 0,0,0,0,0,0,0,0
+    /*         0 1 2 3 4 5 6 7  8 9 A B C D E F */
+};
+
 static inline size_t
-utf8_validate(unsigned char* data, size_t size)
+utf8_validate(const unsigned char* JIFFY_RESTRICT data, size_t size)
 {
-    size_t ulen = 0;
-    int ui;
-    size_t i;
-
-    if((data[0] & 0x80) == 0x00) {
-        ulen = 1;
-    } else if((data[0] & 0xE0) == 0xC0) {
-        ulen = 2;
-    } else if((data[0] & 0xF0) == 0xE0) {
-        ulen = 3;
-    } else if((data[0] & 0xF8) == 0xF0) {
-        ulen = 4;
-    }
-    if(ulen == 0 || ulen > size) {
+    unsigned int b0 = data[0];
+    unsigned int len = utf8_seq_len[b0];
+    if(len == 0 || len > size) {
         return 0;
     }
-
-    // Check each continuation byte.
-    for(i = 1; i < ulen; i++) {
-        if((data[i] & 0xC0) != 0x80) return 0;
+    if(len == 1) {
+        // We should never get here from dec_string since we pre-filter bytes < 0x80.
+        return 1; // LCOV_EXCL_LINE
     }
 
-    // Wikipedia says I have to check that a UTF-8 encoding
-    // uses as few bits as possible. This means that we
-    // can't do things like encode 't' in three bytes.
-    // To check this all we need to ensure is that for each
-    // of the following bit patterns that there is at least
-    // one 1 bit in any of the x's
-    //  1: 0yyyyyyy
-    //  2: 110xxxxy 10yyyyyy
-    //  3: 1110xxxx 10xyyyyy 10yyyyyy
-    //  4: 11110xxx 10xxyyyy 10yyyyyy 10yyyyyy
+    // This is the tricky bit: pack the 1 to 3 continuation bytes into an
+    // unsigned int then do masked compares on all at the same time. I saw this
+    // the first time in yyjson library.
+    unsigned int cont = (unsigned int)data[1];
+    if(len >= 3) {
+        cont |= (unsigned int)data[2] << 8;
+    }
+    if(len == 4) {
+        cont |= (unsigned int)data[3] << 16;
+    }
+    static const unsigned int CONT_MASK[5]   = {0, 0, 0x0000C0, 0x00C0C0, 0xC0C0C0};
+    static const unsigned int CONT_EXPECT[5] = {0, 0, 0x000080, 0x008080, 0x808080};
+    if((cont & CONT_MASK[len]) != CONT_EXPECT[len]) {
+        return 0;
+    }
 
-    // ulen == 1 passes by definition
-    if(ulen == 2) {
-        if((data[0] & 0x1E) == 0)
-            return 0;
-    } else if(ulen == 3) {
-        if((data[0] & 0x0F) + (data[1] & 0x20) == 0)
-            return 0;
-    } else if(ulen == 4) {
-        if((data[0] & 0x07) + (data[1] & 0x30) == 0)
-            return 0;
+    if(len == 2) {
+        return 2;
     }
 
-    // Lastly we need to check some miscellaneous ranges for
-    // some of the larger code point values.
-    if(ulen >= 3) {
-        ui = utf8_to_unicode(data, ulen);
-        if(ui < 0) {
+    // Boundary checks based on b0 and b1. The length table has already
+    // filtered overlong 2-byte leads and leads > F4. (See table note)
+    unsigned int b1 = cont & 0xFF;
+    if(len == 3) {
+        // E0 80..9F is overlong 3-byte
+        if((b0 == 0xE0) & (b1 < 0xA0)) {
             return 0;
-        } else if(ui >= 0xD800 && ui <= 0xDFFF) {
-            return 0;
-        } else if(ui > 0x10FFFF) {
+        }
+        // ED A0..BF is a surrogate.
+        if((b0 == 0xED) & (b1 >= 0xA0)) {
             return 0;
         }
+        return 3;
     }
-
-    return ulen;
+    // F0 80..8F overlong 4-byte
+    if((b0 == 0xF0) & (b1 < 0x90)) {
+        return 0;
+    }
+    // F4 90..BF these are invalid
+    if((b0 == 0xF4) & (b1 >= 0x90)) {
+        return 0;
+    }
+    return 4;
 }
 
 static inline int


(couchdb-jiffy) 16/49: Use the restrict pointer qualifier when decoding numbers

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit efaaae1fd24d203d97e2c8edad5e03e20fe0c3c3
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Wed Apr 1 01:50:06 2026 -0400

    Use the restrict pointer qualifier when decoding numbers
    
    What's good for the goose is good for the gander. Use the same "trick" in
    `dec_number` as we did in `dec_string`. To make it work pull out d->p and d->i
    and then avoiding updating d->i on every digit. Only update it once at the end.
    
    On master
    
    ```
    ===== With input Giphy =====
    Name               ips        average  deviation         median         99th %
    jiffy           404.94        2.47 ms    +/-54.98%        2.26 ms        4.72 ms
    
    ===== With input GitHub =====
    Name               ips        average  deviation         median         99th %
    jiffy           1.20 K        0.83 ms    +/-27.98%        0.82 ms        1.60 ms
    
    ===== With input GovTrack =====
    Name               ips        average  deviation         median         99th %
    jiffy             7.64      130.82 ms    +/-11.64%      134.32 ms      153.93 ms
    
    ===== With input Issue 90 =====
    Name               ips        average  deviation         median         99th %
    jiffy            42.52       23.52 ms    +/-32.15%       20.04 ms       49.11 ms
    
    ===== With input JSON Generator =====
    Name               ips        average  deviation         median         99th %
    jiffy           323.84        3.09 ms    +/-60.78%        2.81 ms        8.63 ms
    
    ===== With input JSON Generator (Pretty) =====
    Name               ips        average  deviation         median         99th %
    jiffy           221.14        4.52 ms    +/-80.60%        3.53 ms       18.46 ms
    
    ===== With input Pokedex =====
    Name               ips        average  deviation         median         99th %
    jiffy           307.46        3.25 ms    +/-59.47%        2.84 ms       13.28 ms
    ```
    
    With PR
    ```
    ===== With input Giphy =====
    Name               ips        average  deviation         median         99th %
    jiffy           494.10        2.02 ms     +/-4.45%        2.01 ms        2.37 ms
    
    ===== With input GitHub =====
    Name               ips        average  deviation         median         99th %
    jiffy           1.54 K        0.65 ms    +/-33.14%        0.68 ms        0.78 ms
    
    ===== With input GovTrack =====
    Name               ips        average  deviation         median         99th %
    jiffy             8.38      119.34 ms    +/-26.92%      110.64 ms      226.90 ms
    
    ===== With input Issue 90 =====
    Name               ips        average  deviation         median         99th %
    jiffy            46.22       21.64 ms     +/-4.33%       21.30 ms       26.07 ms
    
    ===== With input JSON Generator =====
    Name               ips        average  deviation         median         99th %
    jiffy           408.25        2.45 ms     +/-9.09%        2.48 ms        2.92 ms
    
    ===== With input JSON Generator (Pretty) =====
    Name               ips        average  deviation         median         99th %
    jiffy           338.91        2.95 ms    +/-73.30%        2.76 ms        5.37 ms
    
    ===== With input Pokedex =====
    Name               ips        average  deviation         median         99th %
    jiffy           484.97        2.06 ms     +/-3.21%        2.07 ms        2.24 ms
    ```
---
 c_src/decoder.c | 70 ++++++++++++++++++++++++++++++++++-----------------------
 1 file changed, 42 insertions(+), 28 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 0b440d9..1f67b41 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -381,23 +381,32 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
     ERL_NIF_TERM num_type = d->atoms->atom_error;
     char state = nst_init;
     char nbuf[NUM_BUF_LEN];
-    int st = d->i;
     int has_frac = 0;
     int has_exp = 0;
     double dval;
     long lval;
 
-    while(d->i < d->len) {
+    // Use the same trick as did for dec_string. The restrict qualifier hints
+    // to the compiler p won't alias any other pointers so it can optimize
+    // access to it. Also avoid writing back do d->i on every increment,
+    // instead increment a local variable (hopefully in a register) then update
+    // d->i once at the end.
+    const unsigned char* JIFFY_RESTRICT p = d->p;
+    const size_t len = d->len;
+    const size_t start = d->i;
+    size_t idx = start;
+
+    while(idx < len) {
         switch(state) {
             case nst_init:
-                switch(d->p[d->i]) {
+                switch(p[idx]) {
                     case '-':
                         state = nst_sign;
-                        d->i++;
+                        idx++;
                         break;
                     case '0':
                         state = nst_frac0;
-                        d->i++;
+                        idx++;
                         break;
                     case '1':
                     case '2':
@@ -409,18 +418,19 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                     case '8':
                     case '9':
                         state = nst_mantissa;
-                        d->i++;
+                        idx++;
                         break;
                     default:
+                        d->i = idx;
                         return 0;
                 }
                 break;
 
             case nst_sign:
-                switch(d->p[d->i]) {
+                switch(p[idx]) {
                     case '0':
                         state = nst_frac0;
-                        d->i++;
+                        idx++;
                         break;
                     case '1':
                     case '2':
@@ -432,23 +442,24 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                     case '8':
                     case '9':
                         state = nst_mantissa;
-                        d->i++;
+                        idx++;
                         break;
                     default:
+                        d->i = idx;
                         return 0;
                 }
                 break;
 
             case nst_mantissa:
-                switch(d->p[d->i]) {
+                switch(p[idx]) {
                     case '.':
                         state = nst_frac1;
-                        d->i++;
+                        idx++;
                         break;
                     case 'e':
                     case 'E':
                         state = nst_esign;
-                        d->i++;
+                        idx++;
                         break;
                     case '0':
                     case '1':
@@ -460,7 +471,7 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                     case '7':
                     case '8':
                     case '9':
-                        d->i++;
+                        idx++;
                         break;
                     default:
                         goto parse;
@@ -468,15 +479,15 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                 break;
 
             case nst_frac0:
-                switch(d->p[d->i]) {
+                switch(p[idx]) {
                     case '.':
                         state = nst_frac1;
-                        d->i++;
+                        idx++;
                         break;
                     case 'e':
                     case 'E':
                         state = nst_esign;
-                        d->i++;
+                        idx++;
                         break;
                     default:
                         goto parse;
@@ -485,7 +496,7 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
 
             case nst_frac1:
                 has_frac = 1;
-                switch(d->p[d->i]) {
+                switch(p[idx]) {
                     case '0':
                     case '1':
                     case '2':
@@ -497,7 +508,7 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                     case '8':
                     case '9':
                         state = nst_frac;
-                        d->i++;
+                        idx++;
                         break;
                     default:
                         goto parse;
@@ -505,11 +516,11 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                 break;
 
             case nst_frac:
-                switch(d->p[d->i]) {
+                switch(p[idx]) {
                     case 'e':
                     case 'E':
                         state = nst_esign;
-                        d->i++;
+                        idx++;
                         break;
                     case '0':
                     case '1':
@@ -521,7 +532,7 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                     case '7':
                     case '8':
                     case '9':
-                        d->i++;
+                        idx++;
                         break;
                     default:
                         goto parse;
@@ -530,7 +541,7 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
 
             case nst_esign:
                 has_exp = 1;
-                switch(d->p[d->i]) {
+                switch(p[idx]) {
                     case '-':
                     case '+':
                     case '0':
@@ -544,15 +555,16 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                     case '8':
                     case '9':
                         state = nst_edigit;
-                        d->i++;
+                        idx++;
                         break;
                     default:
+                        d->i = idx;
                         return 0;
                 }
                 break;
 
             case nst_edigit:
-                switch(d->p[d->i]) {
+                switch(p[idx]) {
                     case '0':
                     case '1':
                     case '2':
@@ -563,7 +575,7 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                     case '7':
                     case '8':
                     case '9':
-                        d->i++;
+                        idx++;
                         break;
                     default:
                         goto parse;
@@ -571,11 +583,13 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                 break;
 
             default:
+                d->i = idx;
                 return 0;
         }
     }
 
 parse:
+    d->i = idx;
 
     switch(state) {
         case nst_init:
@@ -589,9 +603,9 @@ parse:
 
     errno = 0;
 
-    if(d->i - st < NUM_BUF_LEN) {
+    if(d->i - start < NUM_BUF_LEN) {
         memset(nbuf, 0, NUM_BUF_LEN);
-        memcpy(nbuf, &(d->p[st]), d->i - st);
+        memcpy(nbuf, &p[start], d->i - start);
 
         if(has_frac || has_exp) {
             dval = strtod(nbuf, NULL);
@@ -617,7 +631,7 @@ parse:
     }
 
     d->is_partial = 1;
-    *value = enif_make_sub_binary(d->env, d->arg, st, d->i - st);
+    *value = enif_make_sub_binary(d->env, d->arg, start, d->i - start);
     *value = enif_make_tuple2(d->env, num_type, *value);
     return 1;
 }


(couchdb-jiffy) 31/49: Add extra tests and cleanup to increase coverage

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 57682e4679666723eb72f84387559884c8e267e2
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Fri Apr 10 13:55:43 2026 -0400

    Add extra tests and cleanup to increase coverage
    
    Tests are pretty self-explanatory, just a bunch of corner cases like empty
    objects with `return_maps` we never tested, or props with atom keys. Most fun
    ones are the truncated literals like `tru`, `nul` etc.
    
    The NIF lifecyle `reload` callback is deprecate to so we an slap a NULL in
    there instead.
    
    https://www.erlang.org/doc/apps/erts/erl_nif.html
    
    > The fourth argument NULL is ignored. It was earlier used for the deprecated
    reload callback which is no longer supported since OTP 20.
---
 c_src/jiffy.c                       |  8 +-------
 test/jiffy_02_literal_tests.erl     | 10 ++++++++++
 test/jiffy_04_string_tests.erl      |  6 ++++++
 test/jiffy_06_object_tests.erl      |  8 ++++++++
 test/jiffy_16_dedupe_keys_tests.erl |  3 +++
 5 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/c_src/jiffy.c b/c_src/jiffy.c
index 790cf65..9d59aa3 100644
--- a/c_src/jiffy.c
+++ b/c_src/jiffy.c
@@ -63,12 +63,6 @@ load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
     return 0;
 }
 
-static int
-reload(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
-{
-    return 0;
-}
-
 static int
 upgrade(ErlNifEnv* env, void** priv, void** old_priv, ERL_NIF_TERM info)
 {
@@ -93,4 +87,4 @@ static ErlNifFunc funcs[] =
     {"nif_encode_iter", 3, encode_iter, 0}
 };
 
-ERL_NIF_INIT(jiffy, funcs, &load, &reload, &upgrade, &unload);
+ERL_NIF_INIT(jiffy, funcs, &load, NULL, &upgrade, &unload);
diff --git a/test/jiffy_02_literal_tests.erl b/test/jiffy_02_literal_tests.erl
index 5a6a17f..caf41cf 100644
--- a/test/jiffy_02_literal_tests.erl
+++ b/test/jiffy_02_literal_tests.erl
@@ -43,3 +43,13 @@ null_term_test_() ->
     ],
     {"null_term",
         [?_assertEqual(R, dec(<<"null">>, O)) || {R, O} <- T]}.
+
+truncated_literal_test_() ->
+    [?_assertError(_, dec(V)) || V <- [
+        <<"tru">>,
+        <<"fals">>,
+        <<"nul">>,
+        <<"t">>,
+        <<"f">>,
+        <<"n">>
+    ]].
diff --git a/test/jiffy_04_string_tests.erl b/test/jiffy_04_string_tests.erl
index f923a31..a8c9446 100644
--- a/test/jiffy_04_string_tests.erl
+++ b/test/jiffy_04_string_tests.erl
@@ -13,6 +13,12 @@ latin1_atom_test_() ->
     Expected = <<"{\"", 195, 164, "\":\"bar\"}">>,
     ?_assertEqual(Expected, jiffy:encode({[{Key, <<"bar">>}]})).
 
+atom_key_test_() ->
+    [
+        ?_assertEqual(<<"{\"foo\":1}">>, enc({[{foo, 1}]})),
+        ?_assertEqual(<<"{\"bar\":2}">>, enc({[{bar, 2}]}))
+    ].
+
 
 string_success_test_() ->
     [gen(ok, Case) || Case <- cases(ok)].
diff --git a/test/jiffy_06_object_tests.erl b/test/jiffy_06_object_tests.erl
index db43c92..1803207 100644
--- a/test/jiffy_06_object_tests.erl
+++ b/test/jiffy_06_object_tests.erl
@@ -73,9 +73,17 @@ cases(error) ->
 % have to ensure we test large object as well not just smaller ones to exercise
 % that path.
 %
+empty_object_maps_test_() ->
+    ?_assertEqual(#{}, dec(<<"{}">>, [return_maps])).
+
 large_object_maps_test_() ->
     Opts = [return_maps],
     [
+        {"200 keys",
+            fun() ->
+                {Json, Expected} = large_obj(200),
+                round_trip(Json, Expected, Opts)
+            end},
         {"20 duplicate keys (10 as 0, 10 as 1)",
             fun() ->
                 KVs = [{I rem 2, I} || I <- lists:seq(1, 20)],
diff --git a/test/jiffy_16_dedupe_keys_tests.erl b/test/jiffy_16_dedupe_keys_tests.erl
index 90c2264..7713e51 100644
--- a/test/jiffy_16_dedupe_keys_tests.erl
+++ b/test/jiffy_16_dedupe_keys_tests.erl
@@ -95,3 +95,6 @@ dedupe_keys_test_() ->
         Json = jiffy:encode(Data),
         ?_assertEqual(Result, jiffy:decode(Json, Opts))
     end, Cases)}.
+
+dedupe_keys_empty_test() ->
+    ?assertEqual({[]}, jiffy:decode(<<"{}">>, [dedupe_keys])).


(couchdb-jiffy) 09/49: Add extra number tests

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 8f5016201cb7c562f4a393115049aa5fdf42b31d
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Tue Mar 31 00:52:38 2026 -0400

    Add extra number tests
    
    Most importantly coverage showed we didn't exercise some path in `digits10`
    function. We needed large enough digits but which are smaller than bigints.
---
 test/jiffy_03_number_tests.erl | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/test/jiffy_03_number_tests.erl b/test/jiffy_03_number_tests.erl
index 82f4eef..b19dffd 100644
--- a/test/jiffy_03_number_tests.erl
+++ b/test/jiffy_03_number_tests.erl
@@ -84,7 +84,14 @@ cases(ok) ->
         {<<"0E3">>, 0.0, <<"0.0">>},
         {<<"1.5E3">>, 1500.0, <<"1500.0">>},
         {<<"2.5E-1">>, 0.25, <<"0.25">>},
-        {<<"-0.325E+2">>, -32.5, <<"-32.5">>}
+        {<<"-0.325E+2">>, -32.5, <<"-32.5">>},
+        %% Large integers to exercise digits10 (11-12+ digit path)
+        {<<"10000000000">>, 10000000000},
+        {<<"100000000000">>, 100000000000},
+        {<<"-10000000000">>, -10000000000},
+        %% Number parsing edge cases
+        {<<"-0.0E1">>, -0.0, <<"0.0">>},
+        {<<"1E-1">>, 1.0e-1, <<"0.1">>}
     ];
 
 cases(error) ->


(couchdb-jiffy) 02/49: Fix -Wextra warnings

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 66a6d9940673bfa94fcfb980e5510f9e6beea316
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Sun Mar 22 01:14:24 2026 -0400

    Fix -Wextra warnings
    
    Compiling with gcc 14 and `-Wextra` flags showed a few instances of invalid
    sign compare (comparing int to unsigned long int for ex). So update some
    variables to match the types.
    
    To fix sign compare warnings, the return type of `utf8_validate()` changed from
    an `int` to `size_t`, and the error result is indicated by `0` and not `-1`, as
    before. Previously it returned -1, or 1,2,3,4 values only. Since 0 wasn't used
    and it's not a valid return, use it as the new error result.
---
 c_src/decoder.c | 10 +++++-----
 c_src/encoder.c |  8 ++++----
 c_src/jiffy.c   | 11 +++++++----
 c_src/jiffy.h   |  2 +-
 c_src/utf8.c    | 26 +++++++++++++-------------
 5 files changed, 30 insertions(+), 27 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 27d101a..6a1a7fd 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -58,8 +58,8 @@ typedef struct {
     ERL_NIF_TERM    null_term;
 
     unsigned char*  p;
-    int             i;
-    int             len;
+    size_t          i;
+    size_t          len;
 
     char*           st_data;
     int             st_size;
@@ -193,8 +193,8 @@ dec_string(Decoder* d, ERL_NIF_TERM* value)
     int has_escape = 0;
     int num_escapes = 0;
     int st;
-    int ulen;
-    int ui;
+    size_t ulen;
+    size_t ui;
     int hi;
     int lo;
     char* chrbuf;
@@ -278,7 +278,7 @@ dec_string(Decoder* d, ERL_NIF_TERM* value)
             d->i++;
         } else {
             ulen = utf8_validate(&(d->p[d->i]), d->len - d->i);
-            if(ulen < 0) {
+            if(ulen == 0) {
                 return 0;
             }
             d->i += ulen;
diff --git a/c_src/encoder.c b/c_src/encoder.c
index 03c5500..4a68c74 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -264,7 +264,7 @@ enc_atom(Encoder* e, ERL_NIF_TERM val)
     unsigned char data[512];
 
     size_t size;
-    int i;
+    size_t i;
 
     if(!enif_get_atom(e->env, val, (char*)data, 512, ERL_NIF_LATIN1)) {
         return 0;
@@ -322,9 +322,9 @@ enc_string(Encoder* e, ERL_NIF_TERM val)
     unsigned char* data;
     size_t size;
     int esc_len;
-    int ulen;
+    size_t ulen;
     int uval;
-    int i;
+    size_t i;
 
     if(!enif_inspect_binary(e->env, val, &bin)) {
         return 0;
@@ -353,7 +353,7 @@ enc_string(Encoder* e, ERL_NIF_TERM val)
         } else if(data[i] >= 0x80) {
             ulen = utf8_validate(&(data[i]), size - i);
 
-            if (ulen < 0) {
+            if (ulen == 0) {
                 return 0;
             } else if (e->uescape) {
                 uval = utf8_to_unicode(&(data[i]), size-i);
diff --git a/c_src/jiffy.c b/c_src/jiffy.c
index dfca7c7..790cf65 100644
--- a/c_src/jiffy.c
+++ b/c_src/jiffy.c
@@ -82,12 +82,15 @@ unload(ErlNifEnv* env, void* priv)
     return;
 }
 
+// {name, arity, fptr, dirty_flag}
+// dirty flag: 0 (default) | ERL_NIF_DIRTY_JOB_{IO|CPU}_BOUND
+//
 static ErlNifFunc funcs[] =
 {
-    {"nif_decode_init", 2, decode_init},
-    {"nif_decode_iter", 5, decode_iter},
-    {"nif_encode_init", 2, encode_init},
-    {"nif_encode_iter", 3, encode_iter}
+    {"nif_decode_init", 2, decode_init, 0},
+    {"nif_decode_iter", 5, decode_iter, 0},
+    {"nif_encode_init", 2, encode_init, 0},
+    {"nif_encode_iter", 3, encode_iter, 0}
 };
 
 ERL_NIF_INIT(jiffy, funcs, &load, &reload, &upgrade, &unload);
diff --git a/c_src/jiffy.h b/c_src/jiffy.h
index db42611..351749f 100644
--- a/c_src/jiffy.h
+++ b/c_src/jiffy.h
@@ -74,7 +74,7 @@ int int_from_hex(const unsigned char* p);
 int int_to_hex(int val, unsigned char* p);
 int utf8_len(int c);
 int utf8_esc_len(int c);
-int utf8_validate(unsigned char* data, size_t size);
+size_t utf8_validate(unsigned char* data, size_t size);
 int utf8_to_unicode(unsigned char* buf, size_t size);
 int unicode_to_utf8(int c, unsigned char* buf);
 int unicode_from_pair(int hi, int lo);
diff --git a/c_src/utf8.c b/c_src/utf8.c
index 1176857..6f8d3bd 100644
--- a/c_src/utf8.c
+++ b/c_src/utf8.c
@@ -111,11 +111,12 @@ utf8_esc_len(int c)
     }
 }
 
-int
+size_t
 utf8_validate(unsigned char* data, size_t size)
 {
-    int ulen = -1;
+    size_t ulen = 0;
     int ui;
+    size_t i;
 
     if((data[0] & 0x80) == 0x00) {
         ulen = 1;
@@ -126,13 +127,13 @@ utf8_validate(unsigned char* data, size_t size)
     } else if((data[0] & 0xF8) == 0xF0) {
         ulen = 4;
     }
-    if(ulen < 0 || ulen > size) {
-        return -1;
+    if(ulen == 0 || ulen > size) {
+        return 0;
     }
 
     // Check each continuation byte.
-    for(ui = 1; ui < ulen; ui++) {
-        if((data[ui] & 0xC0) != 0x80) return -1;
+    for(i = 1; i < ulen; i++) {
+        if((data[i] & 0xC0) != 0x80) return 0;
     }
 
     // Wikipedia says I have to check that a UTF-8 encoding
@@ -149,13 +150,13 @@ utf8_validate(unsigned char* data, size_t size)
     // ulen == 1 passes by definition
     if(ulen == 2) {
         if((data[0] & 0x1E) == 0)
-            return -1;
+            return 0;
     } else if(ulen == 3) {
         if((data[0] & 0x0F) + (data[1] & 0x20) == 0)
-            return -1;
+            return 0;
     } else if(ulen == 4) {
         if((data[0] & 0x07) + (data[1] & 0x30) == 0)
-            return -1;
+            return 0;
     }
 
     // Lastly we need to check some miscellaneous ranges for
@@ -163,11 +164,11 @@ utf8_validate(unsigned char* data, size_t size)
     if(ulen >= 3) {
         ui = utf8_to_unicode(data, ulen);
         if(ui < 0) {
-            return -1;
+            return 0;
         } else if(ui >= 0xD800 && ui <= 0xDFFF) {
-            return -1;
+            return 0;
         } else if(ui > 0x10FFFF) {
-            return -1;
+            return 0;
         }
     }
 
@@ -269,4 +270,3 @@ unicode_uescape(int val, unsigned char* p)
     }
     return -1;
 }
-


(couchdb-jiffy) 05/49: Fix OTP 27 prop tests

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 936e0f8c1a871915ea8c10673b35075a7fdf4317
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Sat Mar 28 15:32:07 2026 -0400

    Fix OTP 27 prop tests
    
    In OTP 27 0.0 =/= -0.0 and because of that exact term matching in the prop
    check started to fail. To avoid that, do not generate -0.0 in the prop tests
    instead test it explicilty elsewhere.
    
    It may seem tempting to switch to == for prop check instead, however that would
    mean we'd also loose type checks that ints stayed ints and floats stayed
    floats in general.
---
 .github/workflows/ci.yml         | 20 ++++----------------
 Makefile                         |  1 +
 test/jiffy_03_number_tests.erl   |  2 ++
 test/jiffy_11_property_tests.erl | 10 +++++++++-
 4 files changed, 16 insertions(+), 17 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index df72868..aa40a20 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,7 +34,7 @@ jobs:
   # they are on OTP 25 so we can use erlang up to version 27 technically.
   #
   eqc-mini-25:
-    name: eqc-mini
+    name: eqc-mini-25
     runs-on: ubuntu-latest
     container:
       image: erlang:25
@@ -44,29 +44,17 @@ jobs:
         run: |
           make REBAR=rebar3 check-with-eqc
 
-  eqc-mini-26:
-    name: eqc-mini
+  eqc-mini-27:
+    name: eqc-mini-27
     runs-on: ubuntu-latest
     container:
-      image: erlang:26
+      image: erlang:27
     steps:
       - uses: actions/checkout@v6
       - name: rebar3 make check-with-eqc
         run: |
           make REBAR=rebar3 check-with-eqc
 
-  # This currently fails. Most likely related to 0.0
-  # eqc-mini-27:
-  #   name: eqc-mini
-  #   runs-on: ubuntu-latest
-  #   container:
-  #     image: erlang:27
-  #   steps:
-  #     - uses: actions/checkout@v6
-  #     - name: rebar3 make check-with-eqc
-  #       run: |
-  #         make REBAR=rebar3 check-with-eqc
-
   gcc-14:
     name: gcc-14
     runs-on: ubuntu-latest
diff --git a/Makefile b/Makefile
index 5b8ef81..5f48efb 100644
--- a/Makefile
+++ b/Makefile
@@ -10,6 +10,7 @@ clean:
 	rm -rf .eunit
 	rm -f test/*.beam
 	rm -rf eqc
+	rm -rf _build
 
 
 distclean: clean
diff --git a/test/jiffy_03_number_tests.erl b/test/jiffy_03_number_tests.erl
index 7ae8404..82f4eef 100644
--- a/test/jiffy_03_number_tests.erl
+++ b/test/jiffy_03_number_tests.erl
@@ -45,6 +45,7 @@ cases(ok) ->
     [
         {<<"0">>, 0},
         {<<"-0">>, 0, <<"0">>},
+        {<<"-0.0">>, -0.0, <<"0.0">>},
         {<<"1">>, 1},
         {<<"12">>, 12},
         {<<"-3">>, -3},
@@ -106,6 +107,7 @@ cases(error) ->
 cases(floats) ->
     [
         0.0,
+        -0.0,
         0.00000001,
         0.000000012,
         0.0000000123,
diff --git a/test/jiffy_11_property_tests.erl b/test/jiffy_11_property_tests.erl
index b3f05a7..15840f5 100644
--- a/test/jiffy_11_property_tests.erl
+++ b/test/jiffy_11_property_tests.erl
@@ -130,6 +130,14 @@ any(S) ->
 any_value() ->
     oneof(any_value_types()).
 
+% As of OTP 27 0.0 =/= -0.0 so we cannot use exact matching on round trips any
+% longer. Therefore we test the 0.0 and -0.0 round-trip explicilty somewhere
+% else but here we exclude it. We don't want to use == for term matching
+% either, because then we'd be losing a check that ints stay as ints and floats
+% as floats.
+%
+real_non_neg_0() ->
+    ?SUCHTHAT(F, real(), F =/= -0.0).
 
 any_value_types() ->
     [
@@ -196,7 +204,7 @@ json_false() ->
 
 
 json_number() ->
-    oneof([largeint(), int(), real()]).
+    oneof([largeint(), int(), real_non_neg_0()]).
 
 
 json_string() ->


(couchdb-jiffy) 46/49: Implement SIMD vectorization

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 0b795ab40ab123a3cd19ba53a09cdc567156f1f7
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Tue Apr 14 13:52:48 2026 -0400

    Implement SIMD vectorization
    
    Structure scan-ahead logic so Clang and GCC will auto-vectorize the code.
    For the auto-vectorizer we want a loop with a known bound and no early exit,
    which is a bit tricky, since the whole idea is to exit when we find a "stop"
    character. To get the best of both worlds we scan 32 bytes at a time, and then
    have an outer loop around that.
    
    The title and the .h file name is also a subtle joke, as we didn't actually
    write a single line of SIMD assembly. However, we've shown vectorization takes
    place (left some command lines in the file as proof) and we still keep the code
    fairly readable.
    
    To get an even better speed boost someone who knows they'll deploy on certain
    CPUs could choose to compile with -march=native or -march=core-avx2 or
    something like that. In that case the auto-vectorizing code may do an even
    better job and use wider AVX2 registers. Most Intel and AMD CPUs since 2013 or
    so would support that.
---
 c_src/decoder.c    | 16 ++++----------
 c_src/encoder.c    | 17 +++------------
 c_src/jiffy_simd.h | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 71 insertions(+), 26 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index ae2d266..7ebaeb9 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -8,6 +8,7 @@
 #include "erl_nif.h"
 #include "ffc.h"
 #include "jiffy.h"
+#include "jiffy_simd.h"
 #include "jiffy_utf8.h"
 
 #define STACK_SIZE_INC 64
@@ -269,18 +270,9 @@ dec_string(Decoder* d, ERL_NIF_TERM* value)
                     return 0;
             }
         } else if(d->p[d->i] < 0x80) {
-            // Scan ahead plain ASCII as an optimization
-            const unsigned char* JIFFY_RESTRICT p = d->p;
-            size_t idx = d->i + 1;
-            const size_t len = d->len;
-            while(idx < len
-                    && p[idx] >= 0x20
-                    && p[idx] < 0x80
-                    && p[idx] != '\"'
-                    && p[idx] != '\\') {
-                idx++;
-            }
-            d->i = idx;
+            // Scan ahead plain ASCII as an optimization. The first
+            // byte has already been checked, so start at i+1.
+            d->i = jiffy_scan_string_body(d->p, d->len, d->i + 1);
         } else {
             ulen = utf8_validate(&(d->p[d->i]), d->len - d->i);
             if(ulen == 0) {
diff --git a/c_src/encoder.c b/c_src/encoder.c
index a2a0108..196ecf8 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -5,6 +5,7 @@
 #include <string.h>
 
 #include "jiffy.h"
+#include "jiffy_simd.h"
 #include "jiffy_utf8.h"
 #include "ryu/ryu.h"
 
@@ -408,13 +409,7 @@ enc_atom(Encoder* e, ERL_NIF_TERM val)
                     i++;
                 }
             } else {
-                while(i < size
-                        && data[i] >= 0x20
-                        && data[i] < 0x80
-                        && data[i] != '\"'
-                        && data[i] != '\\') {
-                    i++;
-                }
+                i = jiffy_scan_string_body(data, size, i);
             }
             size_t run = i - start;
             if(!enc_ensure(e, run)) {
@@ -497,13 +492,7 @@ enc_string(Encoder* e, ERL_NIF_TERM val)
                     i++;
                 }
             } else {
-                while(i < size
-                        && data[i] >= 0x20
-                        && data[i] < 0x80
-                        && data[i] != '\"'
-                        && data[i] != '\\') {
-                    i++;
-                }
+                i = jiffy_scan_string_body(data, size, i);
             }
             size_t run = i - start;
             if(!enc_ensure(e, run)) {
diff --git a/c_src/jiffy_simd.h b/c_src/jiffy_simd.h
new file mode 100644
index 0000000..44ad5d8
--- /dev/null
+++ b/c_src/jiffy_simd.h
@@ -0,0 +1,64 @@
+// This file is part of Jiffy released under the MIT license.
+// See the LICENSE file for more information.
+//
+// Structure the scan-ahead logic so it's both readable and so that Clang and
+// GCC will auto-vectorize the code. For the auto-vectorizer we want to have a
+// loop with a known bound and no early exit, which is a bit tricky, since the
+// whole idea is to exit when we find a "stop" character. To get the best of
+// both worlds, scan 32 bytes at a time and then have an outer loop around that
+//
+// We get auto-vectorization by default with Clang (MacOS 15+) and GCC (14+)
+//
+// Clang
+// ===
+// % cc -S -O3 -Rpass='.*vectoriz.*' -I "$ERL_INC" -I c_src  -c c_src/jiffy.c  -o jiffy.s 2>&1 | grep -i -w 'vectorized' | grep simd
+// c_src/jiffy_simd.h:22:61: remark: Vectorized horizontal reduction with cost -112 and with tree size 3 [-Rpass=slp-vectorizer]
+// c_src/jiffy_simd.h:22:61: remark: Vectorized horizontal reduction with cost -87 and with tree size 3 [-Rpass=slp-vectorizer]
+//
+// GCC
+// ====
+// % gcc-14 -S -O3 -fopt-info-vec-all -I "$ERL_INC" -I c_src  -c c_src/jiffy.c -o jiffy.s 2>&1 | grep -i -w 'optimized'| grep simd
+// c_src/jiffy_simd.h:21:23: optimized: loop vectorized using 16 byte vectors
+// c_src/jiffy_simd.h:21:23: optimized: loop vectorized using 16 byte vectors
+// c_src/jiffy_simd.h:21:23: optimized: loop vectorized using 16 byte vectors
+//
+// If we know we'll deploy on the same architecture we're compiling on we can
+// use -march=native and get even better vectorization with 32 byte chunks at a
+// time
+//
+// GCC /w -march=native
+// ====
+// % gcc-14 -march=native -S -O3 -fopt-info-vec-all -I "$ERL_INC" -I c_src  -c c_src/jiffy.c -o jiffy.s 2>&1 | grep -i -w 'optimized'| grep simd
+// c_src/jiffy_simd.h:21:23: optimized: loop vectorized using 32 byte vectors
+// c_src/jiffy_simd.h:21:23: optimized: loop vectorized using 32 byte vectors
+// c_src/jiffy_simd.h:21:23: optimized: loop vectorized using 32 byte vectors
+
+#ifndef JIFFY_SIMD_H
+#define JIFFY_SIMD_H
+
+#define JIFFY_SIMD_BLOCK_SIZE 32
+
+static inline unsigned int
+jiffy_block_has_stop(const unsigned char* JIFFY_RESTRICT p)
+{
+    unsigned int bad = 0;
+    for (int i = 0; i < JIFFY_SIMD_BLOCK_SIZE; i++) {
+        unsigned char c = p[i];
+        bad |= (unsigned)(c < 0x20) | (unsigned)(c >= 0x80) | (unsigned)(c == '"') | (unsigned)(c == '\\');
+    }
+    return bad;
+}
+
+static inline size_t
+jiffy_scan_string_body(const unsigned char* JIFFY_RESTRICT p, size_t len, size_t i)
+{
+    while (i + JIFFY_SIMD_BLOCK_SIZE <= len && !jiffy_block_has_stop(p + i)) {
+        i += JIFFY_SIMD_BLOCK_SIZE;
+    }
+    while (i < len && p[i] >= 0x20 && p[i] < 0x80 && p[i] != '"' && p[i] != '\\') {
+        i++;
+    }
+    return i;
+}
+
+#endif


(couchdb-jiffy) 43/49: Optimize termstack_restore allocation

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 4c4cf905a1f2885cf42d3dea7f43d6757b508f72
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Mon Apr 13 17:38:10 2026 -0400

    Optimize termstack_restore allocation
    
    When we restore try to get back to the memory size we had before instead of
    always doubling
    
    h/t to @davisp for spotting this one
---
 c_src/encoder.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/c_src/encoder.c b/c_src/encoder.c
index cd41d35..310ebe2 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -84,14 +84,20 @@ termstack_restore(ErlNifEnv* env, ERL_NIF_TERM from, TermStack* stack)
     int arity;
 
     if(enif_get_tuple(env, from, &arity, &elements)) {
+        assert(arity > 0 && "Erlang bug: enif_get_tuple returned a negative arity");
+
         stack->top = arity;
 
         if(arity <= SMALL_TERMSTACK_SIZE) {
             stack->elements = &stack->__default_elements[0];
             stack->size = SMALL_TERMSTACK_SIZE;
         } else {
-            stack->size = arity * 2;
-            stack->elements = enif_alloc(stack->size * sizeof(ERL_NIF_TERM));
+            size_t size = SMALL_TERMSTACK_SIZE;
+            while(size < stack->top) {
+                size *= 2;
+            }
+            stack->size = size;
+            stack->elements = enif_alloc(size * sizeof(ERL_NIF_TERM));
 
             if(!stack->elements) {
                 return 0;


(couchdb-jiffy) 18/49: Improve error handling in dec_number

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit e48ccda83d4e93b4de47f93ff718148f4bc01013
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Wed Apr 1 15:16:31 2026 -0400

    Improve error handling in dec_number
    
    Use davisp's suggestion and make an explicit label + goto to it.
---
 c_src/decoder.c | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 2a9267b..354c335 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -443,8 +443,7 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                         idx++;
                         break;
                     default:
-                        d->i = idx;
-                        return 0;
+                      goto error;
                 }
                 break;
 
@@ -556,8 +555,7 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                         idx++;
                         break;
                     default:
-                        d->i = idx;
-                        return 0;
+                        goto error;
                 }
                 break;
 
@@ -581,8 +579,7 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                 break;
 
             default:
-                d->i = idx;
-                return 0;
+                goto error;
         }
     }
 
@@ -632,6 +629,10 @@ parse:
     *value = enif_make_sub_binary(d->env, d->arg, start, d->i - start);
     *value = enif_make_tuple2(d->env, num_type, *value);
     return 1;
+
+error:
+    d->i = idx;
+    return 0;
 }
 
 static ERL_NIF_TERM


(couchdb-jiffy) 33/49: Produce a unity build

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 38dffa9e043ae61591b6624823deee7b4eff5b4f
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Fri Apr 10 15:11:14 2026 -0400

    Produce a unity build
    
    Include all the .c files jiffy.c and let the compiler do all the possible
    optimizations. This in the same vein as linking with LTO.
    
    This unexpectedly produced a speed-up in large string encoding:
    
    ```
    ===== With input Issue 90 =====
    Name                     ips        average  deviation         median         99th %
    jiffy                  99.37       10.06 ms    +/-21.47%        9.63 ms       18.06 ms
    jiffy (master)         67.72       14.77 ms     +/-2.57%       14.62 ms       16.13 ms
    Comparison:
    jiffy                  99.37
    jiffy (master)         67.72 - 1.47x slower +4.70 ms
    ```
---
 c_src/jiffy.c | 10 +++++++++-
 rebar.config  |  3 +--
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/c_src/jiffy.c b/c_src/jiffy.c
index 9d59aa3..3083455 100644
--- a/c_src/jiffy.c
+++ b/c_src/jiffy.c
@@ -1,7 +1,15 @@
 // This file is part of Jiffy released under the MIT license.
 // See the LICENSE file for more information.
+//
+// Note: we're doing a "unity build" pattern here: all sources are compiled as
+// a single translation unit so the compiler can optimize across file
+// boundaries without lto
 
-#include "jiffy.h"
+#include "decoder.c"
+#include "encoder.c"
+#include "objects.c"
+#include "util.c"
+#include "ryu/d2s.c"
 
 static int
 load(ErlNifEnv* env, void** priv, ERL_NIF_TERM info)
diff --git a/rebar.config b/rebar.config
index f98a95c..760e4c1 100644
--- a/rebar.config
+++ b/rebar.config
@@ -2,8 +2,7 @@
 
 {port_specs, [
     {"priv/jiffy.so", [
-        "c_src/*.c",
-        "c_src/ryu/*.c"
+        "c_src/jiffy.c"
     ]}
 ]}.
 


(couchdb-jiffy) 17/49: Use assert for dec_number unreacheable state

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit b2c4ae23a9e8f936fde471bd99baa0e8704d83f3
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Wed Apr 1 15:08:05 2026 -0400

    Use assert for dec_number unreacheable state
    
    Hat tip to davisp for noticing this is unreacheable and for the nice tip on how
    to make lcov ignore it.
---
 c_src/decoder.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 1f67b41..2a9267b 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -395,7 +395,6 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
     const size_t len = d->len;
     const size_t start = d->i;
     size_t idx = start;
-
     while(idx < len) {
         switch(state) {
             case nst_init:
@@ -421,8 +420,7 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                         idx++;
                         break;
                     default:
-                        d->i = idx;
-                        return 0;
+                        assert(0 && "this state should be unreachable"); // LCOV_EXCL_LINE
                 }
                 break;
 


(couchdb-jiffy) 07/49: Clean up unused code paths. Make OTP 20 the official minimum version

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 5ba849569f27cb607eb0ae1289e4db43d327b072
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Tue Mar 31 00:05:17 2026 -0400

    Clean up unused code paths. Make OTP 20 the official minimum version
    
    These are based on the new coverage reports
    
     * `make_ok()` function is not used anywhere
     * `CONSUME_TIMESLICE_PRESENT` and SCHEDULE_NIF_PRESENT features are always present
     * `ERROR()` and `U()` macros are not used.
    
    The most "interesting" change is finding out `enif_alloc_resource()` cannot
    return `NULL` [1][2], and so `dec_new()` and its callers don't have to handle
    `NULL`s. Which, in turn, eliminated a bunch of `make_error()` calls. With some
    `make_error()`s removed, it doesn't have to be utility function, so can be
    moved to the encoder only.
    
    When moving `make_error()` to the encoder, noticed those functions along with
    others should really be static if they are not used from other modules.
    
    Since we removed checks for CONSUME_TIMESLICE_PRESENT, which is present since
    OTP 17 onwards, and to avoid users getting surprises later during C during
    compilation, make OTP 20 the official minimum OTP version (so far it's been
    unofficial only).
    
    [1] https://www.erlang.org/doc/apps/erts/erl_nif.html#enif_alloc_resource
    
    [2]
    
     - In [beam/erl_nif.c](https://github.com/erlang/otp/blob/639c85d7400d34c82df962547561cc1dc0a42755/erts/emulator/beam/erl_nif.c#L3121)
    it calls `bin = erts_create_magic_binary_x()`
    
     - In
     [beam/erl_binary.h](https://github.com/erlang/otp/blob/639c85d7400d34c82df962547561cc1dc0a42755/erts/emulator/beam/erl_binary.h#L407)
     if the magic binary allocation fails it calls `erts_alloc_n_enomem()`
    
     - In
     [beam/erl_alloc.c](https://github.com/erlang/otp/blob/639c85d7400d34c82df962547561cc1dc0a42755/erts/emulator/beam/erl_alloc.c#L2108)
     `erts_alloc_n_enomem()` calls `erts_alc_fatal_error()` which calls
     `erts_exit()` after a bunch of logging and debug dumps.
---
 c_src/decoder.c | 24 ++++++------------------
 c_src/encoder.c | 41 ++++++++++++++++++++++++-----------------
 c_src/jiffy.h   | 10 ----------
 c_src/util.c    | 26 --------------------------
 rebar.config    |  2 ++
 5 files changed, 32 insertions(+), 71 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 6a1a7fd..f3fcc7f 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -10,9 +10,6 @@
 #include "erl_nif.h"
 #include "jiffy.h"
 
-#define U(c) ((unsigned char) (c))
-#define ERROR(i, msg) make_error(st, env, msg)
-
 #define STACK_SIZE_INC 64
 #define NUM_BUF_LEN 32
 
@@ -66,17 +63,17 @@ typedef struct {
     int             st_top;
 } Decoder;
 
+// Returns an allocated resource or crashes the VM. No need to
+// check return pointer for NULL
 Decoder*
 dec_new(ErlNifEnv* env)
 {
     jiffy_st* st = (jiffy_st*) enif_priv_data(env);
+    // If enif_alloc_resource cannot allocate it crashes the VM
     Decoder* d = enif_alloc_resource(st->res_dec, sizeof(Decoder));
+    assert(d != NULL);
     int i;
 
-    if(d == NULL) {
-        return NULL;
-    }
-
     d->atoms = st;
 
     d->bytes_per_red = DEFAULT_BYTES_PER_REDUCTION;
@@ -654,10 +651,9 @@ decode_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
         return enif_make_badarg(env);
     }
 
+    // If allocation fails VM will crash.
     d = dec_new(env);
-    if(d == NULL) {
-        return make_error(st, env, "internal_error");
-    }
+    assert(d != NULL);
 
     tmp_argv[0] = argv[0];
     tmp_argv[1] = enif_make_resource(env, d);
@@ -746,7 +742,6 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
 
             bump_used_reds(env, bytes_processed, d->bytes_per_red);
 
-#if SCHEDULE_NIF_PRESENT
             return enif_schedule_nif(
                     env,
                     "nif_decode_iter",
@@ -755,13 +750,6 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
                     5,
                     tmp_argv
                 );
-#else
-            return enif_make_tuple2(
-                    env,
-                    st->atom_iter,
-                    enif_make_tuple_from_array(env, tmp_argv, 5)
-                );
-#endif
         }
 
         switch(dec_curr(d)) {
diff --git a/c_src/encoder.c b/c_src/encoder.c
index 4a68c74..29ff860 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -66,7 +66,7 @@ static char* shifts[NUM_SHIFTS] = {
 };
 
 
-Encoder*
+static Encoder*
 enc_new(ErlNifEnv* env)
 {
     jiffy_st* st = (jiffy_st*) enif_priv_data(env);
@@ -99,7 +99,7 @@ enc_new(ErlNifEnv* env)
     return e;
 }
 
-int
+static int
 enc_init(Encoder* e, ErlNifEnv* env)
 {
     e->env = env;
@@ -116,20 +116,34 @@ enc_destroy(ErlNifEnv* env, void* obj)
     }
 }
 
-ERL_NIF_TERM
+static ERL_NIF_TERM
+make_error(jiffy_st* st, ErlNifEnv* env, const char* error)
+{
+    return enif_make_tuple2(env, st->atom_error, make_atom(env, error));
+}
+
+static ERL_NIF_TERM
 enc_error(Encoder* e, const char* msg)
 {
     //assert(0 && msg);
     return make_error(e->atoms, e->env, msg);
 }
 
-ERL_NIF_TERM
+static ERL_NIF_TERM
+make_obj_error(jiffy_st* st, ErlNifEnv* env,
+        const char* error, ERL_NIF_TERM obj)
+{
+    ERL_NIF_TERM reason = enif_make_tuple2(env, make_atom(env, error), obj);
+    return enif_make_tuple2(env, st->atom_error, reason);
+}
+
+static ERL_NIF_TERM
 enc_obj_error(Encoder* e, const char* msg, ERL_NIF_TERM obj)
 {
     return make_obj_error(e->atoms, e->env, msg, obj);
 }
 
-int
+static int
 enc_flush(Encoder* e)
 {
     ERL_NIF_TERM bin;
@@ -411,7 +425,7 @@ enc_object_key(ErlNifEnv *env, Encoder* e, ERL_NIF_TERM val)
 #define P11 100000000000L
 #define P12 1000000000000L
 
-int
+static inline int
 digits10(ErlNifUInt64 v)
 {
     if (v < P01) return 1;
@@ -435,7 +449,7 @@ digits10(ErlNifUInt64 v)
     return 12 + digits10(v / P12);
 }
 
-unsigned int
+static inline unsigned int
 u64ToAsciiTable(unsigned char *dst, ErlNifUInt64 value)
 {
     static const char digits[201] =
@@ -464,7 +478,7 @@ u64ToAsciiTable(unsigned char *dst, ErlNifUInt64 value)
     return length;
 }
 
-unsigned
+static inline unsigned
 i64ToAsciiTable(unsigned char *dst, ErlNifSInt64 value)
 {
     if (value < 0) {
@@ -595,7 +609,7 @@ enc_comma(Encoder* e)
     return 1;
 }
 
-int
+static int
 enc_map_to_ejson(ErlNifEnv* env, ERL_NIF_TERM map, ERL_NIF_TERM* out)
 {
     ErlNifMapIterator iter;
@@ -743,7 +757,6 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
             termstack_destroy(&stack);
             bump_used_reds(env, bytes_processed, e->bytes_per_red);
 
-#if SCHEDULE_NIF_PRESENT
             return enif_schedule_nif(
                     env,
                     "nif_encode_iter",
@@ -752,13 +765,7 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
                     3,
                     tmp_argv
                 );
-#else
-            return enif_make_tuple2(
-                    env,
-                    st->atom_iter,
-                    enif_make_tuple_from_array(env, tmp_argv, 3)
-                );
-#endif
+
         }
 
         curr = termstack_pop(&stack);
diff --git a/c_src/jiffy.h b/c_src/jiffy.h
index 351749f..af755da 100644
--- a/c_src/jiffy.h
+++ b/c_src/jiffy.h
@@ -9,12 +9,6 @@
 #define DEFAULT_BYTES_PER_REDUCTION 20
 #define DEFAULT_ERLANG_REDUCTION_COUNT 2000
 
-#define CONSUME_TIMESLICE_PRESENT \
-        ((ERL_NIF_MAJOR_VERSION >= 2 && ERL_NIF_MINOR_VERSION >= 4))
-
-#define SCHEDULE_NIF_PRESENT \
-        ((ERL_NIF_MAJOR_VERSION >= 2 && ERL_NIF_MINOR_VERSION >= 7))
-
 typedef struct {
     ERL_NIF_TERM    atom_ok;
     ERL_NIF_TERM    atom_error;
@@ -49,10 +43,6 @@ typedef struct {
 } jiffy_st;
 
 ERL_NIF_TERM make_atom(ErlNifEnv* env, const char* name);
-ERL_NIF_TERM make_ok(jiffy_st* st, ErlNifEnv* env, ERL_NIF_TERM data);
-ERL_NIF_TERM make_error(jiffy_st* st, ErlNifEnv* env, const char* error);
-ERL_NIF_TERM make_obj_error(jiffy_st* st, ErlNifEnv* env, const char* error,
-        ERL_NIF_TERM obj);
 int get_bytes_per_iter(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpi);
 int get_bytes_per_red(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpr);
 int get_null_term(ErlNifEnv* env, ERL_NIF_TERM val, ERL_NIF_TERM *null_term);
diff --git a/c_src/util.c b/c_src/util.c
index f6dcb68..5df4fde 100644
--- a/c_src/util.c
+++ b/c_src/util.c
@@ -14,26 +14,6 @@ make_atom(ErlNifEnv* env, const char* name)
     return enif_make_atom(env, name);
 }
 
-ERL_NIF_TERM
-make_ok(jiffy_st* st, ErlNifEnv* env, ERL_NIF_TERM value)
-{
-    return enif_make_tuple2(env, st->atom_ok, value);
-}
-
-ERL_NIF_TERM
-make_error(jiffy_st* st, ErlNifEnv* env, const char* error)
-{
-    return enif_make_tuple2(env, st->atom_error, make_atom(env, error));
-}
-
-ERL_NIF_TERM
-make_obj_error(jiffy_st* st, ErlNifEnv* env,
-        const char* error, ERL_NIF_TERM obj)
-{
-    ERL_NIF_TERM reason = enif_make_tuple2(env, make_atom(env, error), obj);
-    return enif_make_tuple2(env, st->atom_error, reason);
-}
-
 int
 get_bytes_per_iter(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpi)
 {
@@ -130,7 +110,6 @@ should_yield(size_t used, size_t bytes_per_red)
 void
 bump_used_reds(ErlNifEnv* env, size_t used, size_t bytes_per_red)
 {
-#if CONSUME_TIMESLICE_PRESENT
     size_t reds_used;
     size_t pct_used;
 
@@ -144,9 +123,4 @@ bump_used_reds(ErlNifEnv* env, size_t used, size_t bytes_per_red)
 
         enif_consume_timeslice(env, pct_used);
     }
-#endif
-
-    (void) env;
-    (void) used;
-    (void) bytes_per_red;
 }
diff --git a/rebar.config b/rebar.config
index 6ab52c2..2cd6b3b 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,3 +1,5 @@
+{require_min_otp_vsn, "20"}.
+
 {port_specs, [
     {"priv/jiffy.so", [
         "c_src/*.c",


(couchdb-jiffy) 01/49: Add a missing else in utf8 validation

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 3282a1a7d6498ffdd506b21336e4d7a038e3c8d9
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Wed Mar 18 19:16:03 2026 -0400

    Add a missing else in utf8 validation
    
    The code worked just fine previously, just did an extra comparison.
---
 c_src/utf8.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/c_src/utf8.c b/c_src/utf8.c
index c8c96b1..1176857 100644
--- a/c_src/utf8.c
+++ b/c_src/utf8.c
@@ -119,7 +119,7 @@ utf8_validate(unsigned char* data, size_t size)
 
     if((data[0] & 0x80) == 0x00) {
         ulen = 1;
-    } if((data[0] & 0xE0) == 0xC0) {
+    } else if((data[0] & 0xE0) == 0xC0) {
         ulen = 2;
     } else if((data[0] & 0xF0) == 0xE0) {
         ulen = 3;


(couchdb-jiffy) 24/49: Ignore .cache

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 7d37dd191c96cb36954d481a0c43ab126986d455
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Thu Apr 2 18:00:04 2026 -0400

    Ignore .cache
    
    This is createdy by lsp-mode's clangd daemon
---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index 8ddf166..10d69c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,3 +24,4 @@ coverage-html/
 current_counterexample.eqc
 *.gcda
 *.gcno
+.cache


(couchdb-jiffy) 22/49: Whitespace scan ahead optimization in decoder

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 32062a26820bf56ad7c60e29d6979c248df0f28d
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Thu Apr 2 16:56:27 2026 -0400

    Whitespace scan ahead optimization in decoder
    
    Same idea as with plain ASCII and number scanning optimization: if we see one
    whitespace character, check if more follow it. This would be most helpful when
    decoding pretty formatted json objects.
---
 c_src/decoder.c | 27 ++++++++++++++++++++++-----
 1 file changed, 22 insertions(+), 5 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 46239db..6acccf7 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -667,6 +667,21 @@ make_array(ErlNifEnv* env, ERL_NIF_TERM list)
     return ret;
 }
 
+// Scan ahead and look for whitespace. This is used when we saw at least one
+// whitespace character and then expect to find more.
+static inline size_t
+skip_whitespace(const unsigned char* JIFFY_RESTRICT p, size_t len, size_t i)
+{
+    while(i < len && (
+            p[i] == ' ' ||
+            p[i] == '\n' ||
+            p[i] == '\r' ||
+            p[i] == '\t')) {
+        i++;
+    }
+    return i;
+}
+
 ERL_NIF_TERM
 decode_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
 {
@@ -757,6 +772,8 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
     start = d->i;
 
     const size_t yt = yield_threshold(d->bytes_per_red);
+    const unsigned char* JIFFY_RESTRICT p = d->p;
+    const size_t len = d->len;
 
     while(d->i < bin.size) {
         bytes_processed = d->i - start;
@@ -790,7 +807,7 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
                     case '\n':
                     case '\r':
                     case '\t':
-                        d->i++;
+                        d->i = skip_whitespace(p, len, d->i + 1);
                         break;
                     case 'n':
                         if(d->i + 3 >= d->len) {
@@ -905,7 +922,7 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
                     case '\n':
                     case '\r':
                     case '\t':
-                        d->i++;
+                        d->i = skip_whitespace(p, len, d->i + 1);
                         break;
                     case '\"':
                         if(!dec_string(d, &val)) {
@@ -949,7 +966,7 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
                     case '\n':
                     case '\r':
                     case '\t':
-                        d->i++;
+                        d->i = skip_whitespace(p, len, d->i + 1);
                         break;
                     case ':':
                         dec_pop_assert(d, st_colon);
@@ -968,7 +985,7 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
                     case '\n':
                     case '\r':
                     case '\t':
-                        d->i++;
+                        d->i = skip_whitespace(p, len, d->i + 1);
                         break;
                     case ',':
                         dec_pop_assert(d, st_comma);
@@ -1041,7 +1058,7 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
                     case '\n':
                     case '\r':
                     case '\t':
-                        d->i++;
+                        d->i = skip_whitespace(p, len, d->i + 1);
                         break;
                     default:
                         goto decode_done;


(couchdb-jiffy) 41/49: Skip setting buffer to 0 for strtod and strtol

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit c3dce8afc2708e288caf2ed22a8387030fc136fe
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Mon Apr 13 12:21:48 2026 -0400

    Skip setting buffer to 0 for strtod and strtol
    
    Just terminate with 0 after copy
---
 c_src/decoder.c                | 10 ++++---
 test/jiffy_03_number_tests.erl | 63 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 69 insertions(+), 4 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index d934100..779f947 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -635,9 +635,11 @@ parse:
 
     errno = 0;
 
-    if(d->i - start < NUM_BUF_LEN) {
-        memset(nbuf, 0, NUM_BUF_LEN);
-        memcpy(nbuf, &p[start], d->i - start);
+    size_t num_len = d->i - start;
+
+    if(num_len < NUM_BUF_LEN) {
+        memcpy(nbuf, &p[start], num_len);
+        nbuf[num_len] = '\0';
 
         if(has_frac || has_exp) {
             dval = strtod(nbuf, NULL);
@@ -663,7 +665,7 @@ parse:
     }
 
     d->is_partial = 1;
-    *value = enif_make_sub_binary(d->env, d->arg, start, d->i - start);
+    *value = enif_make_sub_binary(d->env, d->arg, start, num_len);
     *value = enif_make_tuple2(d->env, num_type, *value);
     return 1;
 
diff --git a/test/jiffy_03_number_tests.erl b/test/jiffy_03_number_tests.erl
index 2a8fdbb..3c8fb8b 100644
--- a/test/jiffy_03_number_tests.erl
+++ b/test/jiffy_03_number_tests.erl
@@ -73,6 +73,69 @@ cases(ok) ->
             1.0E-36,
             <<"1.0e-36">>
         },
+        % There is an internal num_buffer used for loading values in before
+        % passing them to strtod and strtol so we're testing around that 32
+        % size limit
+        % 30 ones
+        {
+            <<"111111111111111111111111111111">>,
+            111111111111111111111111111111,
+            <<"111111111111111111111111111111">>
+        },
+        % 31 ones
+        {
+            <<"1111111111111111111111111111111">>,
+            1111111111111111111111111111111,
+            <<"1111111111111111111111111111111">>
+        },
+        % 32 ones
+        {
+            <<"11111111111111111111111111111111">>,
+            11111111111111111111111111111111,
+            <<"11111111111111111111111111111111">>
+        },
+        % 33 ones
+        {
+            <<"111111111111111111111111111111111">>,
+            111111111111111111111111111111111,
+            <<"111111111111111111111111111111111">>
+        },
+        % 34 ones
+        {
+            <<"1111111111111111111111111111111111">>,
+            1111111111111111111111111111111111,
+            <<"1111111111111111111111111111111111">>
+        },
+        % 30 frac digits
+        {
+            <<"1.00000000000000000000000000E10">>,
+            1.0e10,
+            <<"1.0e10">>
+        },
+        % 31 frac digits
+        {
+            <<"1.00000000000000000000000000E10">>,
+            1.0e10,
+            <<"1.0e10">>
+        },
+        % 32 frac digits
+        {
+            <<"1.000000000000000000000000000E10">>,
+            1.0e10,
+            <<"1.0e10">>
+        },
+        % 33 frac digits
+        {
+            <<"1.0000000000000000000000000000E10">>,
+            1.0e10,
+            <<"1.0e10">>
+        },
+        % 34 frac digits
+        {
+            <<"1.00000000000000000000000000000E10">>,
+            1.0e10,
+            <<"1.0e10">>
+        },
         {<<"0.75">>, 0.75},
         {<<"2.0123456789">>, 2.0123456789, <<"2.0123456789">>},
         {<<"2.4234324E24">>, 2.4234324E24, <<"2.4234324e24">>},


(couchdb-jiffy) 26/49: Replace chocolatey with erlef/setup-beam

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 6362a567dc967062c6dab99a57eed6d0a32859df
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Thu Apr 2 22:24:01 2026 -0400

    Replace chocolatey with erlef/setup-beam
    
    Unfortunatly chocolatey has been pretty flaky lately. Let's see if we have
    better luck with erlef
---
 .github/workflows/ci.yml | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4cbec22..46a1090 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -134,9 +134,10 @@ jobs:
           git config --global core.autocrlf false
           git config --global core.eol lf
     - uses: actions/checkout@v6
-    - name: install rebar3
-      run: |
-        choco install rebar3
+    - uses: erlef/setup-beam@v1
+      with:
+        otp-version: '27'
+        rebar3-version: '3.23'
     - name: make check
       run: |
         $path = vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -nologo -nocolor -property installationPath


(couchdb-jiffy) 11/49: Increase coverage for bad options

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 0693a9a827604dd971835e8221090175d89bb412
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Tue Mar 31 01:02:59 2026 -0400

    Increase coverage for bad options
    
    Here we're mostly being silly and padding out stats
---
 test/jiffy_12_error_tests.erl | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/test/jiffy_12_error_tests.erl b/test/jiffy_12_error_tests.erl
index 54de9a7..fa636f2 100644
--- a/test/jiffy_12_error_tests.erl
+++ b/test/jiffy_12_error_tests.erl
@@ -82,6 +82,13 @@ enc_invalid_object_member_key_test_() ->
     ]}.
 
 
+encode_bad_option_test() ->
+    ?assertError(badarg, jiffy:encode(1, [not_a_valid_option])).
+
+
+decode_bad_option_test() ->
+    ?assertError(badarg, jiffy:decode(<<"1">>, [not_a_valid_option])).
+
 
 enc_error(Type, Obj, Case) ->
     ?_assertMatch({'EXIT', {{Type, Obj}, _}}, (catch jiffy:encode(Case))).


(couchdb-jiffy) 08/49: Make most of decode functions static

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 024386313a07d3c5d41546f6ccec9aafc3db763d
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Tue Mar 31 00:49:45 2026 -0400

    Make most of decode functions static
    
    Since we did this to the encoder, it seems right to do likewise to the decoder.
    Add some "inline" suggestions to the compiler for some smaller ones. Saw
    encoder used some of those so it fits here as well.
---
 c_src/decoder.c | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index f3fcc7f..1f39394 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -65,7 +65,7 @@ typedef struct {
 
 // Returns an allocated resource or crashes the VM. No need to
 // check return pointer for NULL
-Decoder*
+static Decoder*
 dec_new(ErlNifEnv* env)
 {
     jiffy_st* st = (jiffy_st*) enif_priv_data(env);
@@ -102,7 +102,7 @@ dec_new(ErlNifEnv* env)
     return d;
 }
 
-void
+static void
 dec_init(Decoder* d, ErlNifEnv* env, ERL_NIF_TERM arg, ErlNifBinary* bin)
 {
     d->env = env;
@@ -122,7 +122,7 @@ dec_destroy(ErlNifEnv* env, void* obj)
     }
 }
 
-ERL_NIF_TERM
+static ERL_NIF_TERM
 dec_error(Decoder* d, const char* atom)
 {
     ERL_NIF_TERM pos = enif_make_int(d->env, d->i+1);
@@ -131,20 +131,20 @@ dec_error(Decoder* d, const char* atom)
     return enif_make_tuple2(d->env, d->atoms->atom_error, ret);
 }
 
-char
+static inline char
 dec_curr(Decoder* d)
 {
     assert(d->st_top > 0);
     return d->st_data[d->st_top - 1];
 }
 
-int
+static inline int
 dec_top(Decoder* d)
 {
     return d->st_top;
 }
 
-void
+static inline void
 dec_push(Decoder* d, char val)
 {
     int new_sz;
@@ -163,7 +163,7 @@ dec_push(Decoder* d, char val)
     d->st_data[d->st_top++] = val;
 }
 
-char
+static inline char
 dec_pop(Decoder* d) {
     char current = st_invalid;
 
@@ -176,7 +176,7 @@ dec_pop(Decoder* d) {
     return current;
 }
 
-void
+static void
 dec_pop_assert(Decoder* d, char val)
 {
     char current = dec_pop(d);
@@ -184,7 +184,7 @@ dec_pop_assert(Decoder* d, char val)
     (void)current;
 }
 
-int
+static int
 dec_string(Decoder* d, ERL_NIF_TERM* value)
 {
     int has_escape = 0;
@@ -368,7 +368,7 @@ parse:
     return 1;
 }
 
-int
+static int
 dec_number(Decoder* d, ERL_NIF_TERM* value)
 {
     ERL_NIF_TERM num_type = d->atoms->atom_error;
@@ -615,7 +615,7 @@ parse:
     return 1;
 }
 
-ERL_NIF_TERM
+static ERL_NIF_TERM
 make_empty_object(ErlNifEnv* env, int ret_map)
 {
     if(ret_map) {
@@ -625,7 +625,7 @@ make_empty_object(ErlNifEnv* env, int ret_map)
     return enif_make_tuple1(env, enif_make_list(env, 0));
 }
 
-ERL_NIF_TERM
+static inline ERL_NIF_TERM
 make_array(ErlNifEnv* env, ERL_NIF_TERM list)
 {
     ERL_NIF_TERM ret = enif_make_list(env, 0);


(couchdb-jiffy) 04/49: Run prop tests in CI

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 7fdf2762e49257272188c2d6c02929db5f92fc00
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Sat Mar 28 13:08:57 2026 -0400

    Run prop tests in CI
    
    We got prop tests but we almost never run them as installing EQC mini is a bit
    of an adventure. Once I figured it out how to install it let's make it a script
    and run it in CI before I forget how to do it again.
    
    I didn't want to spam the download endpoint with fetching the beams on every
    matrix build so made a single run on Erlang 27 for it. Besides probably good
    idea to stick with 27 only since the beams seem to be built in OTP 25, and we
    only get guarantees about compatibility up to 2 version ahead only.
---
 .github/workflows/ci.yml | 39 ++++++++++++++++++++++++++++++++++++++-
 .gitignore               |  3 +++
 Makefile                 |  5 +++++
 rebar.config.script      | 14 ++++++++++++--
 test/install_eqc_mini.sh | 40 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 98 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7501cea..df72868 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -30,6 +30,43 @@ jobs:
       run: |
         make REBAR=${{matrix.rebar}} check
 
+  # Keep this no more than 2 releases away from EQC-mini beam files. Currently
+  # they are on OTP 25 so we can use erlang up to version 27 technically.
+  #
+  eqc-mini-25:
+    name: eqc-mini
+    runs-on: ubuntu-latest
+    container:
+      image: erlang:25
+    steps:
+      - uses: actions/checkout@v6
+      - name: rebar3 make check-with-eqc
+        run: |
+          make REBAR=rebar3 check-with-eqc
+
+  eqc-mini-26:
+    name: eqc-mini
+    runs-on: ubuntu-latest
+    container:
+      image: erlang:26
+    steps:
+      - uses: actions/checkout@v6
+      - name: rebar3 make check-with-eqc
+        run: |
+          make REBAR=rebar3 check-with-eqc
+
+  # This currently fails. Most likely related to 0.0
+  # eqc-mini-27:
+  #   name: eqc-mini
+  #   runs-on: ubuntu-latest
+  #   container:
+  #     image: erlang:27
+  #   steps:
+  #     - uses: actions/checkout@v6
+  #     - name: rebar3 make check-with-eqc
+  #       run: |
+  #         make REBAR=rebar3 check-with-eqc
+
   gcc-14:
     name: gcc-14
     runs-on: ubuntu-latest
@@ -52,7 +89,7 @@ jobs:
       image: erlang:28
     steps:
       - uses: actions/checkout@v6
-      - name: install clang 20
+      - name: install clang
         run: |
           apt update
           apt install -y --no-install-recommends --no-install-suggests clang-19
diff --git a/.gitignore b/.gitignore
index bd33acc..c98ce03 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,6 @@ erln8.config
 hexer.config
 rebar.lock
 TEST-*.xml
+/eqc/
+.eqc-info
+current_counterexample.eqc
diff --git a/Makefile b/Makefile
index 1ad82f4..5b8ef81 100644
--- a/Makefile
+++ b/Makefile
@@ -9,6 +9,7 @@ clean:
 	rm -rf logs
 	rm -rf .eunit
 	rm -f test/*.beam
+	rm -rf eqc
 
 
 distclean: clean
@@ -25,6 +26,10 @@ eunit:
 
 check: build eunit
 
+check-with-eqc: install_eqc build eunit
+
+install_eqc:
+	./test/install_eqc_mini.sh
 
 release:
 	rebar3 hex publish
diff --git a/rebar.config.script b/rebar.config.script
index c2f1689..604f4de 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -1,10 +1,20 @@
 % This file is part of Jiffy released under the MIT license.
 % See the LICENSE file for more information.
 %
-% Only run the EQC checks when EQC is present.
 
+% Only run the EQC checks when EQC is present.
+%
+% We have a helper tests/install_eqc_mini.sh script to install it ./eqc, so we
+% look for it there first, and add that to the code path. Users may have also
+% installed it directly into their OTP distribution, which should be fine,
+% code:which(eqc) should find that as well.
+%
+LocalEQC = filename:join([filename:dirname(SCRIPT), "eqc", "ebin"]),
+case filelib:is_dir(LocalEQC) of
+    true -> code:add_patha(LocalEQC);
+    false -> ok
+end,
 HaveEQC = code:which(eqc) =/= non_existing,
-
 ErlOpts = if not HaveEQC -> []; true ->
     [{d, 'HAVE_EQC'}]
 end,
diff --git a/test/install_eqc_mini.sh b/test/install_eqc_mini.sh
new file mode 100755
index 0000000..5614c75
--- /dev/null
+++ b/test/install_eqc_mini.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# Install EQC mini in $project_dir/eqc, where rebar config will find it.
+#
+# Note: mind what version it's compiled for and make sure to test it on
+# Erlang version no more than two major version numbers away from it.
+# So, for example, 25 should be good for 25, 26 and 27.
+#
+
+set -eu
+
+EQC_VERSION="2.02.0"
+EQC_URL="https://www.quviq.com/downloads/eqcR25-${EQC_VERSION}.zip"
+EQC_ZIP_DIR="Quviq QuickCheck Mini version ${EQC_VERSION} for OTP 25"
+
+PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
+INSTALL_DIR="${PROJECT_DIR}/eqc"
+
+if [ -d "${INSTALL_DIR}/ebin" ]; then
+    echo "Already installed at ${INSTALL_DIR}"
+    exit 0
+fi
+
+TMPDIR="${TMPDIR:-/tmp}"
+ZIP_FILE="${TMPDIR}/eqc-mini-${EQC_VERSION}.zip"
+EXTRACT_DIR="${TMPDIR}/eqc-mini-extract-$$"
+
+echo "Downloading ${EQC_URL}"
+curl -sfL "${EQC_URL}" -o "${ZIP_FILE}"
+
+rm -rf "${EXTRACT_DIR}"
+mkdir -p "${EXTRACT_DIR}"
+unzip -q -o "${ZIP_FILE}" -d "${EXTRACT_DIR}"
+
+rm -rf "${INSTALL_DIR}"
+mv "${EXTRACT_DIR}/${EQC_ZIP_DIR}/eqc-${EQC_VERSION}" "${INSTALL_DIR}"
+
+rm -rf "${EXTRACT_DIR}" "${ZIP_FILE}"
+
+echo "Installed in ${INSTALL_DIR}"


(couchdb-jiffy) 47/49: Improve coverage even more

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit c93691c63c72d75720a19c263a250c34d724d0c5
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Thu Apr 16 15:53:43 2026 -0400

    Improve coverage even more
    
    We slipped in some cases, for example when we bumped the reduction count. When
    adding more tests for yielding found out the `byte_per_iter` could become <
    1 (made the test stuck) so add a fix to clip it to 1.
    
    Then there a few more odds and ends for string escaping and options.
---
 c_src/util.c                        | 10 +++++-
 test/jiffy_04_string_tests.erl      | 23 +++++++++++--
 test/jiffy_12_error_tests.erl       | 26 ++++++++++----
 test/jiffy_16_dedupe_keys_tests.erl | 23 +++++++++++++
 test/jiffy_19_yielding_tests.erl    | 68 ++++++++++++++++++++++++++++++++++---
 test/jiffy_util.hrl                 | 13 ++++---
 6 files changed, 146 insertions(+), 17 deletions(-)

diff --git a/c_src/util.c b/c_src/util.c
index cc23a15..a84eabd 100644
--- a/c_src/util.c
+++ b/c_src/util.c
@@ -38,8 +38,12 @@ get_bytes_per_iter(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpi)
         return 0;
     }
 
-    // Calculate the number of bytes per reduction
+    // Calculate the number of bytes per reduction. Clamp to 1 so we
+    // avoid a divide-by-zero in bump_used_reds.
     *bpi = (size_t) (bytes / DEFAULT_ERLANG_REDUCTION_COUNT);
+    if(*bpi == 0) {
+        *bpi = 1;
+    }
 
     return 1;
 }
@@ -68,7 +72,11 @@ get_bytes_per_red(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpi)
         return 0;
     }
 
+    // Same get_bytes_per_iter, clamp to 1 to avoid a divide by 0
     *bpi = (size_t) bytes;
+    if(*bpi == 0) {
+        *bpi = 1;
+    }
 
     return 1;
 }
diff --git a/test/jiffy_04_string_tests.erl b/test/jiffy_04_string_tests.erl
index a8c9446..c96e014 100644
--- a/test/jiffy_04_string_tests.erl
+++ b/test/jiffy_04_string_tests.erl
@@ -146,7 +146,13 @@ cases(error) ->
         % Truncated \uXX (not enough hex digits)
         <<"\"\\u00\"">>,
         % Invalid hex digit in \u escape
-        <<"\"\\uZZZZ\"">>
+        <<"\"\\uZZZZ\"">>,
+        % Same story as \uD834\n but with more trailers to pass the length
+        % guard and reach the '\u' check for the low surrogate. We're down in
+        % the weeds, as it were.
+        <<"\"\\uD834\\nabcdef\"">>,
+        % \uD834\u<bad hex> low surrogate hex error
+        <<"\"\\uD834\\uZZZZ\"">>
     ];
 
 cases(utf8) ->
@@ -205,5 +211,18 @@ cases(bad_utf8_key) ->
 
 cases(escaped_slashes) ->
     [
-        {<<"\"\\/\"">>, <<"/">>}
+        {<<"\"\\/\"">>, <<"/">>},
+        {<<"\"foo\\/bar\\/baz\"">>, <<"foo/bar/baz">>}
+    ].
+
+
+atom_escaped_slashes_test_() ->
+    [
+        ?_assertEqual(<<"\"a\\/b\"">>,
+            enc('a/b', [escape_forward_slashes])),
+        ?_assertEqual(<<"\"a/b\"">>, enc('a/b')),
+        ?_assertEqual(<<"{\"a\\/b\":1}">>,
+            enc({[{'a/b', 1}]}, [escape_forward_slashes])),
+        ?_assertEqual(<<"\"foo\\/bar\\/baz\\/potato\"">>,
+            enc('foo/bar/baz/potato', [escape_forward_slashes]))
     ].
diff --git a/test/jiffy_12_error_tests.erl b/test/jiffy_12_error_tests.erl
index fa636f2..7a1ed9f 100644
--- a/test/jiffy_12_error_tests.erl
+++ b/test/jiffy_12_error_tests.erl
@@ -82,12 +82,26 @@ enc_invalid_object_member_key_test_() ->
     ]}.
 
 
-encode_bad_option_test() ->
-    ?assertError(badarg, jiffy:encode(1, [not_a_valid_option])).
-
-
-decode_bad_option_test() ->
-    ?assertError(badarg, jiffy:decode(<<"1">>, [not_a_valid_option])).
+decode_bad_option_test_() ->
+    [?_assertError(badarg, jiffy:decode(<<"1">>, [O])) || O <- [
+        not_a_valid_option,
+        <<"foo">>,
+        {foo, bar, baz},
+        {bytes_per_iter, not_an_int},
+        {bytes_per_red, not_an_int},
+        {some_other_opt, value},
+        {null_term, 123}
+    ]].
+
+
+encode_bad_option_test_() ->
+    [?_assertError(badarg, jiffy:encode(1, [O])) || O <- [
+        not_a_valid_option,
+        <<"foo">>,
+        {foo, bar, baz},
+        {bytes_per_iter, not_an_int},
+        {bytes_per_red, not_an_int}
+    ]].
 
 
 enc_error(Type, Obj, Case) ->
diff --git a/test/jiffy_16_dedupe_keys_tests.erl b/test/jiffy_16_dedupe_keys_tests.erl
index 7713e51..d78d3a6 100644
--- a/test/jiffy_16_dedupe_keys_tests.erl
+++ b/test/jiffy_16_dedupe_keys_tests.erl
@@ -4,6 +4,7 @@
 -module(jiffy_16_dedupe_keys_tests).
 
 -include_lib("eunit/include/eunit.hrl").
+-include("jiffy_util.hrl").
 
 % Duplicate keys with `return_maps`. We settled on
 % last value wins semantics in such cases so test that
@@ -98,3 +99,25 @@ dedupe_keys_test_() ->
 
 dedupe_keys_empty_test() ->
     ?assertEqual({[]}, jiffy:decode(<<"{}">>, [dedupe_keys])).
+
+% Exercise the heap-allocated dedupe hash table path when count > 64 and hit
+% more than HT_STACK_SLOTS. We're padding those coverage stats here, really.
+dedupe_keys_large_test_() ->
+    N = 100,
+    KV = fun(I) -> [<<"\"">>, i2b(I), <<"\":">>, i2b(I)] end,
+    Body = iol2b(lists:join(",", [KV(I) || I <- lists:seq(1, N)])),
+    Dupes = iol2b(lists:join(",", [KV(I) || I <- lists:seq(1, N)])),
+    JUnique = <<"{", Body/binary, "}">>,
+    JDupes = <<"{", Body/binary, ",", Dupes/binary, "}">>,
+    [
+        {"Unique large object",
+            fun() ->
+                {Pairs} = jiffy:decode(JUnique, [dedupe_keys]),
+                ?assertEqual(N, length(Pairs))
+            end},
+        {"All keys duplicated once",
+            fun() ->
+                {Pairs} = jiffy:decode(JDupes, [dedupe_keys]),
+                ?assertEqual(N, length(Pairs))
+            end}
+    ].
diff --git a/test/jiffy_19_yielding_tests.erl b/test/jiffy_19_yielding_tests.erl
index d12ee88..c64bc72 100644
--- a/test/jiffy_19_yielding_tests.erl
+++ b/test/jiffy_19_yielding_tests.erl
@@ -31,15 +31,75 @@ encode_both_bytes_opts_test() ->
 % otheriwse.
 %
 decode_large_with_bytes_per_red_test() ->
-    Data = iolist_to_binary([
+    Data = iol2b([
         <<"[">>,
         lists:join(<<",">>, [<<"1">> || _ <- lists:seq(1, 500)]),
         <<"]">>
     ]),
-    Result = jiffy:decode(Data, [{bytes_per_red, 20}]),
+    Result = dec(Data, [{bytes_per_red, 20}]),
     ?assertEqual(500, length(Result)).
 
 encode_large_with_bytes_per_red_test() ->
     Data = lists:duplicate(500, 1),
-    Encoded = iolist_to_binary(jiffy:encode(Data, [{bytes_per_red, 20}])),
-    ?assertEqual(Data, jiffy:decode(Encoded)).
+    Encoded = enc(Data, [{bytes_per_red, 20}]),
+    ?assertEqual(Data, dec(Encoded)).
+
+% Nested object but a large binary at the bottom. We want the yield to fire
+% while the term stack holds a large (> SMALL_TERMSTACK_SIZE) entries. Then
+% small byte_per_red to force yielding.
+encode_deep_nesting_yield_test() ->
+    Depth = 12,
+    Big = binary:copy(<<"a">>, 10000),
+    Seq = lists:seq(1, Depth),
+    Nested = lists:foldl(fun(_, Acc) -> {[{<<"k">>, Acc}]} end, Big, Seq),
+    Encoded = enc(Nested, [{bytes_per_red, 1}]),
+    ?assertEqual(Nested, dec(Encoded)).
+
+% Force yielding and test restore/save and schedule bits.
+decode_excessive_yield_test_() ->
+    Data = iol2b([
+        <<"[">>,
+        lists:join(<<",">>, [i2b(I) || I <- lists:seq(1, 2000)]),
+        <<"]">>
+    ]),
+    Expected = lists:seq(1, 2000),
+    [
+        {"bytes_per_red = 1",
+            ?_assertEqual(Expected, dec(Data, [{bytes_per_red, 1}]))},
+        {"bytes_per_red = 0 (clamped to 1)",
+            ?_assertEqual(Expected, dec(Data, [{bytes_per_red, 0}]))},
+        {"bytes_per_iter = 1",
+            ?_assertEqual(Expected, dec(Data, [{bytes_per_iter, 1}]))}
+    ].
+
+encode_excessive_yield_test_() ->
+    Data = lists:seq(1, 2000),
+    [
+        {"bytes_per_red = 1",
+            fun() ->
+                Enc = enc(Data, [{bytes_per_red, 1}]),
+                ?assertEqual(Data, dec(Enc))
+            end},
+        {"bytes_per_red = 0 (clamped to 1)",
+            fun() ->
+                Enc = enc(Data, [{bytes_per_red, 0}]),
+                ?assertEqual(Data, dec(Enc))
+            end},
+        {"bytes_per_iter = 1",
+            fun() ->
+                Enc = enc(Data, [{bytes_per_iter, 1}]),
+                ?assertEqual(Data, dec(Enc))
+            end}
+    ].
+
+% Single large string encoded/decoded + low yield threshold, so
+% we hopefully hit the pct_used > 100 in bump_used_reds
+large_string_yield_test_() ->
+    Big = binary:copy(<<"abc">>, 50000),
+    Json = <<"\"", Big/binary, "\"">>,
+    [
+        {"Decode",
+            ?_assertEqual(Big, dec(Json, [{bytes_per_red, 1}]))},
+        {"Encode",
+            ?_assertEqual(Json, enc(Big, [{bytes_per_red, 1}]))}
+    ].
diff --git a/test/jiffy_util.hrl b/test/jiffy_util.hrl
index 983f7e3..9fd7d6b 100644
--- a/test/jiffy_util.hrl
+++ b/test/jiffy_util.hrl
@@ -4,12 +4,18 @@
 -compile(export_all).
 -compile(nowarn_export_all).
 
+iol2b(X) ->
+    iolist_to_binary(X).
+
+i2b(X) ->
+    integer_to_binary(X).
+
 msg(Fmt, Args) ->
     M1 = io_lib:format(Fmt, Args),
     M2 = re:replace(M1, <<"\r">>, <<"\\\\r">>, [global]),
     M3 = re:replace(M2, <<"\n">>, <<"\\\\n">>, [global]),
     M4 = re:replace(M3, <<"\t">>, <<"\\\\t">>, [global]),
-    iolist_to_binary(M4).
+    iol2b(M4).
 
 
 hex(Bin) when is_binary(Bin) ->
@@ -27,12 +33,11 @@ dec(V, Opts) ->
 
 
 enc(V) ->
-    iolist_to_binary(jiffy:encode(V)).
+    iol2b(jiffy:encode(V)).
 
 
 enc(V, Opts) ->
-    iolist_to_binary(jiffy:encode(V, Opts)).
-
+    iol2b(jiffy:encode(V, Opts)).
 
 %% rebar runs eunit with PWD as .eunit/
 %% rebar3 runs eunit with PWD as ./


(couchdb-jiffy) 29/49: Faster array creation

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit e1f4d4d9fa67c052495a9e887e8e3aeac24dce90
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Mon Apr 6 15:29:17 2026 -0400

    Faster array creation
    
    Same idea as in the faster object creation PR [1]: use
    `enif_make_list_from_array` API function to create the object in one API call.
    Just like for objects, if they are just a few (=<64) items, use a stack to hold
    the terms, otherwise use a the heap.
    
    `enif_make_list_from_array` is available since R13B04 so we're not losing any
    compatibility or needing to bump minimum OTP versions here.
    
    [1] https://github.com/davisp/jiffy/pull/262
    [2] https://www.erlang.org/doc/apps/erts/erl_nif.html#enif_make_list_from_array
---
 c_src/decoder.c | 24 ++++++++++++++++++++++--
 c_src/objects.c | 47 ++++++++++++++++++++++++++++++++++-------------
 2 files changed, 56 insertions(+), 15 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 6acccf7..fd2e406 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -12,6 +12,7 @@
 
 #define STACK_SIZE_INC 64
 #define NUM_BUF_LEN 32
+#define JIFFY_SMALL_ARRAY_SIZE 64
 
 enum {
     st_value=0,
@@ -657,11 +658,30 @@ make_empty_object(ErlNifEnv* env, int ret_map)
 static inline ERL_NIF_TERM
 make_array(ErlNifEnv* env, ERL_NIF_TERM list)
 {
-    ERL_NIF_TERM ret = enif_make_list(env, 0);
     ERL_NIF_TERM item;
 
+    unsigned int count = 0;
+    enif_get_list_length(env, list, &count);
+
+    if(count == 0) {
+        return enif_make_list(env, 0);
+    }
+
+    ERL_NIF_TERM small_buf[JIFFY_SMALL_ARRAY_SIZE];
+    ERL_NIF_TERM* arr = (count <= JIFFY_SMALL_ARRAY_SIZE)
+        ? small_buf
+        : enif_alloc(count * sizeof(ERL_NIF_TERM));
+
+    // Fill array in reverse since list was reversed after parsing
+    unsigned int i = count;
     while(enif_get_list_cell(env, list, &item, &list)) {
-        ret = enif_make_list_cell(env, item, ret);
+        arr[--i] = item;
+    }
+
+    ERL_NIF_TERM ret = enif_make_list_from_array(env, arr, count);
+
+    if(arr != small_buf) {
+        enif_free(arr);
     }
 
     return ret;
diff --git a/c_src/objects.c b/c_src/objects.c
index 184606d..cafb4de 100644
--- a/c_src/objects.c
+++ b/c_src/objects.c
@@ -12,7 +12,7 @@
 // detecting when we can skip deduplicating based on the small map
 // size limit.
 #define JIFFY_MAP_SMALL_MAP_LIMIT 32
-
+#define JIFFY_SMALL_PROPLIST_SIZE 32
 // Limit up to when we'll allocate the hash table on the stack.
 // Must be a power of 2, since we're using a bitmask. Also, we want
 // to stay well below 4KB on the stack. That can be checked wit
@@ -170,21 +170,32 @@ static int
 make_proplist(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out,
     int dedupe_keys)
 {
-    ERL_NIF_TERM ret;
     ERL_NIF_TERM key;
     ERL_NIF_TERM val;
 
+    unsigned int list_len = 0;
+    int rv = enif_get_list_length(env, pairs, &list_len);
+    assert(rv && "pairs must be a list");
+    assert((list_len % 2) == 0 && "Unbalanced object pairs.");
+    unsigned int count = list_len / 2;
+
+    if(count == 0) {
+        *out = enif_make_tuple1(env, enif_make_list(env, 0));
+        return 1;
+    }
+
+    ERL_NIF_TERM small_arr[JIFFY_SMALL_PROPLIST_SIZE];
+    ERL_NIF_TERM* arr = (count <= JIFFY_SMALL_PROPLIST_SIZE)
+        ? small_arr
+        : (ERL_NIF_TERM*) enif_alloc(count * sizeof(ERL_NIF_TERM));
+
     ht_slot stack_table[HT_STACK_SLOTS];
     ht_slot* table = NULL;
     unsigned int mask = 0;
     ErlNifUInt64 salt = 0;
 
     if(dedupe_keys) {
-        unsigned int list_len = 0;
-        int rv = enif_get_list_length(env, pairs, &list_len);
-        assert(rv && "pairs must be a list");
-        unsigned int count = list_len / 2;
-        unsigned int ht_size = ht_size_power_of_2(count > 0 ? count : 1);
+        unsigned int ht_size = ht_size_power_of_2(count);
         table = (ht_size <= HT_STACK_SLOTS)
             ? stack_table
             : (ht_slot*) enif_alloc(ht_size * sizeof(ht_slot));
@@ -193,19 +204,23 @@ make_proplist(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out,
         salt = (ErlNifUInt64) enif_monotonic_time(ERL_NIF_NSEC);
     }
 
-    ret = enif_make_list(env, 0);
+    // Fill array backwards since list is reversed from parsing
+    unsigned int unique = 0;
+    unsigned int i = count;
     while(enif_get_list_cell(env, pairs, &val, &pairs)) {
         if(!enif_get_list_cell(env, pairs, &key, &pairs)) {
             assert(0 && "Unbalanced object pairs.");
         }
         if(dedupe_keys) {
             if(ht_insert(env, table, mask, key, salt)) {
-                val = enif_make_tuple2(env, key, val);
-                ret = enif_make_list_cell(env, val, ret);
+                --i;
+                arr[i] = enif_make_tuple2(env, key, val);
+                unique++;
             }
         } else {
-            val = enif_make_tuple2(env, key, val);
-            ret = enif_make_list_cell(env, val, ret);
+            --i;
+            arr[i] = enif_make_tuple2(env, key, val);
+            unique++;
         }
     }
 
@@ -213,7 +228,13 @@ make_proplist(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out,
         enif_free(table);
     }
 
-    *out = enif_make_tuple1(env, ret);
+    ERL_NIF_TERM list = enif_make_list_from_array(env, arr + count - unique, unique);
+
+    if(arr != small_arr) {
+        enif_free(arr);
+    }
+
+    *out = enif_make_tuple1(env, list);
     return 1;
 }
 


(couchdb-jiffy) 06/49: Add C coverage checks

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 0435946bdf84fdeb095f8ffc1257379a1d817327
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Mon Mar 30 22:15:02 2026 -0400

    Add C coverage checks
    
    Use gcov for coverage checks and lcov for report generation while running the
    full test suite, including all the property tests.
    
    This seems to work on Ubuntu and on MacOS. In CI we'll go with Ubuntu and run
    it as one job for now. We don't fail on low coverage, instead we'll track it
    manually by comparing with previous runs. This mostly helpful to run locally,
    as lcov html report is pretty and intuitive to browse through.
    
    Noticed Windows CI kept failing. Trying the next highest Windows version, maybe
    that will succeed.
---
 .github/workflows/ci.yml | 15 ++++++++++++++-
 .gitignore               |  5 +++++
 Makefile                 | 18 +++++++++++++++++-
 3 files changed, 36 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index aa40a20..4cbec22 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -55,6 +55,19 @@ jobs:
         run: |
           make REBAR=rebar3 check-with-eqc
 
+  lcov-coverage:
+    name: lcov-coverage
+    runs-on: ubuntu-latest
+    container:
+      image: erlang:28
+    steps:
+      - uses: actions/checkout@v6
+      - name: install lcov
+        run: |
+          apt update
+          apt install -y --no-install-recommends --no-install-suggests lcov
+          make REBAR=rebar3 coverage
+
   gcc-14:
     name: gcc-14
     runs-on: ubuntu-latest
@@ -113,7 +126,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        os: [windows-2022]
+        os: [windows-2022, windows-2025]
 
     steps:
     - name: prevent git from messing up our json test files
diff --git a/.gitignore b/.gitignore
index c98ce03..590e2b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,5 +16,10 @@ hexer.config
 rebar.lock
 TEST-*.xml
 /eqc/
+coverage.info
+coverage-jiffy.info
+coverage-html/
 .eqc-info
 current_counterexample.eqc
+*.gcda
+*.gcno
diff --git a/Makefile b/Makefile
index 5f48efb..0a88ae6 100644
--- a/Makefile
+++ b/Makefile
@@ -11,6 +11,9 @@ clean:
 	rm -f test/*.beam
 	rm -rf eqc
 	rm -rf _build
+	rm -f c_src/*.gcno c_src/*.gcda c_src/double-conversion/*.gcno c_src/double-conversion/*.gcda
+	rm -f coverage.info coverage-jiffy.info
+	rm -rf coverage-html
 
 
 distclean: clean
@@ -32,6 +35,19 @@ check-with-eqc: install_eqc build eunit
 install_eqc:
 	./test/install_eqc_mini.sh
 
+# macos:
+#   brew install lcov on macos
+# ubuntu:
+#   sudo apt install --no-install-recommends lcov
+#
+coverage:
+	$(MAKE) clean
+	CFLAGS="--coverage -O0" CXXFLAGS="--coverage -O0" LDFLAGS="--coverage" $(MAKE) check-with-eqc
+	@lcov --capture --directory c_src -o coverage.info --ignore-errors inconsistent,unsupported
+	@lcov --extract coverage.info '*/c_src/*' --exclude '*/double-conversion/*' -o coverage-jiffy.info --ignore-errors inconsistent,unsupported
+	@genhtml coverage-jiffy.info -o coverage-html --title "jiffy lcov report"
+	@echo "For coverage report: open coverage-html/index.html"
+
 release:
 	rebar3 hex publish
 
@@ -40,4 +56,4 @@ release:
 	erlc -o test/ $<
 
 
-.PHONY: all clean distclean depends build etap eunit check
+.PHONY: all clean distclean depends build etap eunit check coverage


(couchdb-jiffy) 48/49: Reduce bytes per reduction a bit

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit b8a2299fe4f688c2b7b022dc694f063450ab0edb
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Thu Apr 16 18:07:43 2026 -0400

    Reduce bytes per reduction a bit
    
    A scheduling benchmark test showed we had too high p99 and max values compared
    with the built-in OTP 27 json
    
    Before
    =====
    
    ```
    [jiffy]
      idle (no workers)            n=2489 p50=997us p95=1.1ms p99=1.2ms max=11.5ms
      12x decode                   n=1679 p50=1.5ms p95=4.4ms p99=10.7ms max=76.7ms
      12x encode                   n=1148 p50=2.8ms p95=7.8ms p99=12.6ms max=32.2ms
      24x decode                   n=1187 p50=2.4ms p95=8.3ms p99=13.9ms max=72.2ms
      24x encode                   n=754 p50=5.0ms p95=10.7ms p99=16.8ms max=30.0ms
      48x decode                   n=750 p50=4.0ms p95=13.8ms p99=27.6ms max=58.7ms
      48x encode                   n=137 p50=35.7ms p95=68.7ms p99=92.4ms max=102.0ms
    
    [json]
      idle (no workers)            n=2500 p50=997us p95=1.1ms p99=1.2ms max=1.7ms
      12x decode                   n=2008 p50=1.0ms p95=3.7ms p99=10.5ms max=50.1ms
      12x encode                   n=1977 p50=1.0ms p95=4.9ms p99=10.3ms max=26.0ms
      24x decode                   n=2173 p50=1.0ms p95=2.8ms p99=5.5ms max=23.8ms
      24x encode                   n=1850 p50=1.1ms p95=5.1ms p99=8.7ms max=67.4ms
      48x decode                   n=1596 p50=1.9ms p95=4.7ms p99=9.3ms max=21.0ms
      48x encode                   n=1698 p50=1.2ms p95=6.2ms p99=11.5ms max=32.9ms
    ```
    
    After
    ====
    
    ```
    [jiffy]
      idle (no workers)            n=2500 p50=998us p95=1.1ms p99=1.2ms max=1.3ms
      12x decode                   n=2302 p50=1.1ms p95=2.0ms p99=3.2ms max=17.0ms
      12x encode                   n=1703 p50=1.8ms p95=3.4ms p99=5.1ms max=11.5ms
      24x decode                   n=1872 p50=1.4ms p95=3.5ms p99=5.2ms max=9.5ms
      24x encode                   n=1183 p50=2.9ms p95=6.1ms p99=10.4ms max=16.7ms
    
    [json]
      idle (no workers)            n=2500 p50=998us p95=1.1ms p99=1.2ms max=2.2ms
      12x decode                   n=2354 p50=1.0ms p95=1.8ms p99=4.0ms max=12.1ms
      12x encode                   n=2239 p50=993us p95=3.1ms p99=5.7ms max=13.5ms
      24x decode                   n=2216 p50=1.0ms p95=2.4ms p99=6.0ms max=15.0ms
      24x encode                   n=2155 p50=1.0ms p95=3.3ms p99=6.8ms max=14.2ms
    ```
---
 c_src/jiffy.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/c_src/jiffy.h b/c_src/jiffy.h
index 082b62c..68dcb90 100644
--- a/c_src/jiffy.h
+++ b/c_src/jiffy.h
@@ -6,7 +6,7 @@
 
 #include "erl_nif.h"
 
-#define DEFAULT_BYTES_PER_REDUCTION 20
+#define DEFAULT_BYTES_PER_REDUCTION 10
 
 // This used to be 2000 and in 19.2 was bumped to 4000
 // #define CONTEXT_REDS in erts/emulator/beam/erl_vm.h


(couchdb-jiffy) 42/49: Fix potential segfault in termstack expansion

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit ac3c936f41987a59b8b6ad30d7cc8598ff1aafb7
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Mon Apr 13 16:43:58 2026 -0400

    Fix potential segfault in termstack expansion
    
    When we go from a stack array to a heap in `termstack_push()`, we should copy
    `stack->size * sizeof(ERL_NIF_TERM)` number of bytes not `num_bytes`.
    `num_bytes` is the new value which is twice as large.
    
    This means we've been doing dirty reads and copying extra junk data that we
    didn't need before. If we had used up most of the stack and were right against
    the limit, and there was some stack protection involed we could have hit a
    segfault here.
---
 c_src/encoder.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/c_src/encoder.c b/c_src/encoder.c
index 8ad4067..cd41d35 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -123,7 +123,7 @@ termstack_push(TermStack* stack, ERL_NIF_TERM term)
 
         if (stack->elements == &stack->__default_elements[0]) {
             ERL_NIF_TERM* elems = enif_alloc(num_bytes);
-            memcpy(elems, stack->elements, num_bytes);
+            memcpy(elems, stack->elements, SMALL_TERMSTACK_SIZE * sizeof(ERL_NIF_TERM));
             stack->elements = elems;
         } else {
             stack->elements = enif_realloc(stack->elements, num_bytes);


(couchdb-jiffy) 34/49: Prevent forward slashes from breaking scan-ahead optimization

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 2649ba2f073c1e202e9787bc70b33bdae8ec6305
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Fri Apr 10 17:11:05 2026 -0400

    Prevent forward slashes from breaking scan-ahead optimization
    
    Forward slash escapes are a rare (I couldn't find anyone using it at least)
    option we support, but when we do string scan-aheads we always stop on it,
    because it might have to be handled specially. To avoid that, we first check if
    we need them escaped and run two versions of scan-ahead: one with forward
    slashes and one without.
---
 c_src/encoder.c | 53 +++++++++++++++++++++++++++++++++++++----------------
 1 file changed, 37 insertions(+), 16 deletions(-)

diff --git a/c_src/encoder.c b/c_src/encoder.c
index c8243fa..01c5533 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -340,9 +340,10 @@ enc_special_character(Encoder* e, int val) {
             e->p[e->i++] = 't';
             return 1;
         case '/':
-            if(e->escape_forward_slashes) {
-                e->p[e->i++] = '\\';
+            if(!e->escape_forward_slashes) {
+                return 0;
             }
+            e->p[e->i++] = '\\';
             e->p[e->i++] = '/';
             return 1;
         default:
@@ -392,13 +393,23 @@ enc_atom(Encoder* e, ERL_NIF_TERM val)
             // stop on them as well
             start = i;
             i++;
-            while(i < size
-                    && data[i] >= 0x20
-                    && data[i] < 0x80
-                    && data[i] != '\"'
-                    && data[i] != '\\'
-                    && data[i] != '/') {
-                i++;
+            if(e->escape_forward_slashes) {
+                while(i < size
+                        && data[i] >= 0x20
+                        && data[i] < 0x80
+                        && data[i] != '\"'
+                        && data[i] != '\\'
+                        && data[i] != '/') {
+                    i++;
+                }
+            } else {
+                while(i < size
+                        && data[i] >= 0x20
+                        && data[i] < 0x80
+                        && data[i] != '\"'
+                        && data[i] != '\\') {
+                    i++;
+                }
             }
             size_t run = i - start;
             if(!enc_ensure(e, run)) {
@@ -471,13 +482,23 @@ enc_string(Encoder* e, ERL_NIF_TERM val)
             // choose to escape them too.
             start = i;
             i++;
-            while(i < size
-                    && data[i] >= 0x20
-                    && data[i] < 0x80
-                    && data[i] != '\"'
-                    && data[i] != '\\'
-                    && data[i] != '/') {
-                i++;
+            if(e->escape_forward_slashes) {
+                while(i < size
+                        && data[i] >= 0x20
+                        && data[i] < 0x80
+                        && data[i] != '\"'
+                        && data[i] != '\\'
+                        && data[i] != '/') {
+                    i++;
+                }
+            } else {
+                while(i < size
+                        && data[i] >= 0x20
+                        && data[i] < 0x80
+                        && data[i] != '\"'
+                        && data[i] != '\\') {
+                    i++;
+                }
             }
             size_t run = i - start;
             if(!enc_ensure(e, run)) {


(couchdb-jiffy) 19/49: Use quick lookahead parsing for numbers

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 67571e1bbb04131d3f35441b683373476d94a6e7
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Wed Apr 1 16:58:29 2026 -0400

    Use quick lookahead parsing for numbers
    
    Same idea as we did for strings in [1]. For numbers we do it for any states
    which are looping (mantissa, frac and edigit). The wins are not as great since
    most numbers are not that long but it's plausible someone has some large
    dataset of mostly number arrays perhaps and it could help them.
    
    Benchmarking with jason's benchee setup with a new data file consisting from an
    array of largish numbers showed a 7% improvement compared to current master.
    
    [1]
    https://github.com/davisp/jiffy/commit/dbd7bcc2804ccf37ee936ff36fbe9fdeff8a3664
---
 c_src/decoder.c | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 354c335..5021630 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -390,7 +390,10 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
     // to the compiler p won't alias any other pointers so it can optimize
     // access to it. Also avoid writing back do d->i on every increment,
     // instead increment a local variable (hopefully in a register) then update
-    // d->i once at the end.
+    // d->i once at the end. Also, when parsing looping states (mantissa, frac,
+    // edigit) scan-ahead quickly looking for strings of digits only. The wins
+    // will not be as big as we have for strings as most numbers are not that
+    // long, but it shouldn't hurt either.
     const unsigned char* JIFFY_RESTRICT p = d->p;
     const size_t len = d->len;
     const size_t start = d->i;
@@ -468,7 +471,9 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                     case '7':
                     case '8':
                     case '9':
-                        idx++;
+                        while(idx < len && p[idx] >= '0' && p[idx] <= '9') {
+                            idx++;
+                        }
                         break;
                     default:
                         goto parse;
@@ -529,7 +534,9 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                     case '7':
                     case '8':
                     case '9':
-                        idx++;
+                        while(idx < len && p[idx] >= '0' && p[idx] <= '9') {
+                            idx++;
+                        }
                         break;
                     default:
                         goto parse;
@@ -571,7 +578,9 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
                     case '7':
                     case '8':
                     case '9':
-                        idx++;
+                        while(idx < len && p[idx] >= '0' && p[idx] <= '9') {
+                            idx++;
+                        }
                         break;
                     default:
                         goto parse;


(couchdb-jiffy) 45/49: Use branch predictors for encoding

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit c77f342ab89182c06a4bfe064fa3d365d7d26bd5
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Wed Apr 15 23:12:57 2026 -0400

    Use branch predictors for encoding
    
    Branch predictors are compiler hints to structure the code such that some code
    is more/less likely to be speculatively executed. This is a technique I noticed
    in QuickJS code and figured it's worth trying here. This turns out to help in
    encoding more than decoding. I guess encoding is more "branchy".
    
    The improvements are not huge, just a few percentage points, but they are
    consistent for the majority of encoding benchmarks and with both GCC (Debian)
    and Clang (MacOS) compilers:
    
    Sample of results from Debian 13/GCC
    
    ```
    ===== With input Encode Pokedex =====
    Name                               ips        average  deviation         median         99th %
    jiffy (branch-predictor)        1.87 K      535.83 us    +/-10.10%      541.35 us      674.64 us
    jiffy (master)                  1.70 K      589.32 us    +/-11.87%      579.90 us      783.70 us
    Comparison:
    jiffy (branch-predictor)        1.87 K
    jiffy (master)                  1.70 K - 1.10x slower +53.49 us
    ===== With input Encode Semanticscholar Corpus =====
    Name                               ips        average  deviation         median         99th %
    jiffy (master)                   20.13       49.68 ms     +/-4.94%       49.70 ms       58.81 ms
    jiffy (branch-predictor)         19.37       51.64 ms    +/-11.48%       50.40 ms       62.91 ms
    Comparison:
    jiffy (master)                   20.13
    jiffy (branch-predictor)         19.37 - 1.04x slower +1.95 ms
    ===== With input Encode Twitter =====
    Name                               ips        average  deviation         median         99th %
    jiffy (branch-predictor)        272.86        3.66 ms    +/-14.99%        3.55 ms        5.15 ms
    jiffy (master)                  252.14        3.97 ms    +/-14.50%        3.83 ms        5.18 ms
    Comparison:
    jiffy (branch-predictor)        272.86
    jiffy (master)                  252.14 - 1.08x slower +0.30 ms
    ===== With input Encode UTF-8 escaped =====
    Name                               ips        average  deviation         median         99th %
    jiffy (branch-predictor)        8.88 K      112.59 us     +/-0.76%      112.45 us      115.26 us
    jiffy (master)                  8.44 K      118.48 us     +/-1.41%      118.07 us      124.21 us
    Comparison:
    jiffy (branch-predictor)        8.88 K
    jiffy (master)                  8.44 K - 1.05x slower +5.89 us
    ===== With input Encode UTF-8 unescaped =====
    Name                               ips        average  deviation         median         99th %
    jiffy (branch-predictor)        8.87 K      112.73 us     +/-0.72%      112.58 us      115.29 us
    jiffy (master)                  8.48 K      117.87 us     +/-0.67%      117.84 us      120.02 us
    Comparison:
    jiffy (branch-predictor)        8.87 K
    jiffy (master)                  8.48 K - 1.05x slower +5.14 us
    ```
    
    Sample of results from MacOS (Clang)
    
    ```
    ===== With input Encode Pokedex =====
    Name                               ips        average  deviation         median         99th %
    jiffy (branch-predictor)        1.81 K      551.89 us    +/-11.53%      545.92 us      734.93 us
    jiffy (master)                  1.70 K      587.50 us    +/-11.49%      580.93 us      779.05 us
    Comparison:
    jiffy (branch-predictor)        1.81 K
    jiffy (master)                  1.70 K - 1.06x slower +35.61 us
    ===== With input Encode Semanticscholar Corpus =====
    Name                               ips        average  deviation         median         99th %
    jiffy (master)                   19.34       51.70 ms     +/-9.42%       50.46 ms       64.43 ms
    jiffy (branch-predictor)         19.33       51.73 ms    +/-12.60%       50.31 ms       71.43 ms
    Comparison:
    jiffy (master)                   19.34
    jiffy (branch-predictor)         19.33 - 1.00x slower +0.0301 ms
    ===== With input Encode Twitter =====
    Name                               ips        average  deviation         median         99th %
    jiffy (branch-predictor)        274.06        3.65 ms    +/-14.39%        3.52 ms        4.81 ms
    jiffy (master)                  256.28        3.90 ms    +/-14.25%        3.78 ms        5.11 ms
    Comparison:
    jiffy (branch-predictor)        274.06
    jiffy (master)                  256.28 - 1.07x slower +0.25 ms
    ===== With input Encode UTF-8 escaped =====
    Name                               ips        average  deviation         median         99th %
    jiffy (branch-predictor)        8.89 K      112.47 us     +/-0.77%      112.32 us      115.13 us
    jiffy (master)                  8.47 K      118.05 us     +/-0.79%      118.02 us      120.57 us
    Comparison:
    jiffy (branch-predictor)        8.89 K
    jiffy (master)                  8.47 K - 1.05x slower +5.58 us
    ===== With input Encode UTF-8 unescaped =====
    Name                               ips        average  deviation         median         99th %
    jiffy (branch-predictor)        8.86 K      112.90 us     +/-0.90%      112.67 us      116.13 us
    jiffy (master)                  8.48 K      117.95 us     +/-0.72%      117.92 us      120.21 us
    Comparison:
    jiffy (branch-predictor)        8.86 K
    jiffy (master)                  8.48 K - 1.04x slower +5.05 us
    ```
---
 c_src/encoder.c | 16 ++++++++--------
 c_src/jiffy.h   |  9 +++++++++
 2 files changed, 17 insertions(+), 8 deletions(-)

diff --git a/c_src/encoder.c b/c_src/encoder.c
index 310ebe2..a2a0108 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -261,12 +261,12 @@ enc_ensure(Encoder* e, size_t req)
 {
     size_t new_size = BIN_INC_SIZE;
 
-    if(e->have_buffer) {
-        if(req < (e->buffer.size - e->i)) {
+    if(JIFFY_LIKELY(e->have_buffer)) {
+        if(JIFFY_LIKELY(req < (e->buffer.size - e->i))) {
             return 1;
         }
 
-        if(!enc_flush(e)) {
+        if(JIFFY_UNLIKELY(!enc_flush(e))) {
             return 0;
         }
 
@@ -390,9 +390,9 @@ enc_atom(Encoder* e, ERL_NIF_TERM val)
             return 0;
         }
 
-        if(enc_special_character(e, data[i])) {
+        if(JIFFY_UNLIKELY(enc_special_character(e, data[i]))) {
             i++;
-        } else if(data[i] < 0x80) {
+        } else if(JIFFY_LIKELY(data[i] < 0x80)) {
             // Scan ahead for plain ASCII chars which don't need escaping.
             // Since optionally users could escape forward slashes, too, we
             // stop on them as well
@@ -511,12 +511,12 @@ enc_string(Encoder* e, ERL_NIF_TERM val)
             }
             memcpy(&(e->p[e->i]), &data[start], run);
             e->i += run;
-        } else if(data[i] >= 0x80) {
+        } else if(JIFFY_UNLIKELY(data[i] >= 0x80)) {
             ulen = utf8_validate(&(data[i]), size - i);
 
-            if (ulen == 0) {
+            if (JIFFY_UNLIKELY(ulen == 0)) {
                 return 0;
-            } else if (e->uescape) {
+            } else if (JIFFY_UNLIKELY(e->uescape)) {
                 uval = utf8_to_unicode(&(data[i]), size-i);
                 if(uval < 0) {
                     return 0;
diff --git a/c_src/jiffy.h b/c_src/jiffy.h
index 252d2fc..082b62c 100644
--- a/c_src/jiffy.h
+++ b/c_src/jiffy.h
@@ -23,6 +23,15 @@
   #define inline __inline
 #endif
 
+// These are to help the branch predictor
+#if defined(__GNUC__) || defined(__clang__)
+  #define JIFFY_LIKELY(x)   __builtin_expect(!!(x), 1)
+  #define JIFFY_UNLIKELY(x) __builtin_expect(!!(x), 0)
+#else
+  #define JIFFY_LIKELY(x)   (x)
+  #define JIFFY_UNLIKELY(x) (x)
+#endif
+
 typedef struct {
     ERL_NIF_TERM    atom_ok;
     ERL_NIF_TERM    atom_error;


(couchdb-jiffy) 12/49: Add yielding tests

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit e1fa8a41314ecc6a901d5f8573dd3832d464c0ad
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Tue Mar 31 01:03:51 2026 -0400

    Add yielding tests
    
    Yielding properly is one of the best "hidden" features of Jiffy, which makes it
    run very well in a highly concurrent environment. It turns out we didn't have a
    test which tried to explicilty consume more time slices so try to add one.
    
    The rest is a rather boring set of bytes_per_red/bytes_per_iter option
    combinations.
---
 test/jiffy_19_yielding_tests.erl | 45 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/test/jiffy_19_yielding_tests.erl b/test/jiffy_19_yielding_tests.erl
new file mode 100644
index 0000000..d12ee88
--- /dev/null
+++ b/test/jiffy_19_yielding_tests.erl
@@ -0,0 +1,45 @@
+% This file is part of Jiffy released under the MIT license.
+% See the LICENSE file for more information.
+
+-module(jiffy_19_yielding_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+-include("jiffy_util.hrl").
+
+decode_bytes_per_red_test() ->
+    ?assertEqual([1,2,3], dec(<<"[1,2,3]">>, [{bytes_per_red, 50}])).
+
+encode_bytes_per_red_test() ->
+    ?assertEqual(<<"[1,2,3]">>, enc([1,2,3], [{bytes_per_red, 50}])).
+
+decode_bytes_per_iter_test() ->
+    ?assertEqual([1,2,3], dec(<<"[1,2,3]">>, [{bytes_per_iter, 20000}])).
+
+encode_bytes_per_iter_test() ->
+    ?assertEqual(<<"[1,2,3]">>, enc([1,2,3], [{bytes_per_iter, 20000}])).
+
+decode_both_bytes_opts_test() ->
+    Opts = [{bytes_per_red, 100}, {bytes_per_iter, 200000}],
+    ?assertEqual(1, dec(<<"1">>, Opts)).
+
+encode_both_bytes_opts_test() ->
+    Opts = [{bytes_per_red, 100}, {bytes_per_iter, 200000}],
+    ?assertEqual(<<"1">>, enc(1, Opts)).
+
+% Give it something large to chew on so it hopefully does some yielding.
+% Noticed with coverage enabled we didn't check some yielding paths in CI tests
+% otheriwse.
+%
+decode_large_with_bytes_per_red_test() ->
+    Data = iolist_to_binary([
+        <<"[">>,
+        lists:join(<<",">>, [<<"1">> || _ <- lists:seq(1, 500)]),
+        <<"]">>
+    ]),
+    Result = jiffy:decode(Data, [{bytes_per_red, 20}]),
+    ?assertEqual(500, length(Result)).
+
+encode_large_with_bytes_per_red_test() ->
+    Data = lists:duplicate(500, 1),
+    Encoded = iolist_to_binary(jiffy:encode(Data, [{bytes_per_red, 20}])),
+    ?assertEqual(Data, jiffy:decode(Encoded)).


(couchdb-jiffy) 25/49: Optimize dedupe_keys option

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 09a75e966d041b0b57cc3f48d41969ad28e62101
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Thu Apr 2 19:35:33 2026 -0400

    Optimize dedupe_keys option
    
    Switch sets in objects.cc (used for deduping keys) from an `set` to
    `unordered_set`. Unordered sets have O(1) lookups and regular sets have
    O(logN).
    
    This option mostly useful for projects like Apache CouchDB which rely on the
    proplist represention and use the `dedupe_keys`.
    
    Since we're trying to be backwards comptable as much as possible let's add a
    macro check and use a `set` if `unordered` set is not available.
---
 c_src/objects.cc | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/c_src/objects.cc b/c_src/objects.cc
index 2a35b25..121e062 100644
--- a/c_src/objects.cc
+++ b/c_src/objects.cc
@@ -1,9 +1,16 @@
 // This file is part of Jiffy released under the MIT license.
 // See the LICENSE file for more information.
 
-#include <set>
 #include <string>
 
+#if defined(__cplusplus) && (__cplusplus >= 201103L)
+  #include <unordered_set>
+  typedef std::unordered_set<std::string> string_set;
+#else
+  #include <set>
+  typedef std::set<std::string> string_set;
+#endif
+
 #include <assert.h>
 
 #include "erl_nif.h"
@@ -21,7 +28,7 @@ make_object(ErlNifEnv* env, ERL_NIF_TERM pairs, ERL_NIF_TERM* out,
     ERL_NIF_TERM key;
     ERL_NIF_TERM val;
 
-    std::set<std::string> seen;
+    string_set seen;
 
     ERL_NIF_TERM old_val;
 


(couchdb-jiffy) 13/49: Add coverage for random unicode edge cases

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 7606c3f35b2e5cbf179a35bb0e34be1b5c3e8061
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Tue Mar 31 01:09:48 2026 -0400

    Add coverage for random unicode edge cases
    
    We had pretty good coverage except some odds and ends like uencoded sequences
    that started off well but ended up being junk.
---
 test/jiffy_04_string_tests.erl | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/test/jiffy_04_string_tests.erl b/test/jiffy_04_string_tests.erl
index 0e37f27..f923a31 100644
--- a/test/jiffy_04_string_tests.erl
+++ b/test/jiffy_04_string_tests.erl
@@ -130,7 +130,17 @@ cases(error) ->
         <<"\"\\uD834foo\\uDD1E\"">>,
         <<"\"\\u", 200, 200, 200, 200, "\"">>,
         % CouchDB-345
-        <<34,78,69,73,77,69,78,32,70,216,82,82,32,70,65,69,78,33,34>>
+        <<34,78,69,73,77,69,78,32,70,216,82,82,32,70,65,69,78,33,34>>,
+        % Lone high surrogate followed by non-backslash
+        <<"\"\\uD834a\"">>,
+        % Lone high surrogate followed by backslash but not 'u'
+        <<"\"\\uD834\\n\"">>,
+        % Lone high surrogate followed by \u but not a valid low surrogate
+        <<"\"\\uD834\\u0041\"">>,
+        % Truncated \uXX (not enough hex digits)
+        <<"\"\\u00\"">>,
+        % Invalid hex digit in \u escape
+        <<"\"\\uZZZZ\"">>
     ];
 
 cases(utf8) ->


(couchdb-jiffy) 10/49: Add a deep pretty printing test

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 953a171134e25ae63195caaae51e192e5d12f8aa
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Tue Mar 31 01:00:13 2026 -0400

    Add a deep pretty printing test
    
    Coverage showed we didnt' test the `NUM_SHIFT>8` case in `enc_shift` (Yay for
    coverage!)
---
 test/jiffy_05_array_tests.erl | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/test/jiffy_05_array_tests.erl b/test/jiffy_05_array_tests.erl
index 26e0a65..1f6b750 100644
--- a/test/jiffy_05_array_tests.erl
+++ b/test/jiffy_05_array_tests.erl
@@ -26,6 +26,14 @@ nested(0) -> <<"bottom">>;
 nested(N) -> [nested(N - 1)].
 
 
+encode_deep_pretty_test() ->
+    %% Here we're trying to go deeper than NUM_SHIFTS=8 in enc_shift
+    Deep = lists:foldl(fun(_, Acc) -> [Acc] end, 1, lists:seq(1, 12)),
+    Encoded = iolist_to_binary(jiffy:encode(Deep, [pretty])),
+    ?assert(is_binary(Encoded)),
+    ?assertEqual(Deep, jiffy:decode(Encoded)).
+
+
 gen(ok, {J, E}) ->
     gen(ok, {J, E, J});
 gen(ok, {J1, E, J2}) ->


(couchdb-jiffy) 39/49: Header cleanups

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 6b04965ca668ecc84196a7c6360267cbc63c914f
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Sun Apr 12 22:16:50 2026 -0400

    Header cleanups
    
    `include-what-you-use` says we don't need <stdio.h> in decoder and encoder so
    removing them
---
 c_src/decoder.c | 1 -
 c_src/encoder.c | 1 -
 2 files changed, 2 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 46abfec..861f32b 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -3,7 +3,6 @@
 
 #include <assert.h>
 #include <errno.h>
-#include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
diff --git a/c_src/encoder.c b/c_src/encoder.c
index 01c5533..8ad4067 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -2,7 +2,6 @@
 // See the LICENSE file for more information.
 
 #include <assert.h>
-#include <stdio.h>
 #include <string.h>
 
 #include "jiffy.h"


(couchdb-jiffy) 44/49: Use ffc.h library to parse numbers

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit f6f49d1c0dce7d42d89b7b25aaab3a1ceb17c86e
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Mon Apr 13 22:44:00 2026 -0400

    Use ffc.h library to parse numbers
    
    This is currently the fastest number parsing library available in C. It's port
    of Dr. Lemire's https://github.com/fastfloat/fast_float to C by Koleman Nix and
    available at https://github.com/kolemannix/ffc.h
    
    The nice thing about this library is it can parse the numbers directly from the
    stream without needing a separe copy into buffer just to have a \0 terminator
    for strtod and strtol.
---
 LICENSE         |  221 ++++
 README.ffc.md   |   11 +
 c_src/decoder.c |   47 +-
 c_src/ffc.h     | 3242 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 c_src/jiffy.c   |    4 +
 5 files changed, 3501 insertions(+), 24 deletions(-)

diff --git a/LICENSE b/LICENSE
index 088a534..ba6f644 100644
--- a/LICENSE
+++ b/LICENSE
@@ -342,3 +342,224 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
+
+ffc.h Apache 2.0 License
+========================
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   Copyright 2021 The fast_float authors
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ffc.h Boost License
+===================
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/README.ffc.md b/README.ffc.md
new file mode 100644
index 0000000..fc1f7e1
--- /dev/null
+++ b/README.ffc.md
@@ -0,0 +1,11 @@
+Fast Float C Library (ffc.h)
+----------------------------
+
+This is from https://github.com/kolemannix/ffc.h
+
+A copy and paste into c_src. In the unity build `jiffy.c` it's included as
+
+```
+#define FFC_IMPL
+#include "ffc.h"
+```
diff --git a/c_src/decoder.c b/c_src/decoder.c
index 779f947..ae2d266 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -2,16 +2,15 @@
 // See the LICENSE file for more information.
 
 #include <assert.h>
-#include <errno.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include "erl_nif.h"
+#include "ffc.h"
 #include "jiffy.h"
 #include "jiffy_utf8.h"
 
 #define STACK_SIZE_INC 64
-#define NUM_BUF_LEN 32
 #define JIFFY_SMALL_ARRAY_SIZE 64
 
 enum {
@@ -382,11 +381,10 @@ dec_number(Decoder* d, ERL_NIF_TERM* value)
 {
     ERL_NIF_TERM num_type = d->atoms->atom_error;
     char state = nst_init;
-    char nbuf[NUM_BUF_LEN];
     int has_frac = 0;
     int has_exp = 0;
     double dval;
-    long lval;
+    int64_t lval;
 
     // Use the same trick as did for dec_string. The restrict qualifier hints
     // to the compiler p won't alias any other pointers so it can optimize
@@ -633,26 +631,27 @@ parse:
             break;
     }
 
-    errno = 0;
-
-    size_t num_len = d->i - start;
-
-    if(num_len < NUM_BUF_LEN) {
-        memcpy(nbuf, &p[start], num_len);
-        nbuf[num_len] = '\0';
-
-        if(has_frac || has_exp) {
-            dval = strtod(nbuf, NULL);
-            if(errno != ERANGE) {
-                *value = enif_make_double(d->env, dval);
-                return 1;
-            }
-        } else {
-            lval = strtol(nbuf, NULL, 10);
-            if(errno != ERANGE) {
-                *value = enif_make_int64(d->env, lval);
-                return 1;
-            }
+    // Use ffc.h to parse numbers. It parses direclty from the stream no need
+    // to allocate a separate buffer as with strtod and strtol. The state
+    // machine already validated the syntax, so parse-level errors shouldn't
+    // occur here, only FFC_OUTCOME_OUT_OF_RANGE. If the range erro happens we
+    // fall back to Erlang with handle big numbers.
+    const char* nstart = (const char*)&p[start];
+    const char* nend = (const char*)&p[d->i];
+    const size_t num_len = d->i - start;
+
+    if(has_frac || has_exp) {
+        ffc_parse_options opts = {FFC_PRESET_JSON, '.'};
+        ffc_result res = ffc_from_chars_double_options(nstart, nend, &dval, opts);
+        if(res.outcome == FFC_OUTCOME_OK) {
+            *value = enif_make_double(d->env, dval);
+            return 1;
+        }
+    } else {
+        ffc_result res = ffc_parse_i64(num_len, nstart, 10, &lval);
+        if(res.outcome == FFC_OUTCOME_OK) {
+            *value = enif_make_int64(d->env, lval);
+            return 1;
         }
     }
 
diff --git a/c_src/ffc.h b/c_src/ffc.h
new file mode 100644
index 0000000..8d40697
--- /dev/null
+++ b/c_src/ffc.h
@@ -0,0 +1,3242 @@
+// fast_float by Daniel Lemire (Original)
+// fast_float by João Paulo Magalhaes (Original)
+// fast_float by Koleman Nix (C Port)
+//
+//
+// with contributions from Eugene Golushkov
+// with contributions from Maksim Kita
+// with contributions from Marcin Wojdyr
+// with contributions from Neal Richardson
+// with contributions from Tim Paine
+// with contributions from Fabio Pellacini
+// with contributions from Lénárd Szolnoki
+// with contributions from Jan Pharago
+// with contributions from Maya Warrier
+// with contributions from Taha Khokhar
+// with contributions from Anders Dalvander
+// with contributions from Koleman Nix
+// with contributions from Michael Grunder
+//
+//
+// Licensed under the Apache License, Version 2.0, or the
+// MIT License or the Boost License. This file may not be copied,
+// modified, or distributed except according to those terms.
+//
+// MIT License Notice
+//
+//    MIT License
+//
+//    Copyright (c) 2021 The fast_float authors
+//
+//    Permission is hereby granted, free of charge, to any
+//    person obtaining a copy of this software and associated
+//    documentation files (the "Software"), to deal in the
+//    Software without restriction, including without
+//    limitation the rights to use, copy, modify, merge,
+//    publish, distribute, sublicense, and/or sell copies of
+//    the Software, and to permit persons to whom the Software
+//    is furnished to do so, subject to the following
+//    conditions:
+//
+//    The above copyright notice and this permission notice
+//    shall be included in all copies or substantial portions
+//    of the Software.
+//
+//    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+//    ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+//    TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+//    PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+//    SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+//    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+//    OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+//    IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+//    DEALINGS IN THE SOFTWARE.
+//
+// Apache License (Version 2.0) Notice
+//
+//    Copyright 2021 The fast_float authors
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//
+// BOOST License Notice
+//
+//    Boost Software License - Version 1.0 - August 17th, 2003
+//
+//    Permission is hereby granted, free of charge, to any person or organization
+//    obtaining a copy of the software and accompanying documentation covered by
+//    this license (the "Software") to use, reproduce, display, distribute,
+//    execute, and transmit the Software, and to prepare derivative works of the
+//    Software, and to permit third-parties to whom the Software is furnished to
+//    do so, all subject to the following:
+//
+//    The copyright notices in the Software and this entire statement, including
+//    the above license grant, this restriction and the following disclaimer,
+//    must be included in all copies of the Software, in whole or in part, and
+//    all derivative works of the Software, unless such copies or derivative
+//    works are solely in the form of machine-executable object code generated by
+//    a source language processor.
+//
+//    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//    FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+//    SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+//    FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+//    ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+//    DEALINGS IN THE SOFTWARE.
+//
+
+// This file is auto-generated by amalgamate.py from the sources in src/.
+// Do not edit it directly — edit the source files and regenerate.
+//
+
+/* ffc.h
+   single-header decimal float parser using eisel-lemire
+   This is a direct port by Koleman Nix of the fast_float library
+   authored by Daniel Lemire
+*/
+
+#ifndef FFC_H
+#define FFC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef FFC_API
+#define FFC_API
+
+#define FFC_VERSION_YEAR  26
+#define FFC_VERSION_MONTH 03
+#define FFC_VERSION_BUILD 03
+#define FFC_VERSION ((FFC_VERSION_YEAR << 16) | (FFC_VERSION_MONTH << 8) | (FFC_VERSION_BUILD))
+#define FFC_VERSION_STRINGIFY_(x) #x
+#define FFC_VERSION_STRINGIFY(x) FFC_VERSION_STRINGIFY_(x)
+#define FFC_VERSION_STRING \
+  FFC_VERSION_STRINGIFY(FFC_VERSION_YEAR) "." \
+  FFC_VERSION_STRINGIFY(FFC_VERSION_MONTH) "." \
+  FFC_VERSION_STRINGIFY(FFC_VERSION_BUILD)
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef uint32_t ffc_outcome;
+enum ffc_outcome_bits {
+  FFC_OUTCOME_OK = 0,
+  FFC_OUTCOME_INVALID_INPUT = 1,
+  FFC_OUTCOME_OUT_OF_RANGE = 2,
+};
+
+typedef struct ffc_result {
+  // Where parsing stopped
+  char const *ptr;
+  // The outcome of the call
+  ffc_outcome outcome;
+} ffc_result;
+
+typedef uint64_t ffc_format;
+enum ffc_format_bits {
+  FFC_FORMAT_FLAG_SCIENTIFIC         = 1ULL << 0,
+  FFC_FORMAT_FLAG_FIXED              = 1ULL << 2, // Gap is present in fast_float original
+  FFC_FORMAT_FLAG_HEX                = 1ULL << 3,
+  FFC_FORMAT_FLAG_NO_INFNAN          = 1ULL << 4,
+  FFC_FORMAT_FLAG_BASIC_JSON         = 1ULL << 5,
+  FFC_FORMAT_FLAG_BASIC_FORTRAN      = 1ULL << 6,
+  FFC_FORMAT_FLAG_ALLOW_LEADING_PLUS = 1ULL << 7,
+  FFC_FORMAT_FLAG_SKIP_WHITE_SPACE   = 1ULL << 8,
+
+  /* Presets */
+  FFC_PRESET_GENERAL = FFC_FORMAT_FLAG_FIXED | FFC_FORMAT_FLAG_SCIENTIFIC,
+  
+  FFC_PRESET_JSON = FFC_FORMAT_FLAG_BASIC_JSON | 
+                       FFC_PRESET_GENERAL | 
+                       FFC_FORMAT_FLAG_NO_INFNAN,
+
+  FFC_PRESET_JSON_OR_INFNAN = FFC_FORMAT_FLAG_BASIC_JSON | 
+                                 FFC_PRESET_GENERAL,
+
+  FFC_PRESET_FORTRAN = FFC_FORMAT_FLAG_BASIC_FORTRAN | 
+                          FFC_PRESET_GENERAL
+};
+
+typedef struct ffc_parse_options {
+  /** Which number formats are accepted */
+  ffc_format format;
+  /** The character used as decimal point; period will be used if decimal_point == '\0' */
+  char decimal_point;
+} ffc_parse_options;
+
+ffc_parse_options ffc_parse_options_default(void);
+
+typedef enum ffc_parse_outcome {
+  FFC_PARSE_OUTCOME_NO_ERROR = 0,
+  // [JSON-only] The minus sign must be followed by an integer.
+  FFC_PARSE_OUTCOME_JSON_MISSING_INTEGER_AFTER_SIGN = 1,
+  // A sign must be followed by an integer or dot.
+  FFC_PARSE_OUTCOME_MISSING_INTEGER_OR_DOT_AFTER_SIGN = 2,
+  // [JSON-only] The integer part must not have leading zeros.
+  FFC_PARSE_OUTCOME_JSON_LEADING_ZEROS_IN_INTEGER_PART = 3,
+  // [JSON-only] The integer part must have at least one digit.
+  FFC_PARSE_OUTCOME_JSON_NO_DIGITS_IN_INTEGER_PART = 4,
+  // [JSON-only] If there is a decimal point, there must be digits in the
+  // fractional part.
+  FFC_PARSE_OUTCOME_JSON_NO_DIGITS_IN_FRACTIONAL_PART = 5,
+  // The mantissa must have at least one digit.
+  FFC_PARSE_OUTCOME_NO_DIGITS_IN_MANTISSA = 6,
+  // Scientific notation requires an exponential part.
+  FFC_PARSE_OUTCOME_MISSING_EXPONENTIAL_PART = 7,
+} ffc_parse_outcome;
+
+/*
+ * A simplified API; the result will be 0.0 on error, not uninitialized.
+ * If outcome is null, it will not be written to
+ */
+double     ffc_parse_double_simple(size_t len, const char *input, ffc_outcome *outcome);
+ffc_result ffc_parse_double(size_t len, const char *input, double *out);
+/**
+ * Implements the fast_float algorithm from https://github.com/fastfloat/fast_float
+ * See original for more details
+ *
+ * This function parses the character sequence [first,last) for a number. It
+ * parses floating-point numbers expecting a locale-independent format equivalent
+ * to what is used by std::strtod in the default ("C") locale. The resulting
+ * floating-point value is the closest floating-point value (using either float
+ * or double), using the "round to even" convention for values that would
+ * otherwise fall right in-between two values. That is, we provide exact parsing
+ * according to the IEEE standard.
+ *
+ * Given a successful parse, the pointer (`ptr`) in the returned value is set to
+ * point right after the parsed number, and the `value` referenced is set to the
+ * parsed value. In case of error, the returned `ec` contains a representative
+ * error, otherwise the default (`FFC_OUTCOME_OK`) value is stored.
+ *
+ * The implementation does not allocate heap memory.
+ *
+ * Like the C++17 standard, the `fast_float::from_chars` functions take an
+ * optional last argument of the type `fast_float::chars_format`. It is a bitset
+ * value: we check whether `fmt & fast_float::chars_format::fixed` and `fmt &
+ * fast_float::chars_format::scientific` are set to determine whether we allow
+ * the fixed point and scientific notation respectively. The default is
+ * `fast_float::chars_format::general` which allows both `fixed` and
+ * `scientific`.
+ */
+ffc_result ffc_from_chars_double(const char *start, const char *end, double* out);
+ffc_result ffc_from_chars_double_options(const char *start, const char *end, double* out, ffc_parse_options options);
+
+/*
+ * A simplified API; the result will be 0.0 on error, not uninitialized.
+ * If outcome is null, it will not be written to
+ */
+float      ffc_parse_float_simple(size_t len, const char *s, ffc_outcome *outcome);
+ffc_result ffc_parse_float(size_t len, const char *s, float *out);
+ffc_result ffc_from_chars_float(const char *start,  const char *end, float* out);
+ffc_result ffc_from_chars_float_options(const char *start,  const char *end, float* out, ffc_parse_options options);
+
+ffc_result ffc_parse_i64(size_t len, const char *input, int base, int64_t  *out);
+ffc_result ffc_parse_u64(size_t len, const char *input, int base, uint64_t *out);
+ffc_result ffc_parse_i32(size_t len, const char *input, int base, int32_t  *out);
+ffc_result ffc_parse_u32(size_t len, const char *input, int base, uint32_t *out);
+
+/*
+ * A simplified API; the result will be 0 on error, not uninitialized.
+ * If outcome is null, it will not be written to
+ */
+int64_t  ffc_parse_i64_simple(size_t len, const char *input, int base, ffc_outcome *outcome);
+uint64_t ffc_parse_u64_simple(size_t len, const char *input, int base, ffc_outcome *outcome);
+int32_t  ffc_parse_i32_simple(size_t len, const char *input, int base, ffc_outcome *outcome);
+uint32_t ffc_parse_u32_simple(size_t len, const char *input, int base, ffc_outcome *outcome);
+
+#endif // FFC_API
+
+#ifdef FFC_IMPL
+
+#ifndef FFC_COMMON_H
+#define FFC_COMMON_H
+
+#include <float.h> // for NAN, FLT_MIN
+#include <stdlib.h>
+#include <string.h> // for memcpy
+#include <stdbool.h>
+
+#ifndef ffc_internal
+#define ffc_internal static
+#endif
+
+#if defined(_MSC_VER)
+  #define ffc_inline __forceinline
+#elif defined(__GNUC__) || defined(__clang__)
+  #define ffc_inline __attribute__((always_inline)) inline
+#else
+  #define ffc_inline inline
+#endif
+
+#if FFC_DEBUG
+#include <stdio.h>
+#define ffc_debug(...) do { fprintf(stderr, __VA_ARGS__); } while(0)
+#else
+#define ffc_debug(...) do { } while(0)
+#endif
+
+
+ffc_internal ffc_inline
+uint64_t ffc_get_double_bits(double d) {
+  uint64_t bits;
+  memcpy(&bits, &d, sizeof(double));
+  return bits;
+}
+
+ffc_internal ffc_inline
+uint32_t ffc_get_float_bits(float d) {
+  uint32_t bits;
+  memcpy(&bits, &d, sizeof(float));
+  return bits;
+}
+
+typedef union ffc_value {
+  double d;
+  float f;
+} ffc_value;
+
+typedef union ffc_value_bits {
+  uint64_t di;
+  uint32_t fi;
+} ffc_value_bits;
+
+typedef uint8_t ffc_value_kind;
+enum ffc_value_kind_bits {
+  FFC_VALUE_KIND_FLOAT = 0,
+  FFC_VALUE_KIND_DOUBLE = 1,
+};
+
+typedef union ffc_int_value {
+  int64_t  s64;
+  int32_t  s32;
+  uint64_t u64;
+  uint32_t u32;
+} ffc_int_value;
+
+typedef uint8_t ffc_int_kind;
+enum ffc_int_kind_bits {
+  FFC_INT_KIND_S64 = 0,
+  FFC_INT_KIND_S32 = 1,
+  FFC_INT_KIND_U64 = 2,
+  FFC_INT_KIND_U32 = 3,
+};
+
+ffc_internal ffc_inline
+bool ffc_int_kind_is_signed(ffc_int_kind ik) {
+  return ik == FFC_INT_KIND_S64 || ik == FFC_INT_KIND_S32;
+}
+
+
+ffc_internal ffc_inline
+ffc_value_bits ffc_get_value_bits(ffc_value value, ffc_value_kind vk) {
+  ffc_value_bits bits;
+  if (vk == FFC_VALUE_KIND_DOUBLE) {
+    bits.di = ffc_get_double_bits(value.d);
+  } else {
+    bits.fi = ffc_get_float_bits(value.f);
+  }
+  return bits;
+}
+
+ffc_internal ffc_inline
+uint64_t ffc_int_value_max(ffc_int_kind ik) {
+  switch (ik) {
+  case FFC_INT_KIND_S64: return INT64_MAX;
+  case FFC_INT_KIND_S32: return INT32_MAX;
+  case FFC_INT_KIND_U64: return UINT64_MAX;
+  case FFC_INT_KIND_U32: return UINT32_MAX;
+  default: return 0; // should never happen
+  }
+}
+
+#define ffc_set_value(ptr, value_kind, value) \
+    (((value_kind) == FFC_VALUE_KIND_DOUBLE) ? ((ptr)->d = (double)(value)) : ((ptr)->f = (float)(value)))
+
+#define ffc_read_value(ptr, value_kind) \
+    (((value_kind) == FFC_VALUE_KIND_DOUBLE) ? (ptr)->d : (ptr)->f)
+
+typedef struct ffc_adjusted_mantissa {
+  uint64_t mantissa;
+  int32_t power2; // a negative value indicates an invalid result
+} ffc_adjusted_mantissa;
+
+ffc_internal ffc_inline size_t ffc_get_value_size(ffc_value_kind vk) {
+  if (vk == FFC_VALUE_KIND_DOUBLE) {
+    return sizeof(double);
+  } else {
+    return sizeof(float);
+  }
+}
+
+// Bias so we can get the real exponent with an invalid adjusted_mantissa.
+#define FFC_INVALID_AM_BIAS ((int32_t)-0x8000)
+
+/********************* context crack: word size *********************/
+
+#if (defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) ||            \
+     defined(__amd64) || defined(__aarch64__) || defined(_M_ARM64) ||          \
+     defined(__MINGW64__) || defined(__s390x__) ||                             \
+     (defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) ||      \
+      defined(__PPC64LE__)) ||                                                 \
+     defined(__loongarch64) || (defined(__riscv) && __riscv_xlen == 64))
+#define FFC_64BIT 1
+#elif (defined(__i386) || defined(__i386__) || defined(_M_IX86) ||             \
+       defined(__arm__) || defined(_M_ARM) || defined(__ppc__) ||              \
+       defined(__MINGW32__) || defined(__EMSCRIPTEN__) ||                      \
+       (defined(__riscv) && __riscv_xlen == 32))
+#define FFC_32BIT 1
+#else
+// Need to check incrementally, since SIZE_MAX is a size_t, avoid overflow.
+// We can never tell the register width, but the SIZE_MAX is a good
+// approximation. UINTPTR_MAX and INTPTR_MAX are optional, so avoid them for max
+// portability.
+#if SIZE_MAX == 0xffff
+#error Unknown platform (16-bit, unsupported)
+#elif SIZE_MAX == 0xffffffff
+#define FFC_32BIT 1
+#elif SIZE_MAX == 0xffffffffffffffff
+#define FFC_64BIT 1
+#else
+#error Unknown platform (not 32-bit, not 64-bit?)
+#endif
+#endif
+
+/********************* context crack: intrinsics *********************/
+#if ((defined(_WIN32) || defined(_WIN64)) && !defined(__clang__)) ||           \
+    (defined(_M_ARM64) && !defined(__MINGW32__))
+#include <intrin.h>
+#endif
+
+#if defined(_MSC_VER) && !defined(__clang__)
+#define FFC_VISUAL_STUDIO 1
+#endif
+
+/********************* context crack: byte order / endianness *********************/
+#if defined __BYTE_ORDER__ && defined __ORDER_BIG_ENDIAN__
+#define FFC_IS_BIG_ENDIAN (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#elif defined _WIN32
+#define FFC_IS_BIG_ENDIAN 0
+#else
+#if defined(__APPLE__) || defined(__FreeBSD__)
+#include <machine/endian.h>
+#elif defined(sun) || defined(__sun)
+#include <sys/byteorder.h>
+#elif defined(__MVS__)
+#include <sys/endian.h>
+#else
+#ifdef __has_include
+#if __has_include(<endian.h>)
+#include <endian.h>
+#endif //__has_include(<endian.h>)
+#endif //__has_include
+#endif
+#
+#ifndef __BYTE_ORDER__
+// safe choice
+#define FFC_IS_BIG_ENDIAN 0
+#endif
+#
+#ifndef __ORDER_LITTLE_ENDIAN__
+// safe choice
+#define FFC_IS_BIG_ENDIAN 0
+#endif
+#
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define FFC_IS_BIG_ENDIAN 0
+#else
+#define FFC_IS_BIG_ENDIAN 1
+#endif
+#endif
+
+/********************* context crack: simd *********************/
+#if defined(__SSE2__) || (defined(FFC_VISUAL_STUDIO) &&                  \
+                          (defined(_M_AMD64) || defined(_M_X64) ||             \
+                           (defined(_M_IX86_FP) && _M_IX86_FP == 2)))
+#define FFC_SSE2 1
+#endif
+
+#if defined(__aarch64__) || defined(_M_ARM64)
+#define FFC_NEON 1
+#endif
+
+#if defined(FFC_SSE2) || defined(FFC_NEON)
+#define FFC_HAS_SIMD 1
+#endif
+
+
+#ifdef FFC_SSE2
+#include <emmintrin.h>
+#endif
+
+#ifdef FFC_NEON
+#include <arm_neon.h>
+#endif
+
+
+#if defined(__GNUC__)
+// disable -Wcast-align=strict (GCC only)
+#define FFC_SIMD_DISABLE_WARNINGS                                        \
+  _Pragma("GCC diagnostic push")                                               \
+      _Pragma("GCC diagnostic ignored \"-Wcast-align\"")
+#else
+#define FFC_SIMD_DISABLE_WARNINGS
+#endif
+
+#if defined(__GNUC__)
+#define FFC_SIMD_RESTORE_WARNINGS _Pragma("GCC diagnostic pop")
+#else
+#define FFC_SIMD_RESTORE_WARNINGS
+#endif
+
+ffc_internal ffc_inline
+uint32_t ffc_count_leading_zeroes(uint64_t x) {
+#if defined(__GNUC__) || defined(__clang__)
+  return x ? (uint32_t)__builtin_clzll(x) : 64u;
+#else
+  if (x == 0) return 64u;
+  uint32_t n = 0;
+  if ((x >> 32) == 0) { n |= 32; x <<= 32; }
+  if ((x >> 48) == 0) { n |= 16; x <<= 16; }
+  if ((x >> 56) == 0) { n |=  8; x <<=  8; }
+  if ((x >> 60) == 0) { n |=  4; x <<=  4; }
+  if ((x >> 62) == 0) { n |=  2; x <<=  2; }
+  if ((x >> 63) == 0) { n |=  1; }
+  return n;
+#endif
+}
+
+typedef struct ffc_u128 { uint64_t low; uint64_t high; } ffc_u128;
+ffc_internal ffc_inline
+ffc_u128 ffc_mul_u64(uint64_t a, uint64_t b) {
+#if defined(__SIZEOF_INT128__)
+  __uint128_t z = ((__uint128_t)a) * ((__uint128_t)b);
+  ffc_u128 output;
+  output.low = (uint64_t)z;
+  output.high = (uint64_t)(z >> 64);
+  return output;
+#else
+  uint64_t a0 = a & 0xFFFFFFFF;
+  uint64_t a1 = a >> 32;
+  uint64_t b0 = b & 0xFFFFFFFF;
+  uint64_t b1 = b >> 32;
+
+  uint64_t w0 = a0 * b0;
+  uint64_t t  = (a1 * b0) + (w0 >> 32);
+  uint64_t w1 = t & 0xFFFFFFFF;
+  uint64_t w2 = t >> 32;
+  w1 += a0 * b1;
+
+  ffc_u128 output;
+  output.low = a * b;
+  output.high = (a1 * b1) + w2 + (w1 >> 32);
+  return output;
+#endif
+}
+
+// slow emulation routine for 32-bit
+ffc_internal ffc_inline uint64_t ffc_emulu(uint32_t x, uint32_t y) {
+  return x * (uint64_t)y;
+}
+
+ffc_internal ffc_inline
+uint64_t ffc_umul128_generic(uint64_t ab, uint64_t cd, uint64_t *hi) {
+  uint64_t ad = ffc_emulu((uint32_t)(ab >> 32), (uint32_t)cd);
+  uint64_t bd = ffc_emulu((uint32_t)ab, (uint32_t)cd);
+  uint64_t adbc = ad + ffc_emulu((uint32_t)ab, (uint32_t)(cd >> 32));
+  uint64_t adbc_carry = (uint64_t)(adbc < ad);
+  uint64_t lo = bd + (adbc << 32);
+  *hi = ffc_emulu((uint32_t)(ab >> 32), (uint32_t)(cd >> 32)) + (adbc >> 32) +
+        (adbc_carry << 32) + (uint64_t)(lo < bd);
+  return lo;
+}
+
+// compute 64-bit a*b
+ffc_internal ffc_inline
+ffc_u128 ffc_full_multiplication(uint64_t a, uint64_t b) {
+  ffc_u128 answer;
+#if defined(_M_ARM64) && !defined(__MINGW32__)
+  // ARM64 has native support for 64-bit multiplications, no need to emulate
+  // But MinGW on ARM64 doesn't have native support for 64-bit multiplications
+  answer.high = __umulh(a, b);
+  answer.low = a * b;
+#elif (defined(_WIN64) && !defined(__clang__) &&   \
+                                   !defined(_M_ARM64) && !defined(__GNUC__))
+  answer.low = _umul128(a, b, &answer.high); // _umul128 not available on ARM64
+#elif defined(FFC_64BIT) && defined(__SIZEOF_INT128__)
+  __uint128_t r = ((__uint128_t)a) * b;
+  answer.low = (uint64_t)(r);
+  answer.high = (uint64_t)(r >> 64);
+#else
+  answer.low = ffc_umul128_generic(a, b, &answer.high);
+#endif
+  return answer;
+}
+
+#define FFC_DOUBLE_SMALLEST_POWER_OF_10        -342
+#define FFC_DOUBLE_LARGEST_POWER_OF_10         308
+#define FFC_DOUBLE_SIGN_INDEX                  63
+#define FFC_DOUBLE_INFINITE_POWER              0x7FF
+#define FFC_DOUBLE_MANTISSA_EXPLICIT_BITS      52
+#define FFC_DOUBLE_MINIMUM_EXPONENT            -1023
+#define FFC_DOUBLE_MIN_EXPONENT_ROUND_TO_EVEN  -4
+#define FFC_DOUBLE_MAX_EXPONENT_ROUND_TO_EVEN  23
+#define FFC_DOUBLE_MAX_EXPONENT_FAST_PATH      22
+#define FFC_DOUBLE_MAX_MANTISSA_FAST_PATH      ((uint64_t)(2) << FFC_DOUBLE_MANTISSA_EXPLICIT_BITS)
+#define FFC_DOUBLE_EXPONENT_MASK               0x7FF0000000000000
+#define FFC_DOUBLE_MANTISSA_MASK               0x000FFFFFFFFFFFFF
+#define FFC_DOUBLE_HIDDEN_BIT_MASK             0x0010000000000000
+#define FFC_DOUBLE_MAX_DIGITS                  769
+
+#define FFC_FLOAT_SMALLEST_POWER_OF_10         -64
+#define FFC_FLOAT_LARGEST_POWER_OF_10          38
+#define FFC_FLOAT_SIGN_INDEX                   31
+#define FFC_FLOAT_INFINITE_POWER               0xFF
+#define FFC_FLOAT_MANTISSA_EXPLICIT_BITS       23
+#define FFC_FLOAT_MINIMUM_EXPONENT             -127
+#define FFC_FLOAT_MIN_EXPONENT_ROUND_TO_EVEN   -17
+#define FFC_FLOAT_MAX_EXPONENT_ROUND_TO_EVEN   10
+#define FFC_FLOAT_MAX_EXPONENT_FAST_PATH       10
+#define FFC_FLOAT_MAX_MANTISSA_FAST_PATH       ((uint64_t)(2) << FFC_FLOAT_MANTISSA_EXPLICIT_BITS)
+#define FFC_FLOAT_EXPONENT_MASK                0x7F800000
+#define FFC_FLOAT_MANTISSA_MASK                0x007FFFFF
+#define FFC_FLOAT_HIDDEN_BIT_MASK              0x00800000
+#define FFC_FLOAT_MAX_DIGITS                   114
+
+#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
+  #define FFC_DOUBLE_MIN_EXPONENT_FAST_PATH 0
+  #define FFC_FLOAT_MIN_EXPONENT_FAST_PATH  0
+#else
+  #define FFC_DOUBLE_MIN_EXPONENT_FAST_PATH -22
+  #define FFC_FLOAT_MIN_EXPONENT_FAST_PATH  -10
+#endif
+
+#define ffc_const(value_kind, name) (value_kind == FFC_VALUE_KIND_DOUBLE ? FFC_DOUBLE_##name : FFC_FLOAT_##name)
+
+#define FFC_POWERS_OF_5_NUMBER_OF_ENTRIES (2 * (FFC_DOUBLE_LARGEST_POWER_OF_10 - FFC_DOUBLE_SMALLEST_POWER_OF_10 + 1))
+
+// Powers of five from 5^-342 all the way to 5^308 rounded toward one.
+ffc_internal uint64_t FFC_POWERS_OF_FIVE[FFC_POWERS_OF_5_NUMBER_OF_ENTRIES] = { 
+  0xeef453d6923bd65a, 0x113faa2906a13b3f, 0x9558b4661b6565f8, 0x4ac7ca59a424c507, 0xbaaee17fa23ebf76, 0x5d79bcf00d2df649, 0xe95a99df8ace6f53, 0xf4d82c2c107973dc, 0x91d8a02bb6c10594, 0x79071b9b8a4be869, 0xb64ec836a47146f9, 0x9748e2826cdee284, 0xe3e27a444d8d98b7, 0xfd1b1b2308169b25, 0x8e6d8c6ab0787f72, 0xfe30f0f5e50e20f7, 0xb208ef855c969f4f, 0xbdbd2d335e51a935, 0xde8b2b66b3bc4723, 0xad2c788035e61382, 0x8b16fb203055ac76, 0x4c3bcb5021afcc31, 0xaddcb9e83c6b1793, 0xdf4abe242a1bbf3d, 0xd953e862 [...]
+};
+
+// lookup table for ascii values
+ffc_internal bool FFC_SPACE_LUT[256] = {
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+
+ffc_internal ffc_inline bool ffc_is_space(char c) {
+  return FFC_SPACE_LUT[(uint8_t)c];
+}
+
+ffc_internal uint8_t FFC_CHAR_TO_DIGIT_LUT[] = {
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 0,   1,   2,   3,   4,   5,   6,   7,   8,   9,   255, 255,
+      255, 255, 255, 255, 255, 10,  11,  12,  13,  14,  15,  16,  17,  18,  19,
+      20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,  32,  33,  34,
+      35,  255, 255, 255, 255, 255, 255, 10,  11,  12,  13,  14,  15,  16,  17,
+      18,  19,  20,  21,  22,  23,  24,  25,  26,  27,  28,  29,  30,  31,  32,
+      33,  34,  35,  255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+      255};
+
+ffc_internal ffc_inline 
+uint8_t ffc_char_to_digit(char c) {
+  return FFC_CHAR_TO_DIGIT_LUT[(uint8_t)c];
+}
+
+// Indexed by a 'base' but offset by 2, first entry is base 2, 64 digits, checks out right?
+ffc_internal size_t FFC_MAXDIGITS_OF_BASE_U64[] = {
+    64, 41, 32, 28, 25, 23, 22, 21, 20, 19, 18, 18, 17, 17, 16, 16, 16, 16,
+    15, 15, 15, 15, 14, 14, 14, 14, 14, 14, 14, 13, 13, 13, 13, 13, 13};
+
+ffc_internal ffc_inline size_t ffc_max_digits_u64(int base) {
+  return FFC_MAXDIGITS_OF_BASE_U64[base - 2];
+}
+
+ffc_internal ffc_inline bool ffc_is_integer(char c) {
+  // can be micro-optimized, but compilers are entirely able to optimize it well
+  return (unsigned)(c - '0') <= 9u;
+}
+
+
+ffc_internal uint64_t FFC_MIN_SAFE_OF_BASE_U64[] = {
+      9223372036854775808ull,  12157665459056928801ull, 4611686018427387904,
+      7450580596923828125,     4738381338321616896,     3909821048582988049,
+      9223372036854775808ull,  12157665459056928801ull, 10000000000000000000ull,
+      5559917313492231481,     2218611106740436992,     8650415919381337933,
+      2177953337809371136,     6568408355712890625,     1152921504606846976,
+      2862423051509815793,     6746640616477458432,     15181127029874798299ull,
+      1638400000000000000,     3243919932521508681,     6221821273427820544,
+      11592836324538749809ull, 876488338465357824,      1490116119384765625,
+      2481152873203736576,     4052555153018976267,     6502111422497947648,
+      10260628712958602189ull, 15943230000000000000ull, 787662783788549761,
+      1152921504606846976,     1667889514952984961,     2386420683693101056,
+      3379220508056640625,     4738381338321616896};
+
+ffc_internal ffc_inline uint64_t ffc_min_safe_u64_of_base(int base) {
+  return FFC_MIN_SAFE_OF_BASE_U64[base - 2];
+}
+
+ffc_internal ffc_inline
+bool ffc_strncasecmp3(char const *actual_mixedcase, char const *expected_lowercase, size_t char_width) {
+  uint64_t mask = 0;
+  if (char_width == 1) { mask = 0x2020202020202020; }
+  else if (char_width == 2) { mask = 0x0020002000200020; }
+  else if (char_width == 4) { mask = 0x0000002000000020; }
+  else { return false; }
+
+  uint64_t val1 = 0;
+  uint64_t val2 = 0;
+  if (char_width == 1 || char_width == 2) {
+    memcpy(&val1, actual_mixedcase, 3 * char_width);
+    memcpy(&val2, expected_lowercase, 3 * char_width);
+    val1 |= mask;
+    val2 |= mask;
+    return val1 == val2;
+  } else if (char_width == 4) {
+    memcpy(&val1, actual_mixedcase, 2 * char_width);
+    memcpy(&val2, expected_lowercase, 2 * char_width);
+    val1 |= mask;
+    if (val1 != val2) {
+      return false;
+    }
+    return (actual_mixedcase[2] | 32) == (expected_lowercase[2]);
+  } else {
+    return false;
+  }
+}
+
+ffc_internal ffc_inline
+bool ffc_strncasecmp5(char *actual_mixedcase, char const *expected_lowercase, size_t char_width) {
+  uint64_t mask = 0;
+  uint64_t val1 = 0;
+  uint64_t val2 = 0;
+  if (char_width == 1) {
+    mask = 0x2020202020202020;
+    memcpy(&val1, actual_mixedcase, 5 * char_width);
+    memcpy(&val2, expected_lowercase, 5 * char_width);
+    val1 |= mask;
+    val2 |= mask;
+    return val1 == val2;
+  } else if (char_width == 2) {
+    mask = 0x0020002000200020;
+    memcpy(&val1, actual_mixedcase, 4 * char_width);
+    memcpy(&val2, expected_lowercase, 4 * char_width);
+    val1 |= mask;
+    if (val1 != val2) {
+      return false;
+    }
+    return (actual_mixedcase[4] | 32) == (expected_lowercase[4]);
+  } else if (char_width == 4) {
+    mask = 0x0000002000000020;
+    memcpy(&val1, actual_mixedcase, 2 * char_width);
+    memcpy(&val2, expected_lowercase, 2 * char_width);
+    val1 |= mask;
+    if (val1 != val2) {
+      return false;
+    }
+    memcpy(&val1, actual_mixedcase + 2, 2 * char_width);
+    memcpy(&val2, expected_lowercase + 2, 2 * char_width);
+    val1 |= mask;
+    if (val1 != val2) {
+      return false;
+    }
+    return (actual_mixedcase[4] | 32) == (expected_lowercase[4]);
+  }
+  return false;
+}
+
+/**
+ * Returns true if the floating-pointing rounding mode is to 'nearest'.
+ * It is the default on most system. This function is meant to be inexpensive.
+ * Credit : @mwalcott3
+ */
+
+ffc_internal ffc_inline
+bool ffc_rounds_to_nearest(void) {
+  // https://lemire.me/blog/2020/06/26/gcc-not-nearest/
+#if (FLT_EVAL_METHOD != 1) && (FLT_EVAL_METHOD != 0)
+  return false;
+#endif
+  // See
+  // A fast function to check your floating-point rounding mode
+  // https://lemire.me/blog/2022/11/16/a-fast-function-to-check-your-floating-point-rounding-mode/
+  //
+  // This function is meant to be equivalent to :
+  // prior: #include <cfenv>
+  //  return fegetround() == FE_TONEAREST;
+  // However, it is expected to be much faster than the fegetround()
+  // function call.
+  //
+  // The volatile keyword prevents the compiler from computing the function
+  // at compile-time.
+  // There might be other ways to prevent compile-time optimizations (e.g.,
+  // asm). The value does not need to be std::numeric_limits<float>::min(), any
+  // small value so that 1 + x should round to 1 would do (after accounting for
+  // excess precision, as in 387 instructions).
+  static float volatile fmin = FLT_MIN;
+  float fmini = fmin; // we copy it so that it gets loaded at most once.
+//
+// Explanation:
+// Only when fegetround() == FE_TONEAREST do we have that
+// fmin + 1.0f == 1.0f - fmin.
+//
+// FE_UPWARD:
+//  fmin + 1.0f > 1
+//  1.0f - fmin == 1
+//
+// FE_DOWNWARD or  FE_TOWARDZERO:
+//  fmin + 1.0f == 1
+//  1.0f - fmin < 1
+//
+// Note: This may fail to be accurate if fast-math has been
+// enabled, as rounding conventions may not apply.
+#ifdef FFC_VISUAL_STUDIO
+#pragma warning(push)
+//  todo: is there a VS warning?
+//  see
+//  https://stackoverflow.com/questions/46079446/is-there-a-warning-for-floating-point-equality-checking-in-visual-studio-2013
+#elif defined(__clang__)
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wfloat-equal"
+#elif defined(__GNUC__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wfloat-equal"
+#endif
+  return (fmini + 1.0f == 1.0f - fmini);
+#ifdef FFC_VISUAL_STUDIO
+#pragma warning(pop)
+#elif defined(__clang__)
+#pragma clang diagnostic pop
+#elif defined(__GNUC__)
+#pragma GCC diagnostic pop
+#endif
+}
+
+ffc_internal ffc_inline
+// packs up an adjusted_mantissa into a double
+void ffc_am_to_float(bool negative, ffc_adjusted_mantissa am, ffc_value* value, ffc_value_kind vk) {
+  if (vk == FFC_VALUE_KIND_FLOAT) {
+    uint32_t word = (uint32_t)am.mantissa;
+    word = word | (uint32_t)(am.power2) << ffc_const(vk, MANTISSA_EXPLICIT_BITS);
+    word = word | (uint32_t)(negative) << ffc_const(vk, SIGN_INDEX);
+    memcpy(value, &word, sizeof(uint32_t));
+  } else {
+    uint64_t word = am.mantissa;
+    word = word | (uint64_t)(am.power2) << ffc_const(vk, MANTISSA_EXPLICIT_BITS);
+    word = word | (uint64_t)(negative) << ffc_const(vk, SIGN_INDEX);
+    memcpy(value, &word, sizeof(uint64_t));
+  }
+}
+
+static const double FFC_DOUBLE_POWERS_OF_TEN[] = {
+  1e0,  1e1,  1e2,  1e3,  1e4,  1e5,  1e6,  1e7,  1e8,  1e9,  1e10, 1e11,
+ 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, 1e22
+};
+
+static const float FFC_FLOAT_POWERS_OF_TEN[] = {
+  1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f,
+  1e6f, 1e7f, 1e8f, 1e9f, 1e10f
+};
+
+// Largest integer value v so that (5**index * v) <= 1<<53.
+// 0x20000000000000 == 1 << 53
+#define FFC_55555 (5LL * 5LL * 5LL * 5LL * 5LL)
+static const uint64_t FFC_DOUBLE_MAX_MANTISSA[] = {
+    (uint64_t)0x20000000000000L,
+    (uint64_t)0x20000000000000L / 5,
+    (uint64_t)0x20000000000000L / (5 * 5),
+    (uint64_t)0x20000000000000L / (5 * 5 * 5),
+    (uint64_t)0x20000000000000L / (5 * 5 * 5 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555),
+    (uint64_t)0x20000000000000L / (FFC_55555 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * 5 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * 5 * 5 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * 5 * 5 * 5 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * 5 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * 5 * 5 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * 5 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * 5 * 5 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * 5 * 5 * 5 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * FFC_55555),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * FFC_55555 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * FFC_55555 * 5 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * FFC_55555 * 5 * 5 * 5),
+    (uint64_t)0x20000000000000L / (FFC_55555 * FFC_55555 * FFC_55555 * FFC_55555 * 5 * 5 * 5 * 5)};
+
+// Largest integer value v so that (5**index * v) <= 1<<24.
+// 0x1000000 == 1<<24
+static const uint64_t FFC_FLOAT_MAX_MANTISSA[] = {
+    0x1000000,
+    0x1000000 / 5,
+    0x1000000 / (5 * 5),
+    0x1000000 / (5 * 5 * 5),
+    0x1000000 / (5 * 5 * 5 * 5),
+    0x1000000 / (FFC_55555),
+    0x1000000 / (FFC_55555 * 5),
+    0x1000000 / (FFC_55555 * 5 * 5),
+    0x1000000 / (FFC_55555 * 5 * 5 * 5),
+    0x1000000 / (FFC_55555 * 5 * 5 * 5 * 5),
+    0x1000000 / (FFC_55555 * FFC_55555),
+    0x1000000 / (FFC_55555 * FFC_55555 * 5)};
+
+#ifndef FFC_ASSERT
+#define FFC_ASSERT(x)                                                    \
+  { ((void)(x)); }
+#endif
+
+#ifndef FFC_DEBUG_ASSERT
+#define FFC_DEBUG_ASSERT(x)                                              \
+  { ((void)(x)); }
+#endif
+
+#endif // FFC_COMMON_H
+
+#ifndef FFC_PARSE_H
+#define FFC_PARSE_H
+
+#include <math.h>
+
+/* section: read digits */
+
+ffc_internal ffc_inline
+uint64_t ffc_byteswap(uint64_t val) {
+  return (val & 0xFF00000000000000) >> 56 | (val & 0x00FF000000000000) >> 40 |
+         (val & 0x0000FF0000000000) >> 24 | (val & 0x000000FF00000000) >> 8  |
+         (val & 0x00000000FF000000) <<  8 | (val & 0x0000000000FF0000) << 24 |
+         (val & 0x000000000000FF00) << 40 | (val & 0x00000000000000FF) << 56;
+}
+
+ffc_internal ffc_inline
+uint32_t ffc_byteswap_32(uint32_t val) {
+  return (val >> 24) | ((val >> 8) & 0x0000FF00u) | ((val << 8) & 0x00FF0000u) |
+         (val << 24);
+}
+
+ffc_inline ffc_internal
+uint64_t ffc_read8_to_u64(char const *chars) {
+  uint64_t val;
+  memcpy(&val, chars, sizeof(uint64_t));
+#if FFC_IS_BIG_ENDIAN == 1
+  // Need to read as-if the number was in little-endian order.
+  val = ffc_byteswap(val);
+#endif
+  return val;
+}
+
+// Read 4 UC into a u32. Truncates UC if not char.
+ffc_internal ffc_inline uint32_t
+ffc_read4_to_u32(char const *chars) {
+  uint32_t val;
+  memcpy(&val, chars, sizeof(uint32_t));
+#if FFC_IS_BIG_ENDIAN == 1
+  val = ffc_byteswap_32(val);
+#endif
+  return val;
+}
+
+#ifdef FFC_SSE2
+
+ffc_internal ffc_inline
+uint64_t ffc_simd_read8_to_u64_simdreg(__m128i const data) {
+  FFC_SIMD_DISABLE_WARNINGS
+  __m128i const packed = _mm_packus_epi16(data, data);
+#ifdef FFC_64BIT
+  return (uint64_t)_mm_cvtsi128_si64(packed);
+#else
+  uint64_t value;
+  // Visual Studio + older versions of GCC don't support _mm_storeu_si64
+  _mm_storel_epi64((__m128i*)&value, packed);
+  return value;
+#endif
+  FFC_SIMD_RESTORE_WARNINGS
+}
+
+ffc_internal ffc_inline
+uint64_t ffc_simd_read8_to_u64(uint16_t const *chars) {
+  FFC_SIMD_DISABLE_WARNINGS
+  return ffc_simd_read8_to_u64_simdreg(
+      _mm_loadu_si128((const __m128i*)chars));
+  FFC_SIMD_RESTORE_WARNINGS
+}
+
+#elif defined(FFC_NEON)
+
+ffc_internal ffc_inline
+uint64_t ffc_simd_read8_to_u64_simdreg(uint16x8_t const data) {
+  FFC_SIMD_DISABLE_WARNINGS
+  uint8x8_t utf8_packed = vmovn_u16(data);
+  return vget_lane_u64(vreinterpret_u64_u8(utf8_packed), 0);
+  FFC_SIMD_RESTORE_WARNINGS
+}
+
+ffc_internal ffc_inline
+uint64_t ffc_simd_read8_to_u64(uint16_t const *chars) {
+  FFC_SIMD_DISABLE_WARNINGS
+  return ffc_simd_read8_to_u64_simdreg(vld1q_u16(chars));
+  FFC_SIMD_RESTORE_WARNINGS
+}
+
+#endif // FFC_SSE2
+
+// credit  @aqrit
+ffc_internal ffc_inline uint32_t
+ffc_parse_eight_digits_unrolled_swar(uint64_t val) {
+  uint64_t const mask = 0x000000FF000000FF;
+  uint64_t const mul1 = 0x000F424000000064; // 100 + (1000000ULL << 32)
+  uint64_t const mul2 = 0x0000271000000001; // 1 + (10000ULL << 32)
+  val -= 0x3030303030303030;
+  val = (val * 10) + (val >> 8); // val = (val * 2561) >> 8;
+  val = (((val & mask) * mul1) + (((val >> 16) & mask) * mul2)) >> 32;
+  return (uint32_t)val;
+}
+
+// Call this if chars are definitely 8 digits.
+ffc_internal ffc_inline
+uint32_t ffc_parse_eight_digits_unrolled(char const *chars) {
+  return ffc_parse_eight_digits_unrolled_swar(ffc_read8_to_u64(chars)); // truncation okay
+}
+
+// credit @aqrit
+ffc_internal ffc_inline bool
+ffc_is_made_of_eight_digits_fast(uint64_t val) {
+  return !((((val + 0x4646464646464646) | (val - 0x3030303030303030)) &
+            0x8080808080808080));
+}
+
+ffc_internal ffc_inline bool
+ffc_is_made_of_four_digits_fast(uint32_t val) {
+  return !((((val + 0x46464646) | (val - 0x30303030)) & 0x80808080));
+}
+
+ffc_internal ffc_inline
+uint32_t ffc_parse_four_digits_unrolled(uint32_t val) {
+  val -= 0x30303030;
+  val = (val * 10) + (val >> 8);
+  return (((val & 0x00FF00FF) * 0x00640001) >> 16) & 0xFFFF;
+}
+
+#ifdef FFC_HAS_SIMD
+
+// Call this if chars might not be 8 digits.
+// Using this style (instead of is_made_of_eight_digits_fast() then
+// parse_eight_digits_unrolled()) ensures we don't load SIMD registers twice.
+ffc_internal ffc_inline
+bool ffc_simd_parse_if_eight_digits_unrolled_simd(uint16_t const *chars, uint64_t* i) {
+#ifdef FFC_SSE2
+  FFC_SIMD_DISABLE_WARNINGS
+  __m128i const data =
+      _mm_loadu_si128((__m128i const *)chars);
+
+  // (x - '0') <= 9
+  // http://0x80.pl/articles/simd-parsing-int-sequences.html
+  __m128i const t0 = _mm_add_epi16(data, _mm_set1_epi16(32720));
+  __m128i const t1 = _mm_cmpgt_epi16(t0, _mm_set1_epi16(-32759));
+
+  if (_mm_movemask_epi8(t1) == 0) {
+    *i = *i * 100000000 + ffc_parse_eight_digits_unrolled_swar(ffc_simd_read8_to_u64_simdreg(data));
+    return true;
+  } else
+    return false;
+  FFC_SIMD_RESTORE_WARNINGS
+#elif defined(FFC_NEON)
+  FFC_SIMD_DISABLE_WARNINGS
+  uint16x8_t const data = vld1q_u16(chars);
+
+  // (x - '0') <= 9
+  // http://0x80.pl/articles/simd-parsing-int-sequences.html
+  uint16x8_t const t0 = vsubq_u16(data, vmovq_n_u16('0'));
+  uint16x8_t const mask = vcltq_u16(t0, vmovq_n_u16('9' - '0' + 1));
+
+  if (vminvq_u16(mask) == 0xFFFF) {
+    *i = *i * 100000000 + ffc_parse_eight_digits_unrolled_swar(ffc_simd_read8_to_u64_simdreg(data));
+    return true;
+  } else
+    return false;
+  FFC_SIMD_RESTORE_WARNINGS
+#else
+  (void)chars;
+  (void)i;
+  return false;
+#endif // FFC_SSE2
+}
+
+#endif // FFC_HAS_SIMD
+
+ffc_internal ffc_inline void
+ffc_loop_parse_if_eight_digits(char const **p, char const *const pend,
+                           uint64_t* i) {
+  // optimizes better than parse_if_eight_digits_unrolled() for char.
+  while ((pend - *p >= 8) &&
+         ffc_is_made_of_eight_digits_fast(ffc_read8_to_u64(*p))) {
+    *i = (*i * 100000000) +
+        ffc_parse_eight_digits_unrolled_swar(ffc_read8_to_u64(*p)); 
+        // in rare cases, this will overflow, but that's ok
+    *p += 8;
+  }
+}
+
+/* section end: read digits */
+
+/* section: parse */
+
+ffc_parse_options ffc_parse_options_default(void) {
+  ffc_parse_options options;
+  options.format = FFC_PRESET_GENERAL;
+  options.decimal_point = '.';
+  return options;
+}
+
+typedef struct ffc_parsed {
+  int64_t  exponent;
+  uint64_t mantissa;
+  /* Populated on error; indicates where parsing failed */
+  char     const *lastmatch;
+  bool     negative;
+  bool     valid;
+  bool     too_many_digits;
+  char*    int_part_start;
+  size_t   int_part_len;
+  char*    fraction_part_start;
+  size_t   fraction_part_len;
+
+  ffc_parse_outcome outcome;
+} ffc_parsed;
+
+ffc_internal ffc_inline
+ffc_parsed ffc_report_parse_error(char const *p, ffc_parse_outcome outcome) {
+  ffc_parsed answer;
+  answer.valid = false;
+  answer.lastmatch = p;
+  answer.outcome = outcome;
+  return answer;
+}
+
+ffc_internal ffc_inline
+ffc_parsed ffc_parse_number_string(
+    char const *p,
+    // CONTRACT: p < pend
+    char const *pend,
+    ffc_parse_options const options,
+    // explicitly passed to encourage optimizer to specialize
+    bool const basic_json_fmt
+) {
+  ffc_format fmt = options.format;
+  char decimal_point = options.decimal_point;
+
+  FFC_DEBUG_ASSERT(fmt != 0);
+
+  ffc_parsed answer = {0};
+  answer.negative = (*p == '-');
+  // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
+  // so we only allow it if we've been told to, and are not in json mode
+  bool allow_leading_plus = fmt & FFC_FORMAT_FLAG_ALLOW_LEADING_PLUS;
+  if ((*p == '-') || (uint64_t)(allow_leading_plus && !basic_json_fmt && *p == '+')) {
+    ++p;
+    if (p == pend) {
+      return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_MISSING_INTEGER_OR_DOT_AFTER_SIGN);
+    }
+    if (basic_json_fmt) {
+      if (!ffc_is_integer(*p)) { // a sign must be followed by an integer
+        return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_JSON_MISSING_INTEGER_AFTER_SIGN);
+      }
+    } else {
+      // a sign must be followed by an integer or the dot
+      if (!ffc_is_integer(*p) && (*p != decimal_point)) { 
+        return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_MISSING_INTEGER_OR_DOT_AFTER_SIGN);
+      }
+    }
+  }
+
+  // phew, we've found the digits
+  char const *const start_digits = p;
+
+  uint64_t i = 0; // an unsigned int avoids signed overflows (which are bad)
+
+  while ((p != pend) && ffc_is_integer(*p)) {
+    // Horner's method: only ever multiplies by the constant 10
+    // avoiding variable power-of-10 multiplies
+
+    // might overflow, we will handle the overflow later
+    uint64_t digit_value = (uint64_t)(*p - '0');
+    i = (10 * i) + digit_value; 
+    ++p;
+  }
+
+  char const *const end_of_integer_part = p;
+
+  int64_t digit_count = (int64_t)(end_of_integer_part - start_digits);
+  answer.int_part_start = (char*)start_digits;
+  answer.int_part_len = (size_t)(digit_count);
+
+  if (basic_json_fmt) {
+    // at least 1 digit in integer part
+    if (digit_count == 0) {
+      return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_JSON_NO_DIGITS_IN_INTEGER_PART);
+    }
+    // no leading zeros
+    if ((start_digits[0] == '0' && digit_count > 1)) {
+      return ffc_report_parse_error(start_digits, FFC_PARSE_OUTCOME_JSON_LEADING_ZEROS_IN_INTEGER_PART);
+    }
+  }
+
+  int64_t exponent = 0;
+  bool const has_decimal_point = (p != pend) && (*p == decimal_point);
+
+  /* post-decimal exponential part (calculates a negative exponent) */
+  if (has_decimal_point) {
+    ++p;
+    char const *before = p; 
+    // can occur at most twice without overflowing, but let it occur more, since
+    // for integers with many digits, digit parsing is the primary bottleneck.
+    ffc_loop_parse_if_eight_digits(&p, pend, &i);
+
+    while ((p != pend) && ffc_is_integer(*p)) {
+      uint8_t digit = (uint8_t)(*p - (char)('0'));
+      ++p;
+      i = i * 10 + digit; // in rare cases, this will overflow, but that's ok
+    }
+
+    // pre: i = 123, digit_count = 3
+    // 123.456
+    //        ^ pend
+    //        ^ p
+    //     ^ before
+    // exponent = -3
+    // i = 123456
+    // digit_count = 3 - (-3) = 6
+    exponent = before - p;
+    answer.fraction_part_start = (char*)before;
+    answer.fraction_part_len = (size_t)(p - before);
+    digit_count -= exponent;
+  }
+  if (basic_json_fmt) {
+    // at least 1 digit in fractional part
+    if (has_decimal_point && exponent == 0) {
+      return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_JSON_NO_DIGITS_IN_FRACTIONAL_PART);
+    }
+  } else if (digit_count == 0) { // we must have encountered at least one integer!
+    return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_NO_DIGITS_IN_MANTISSA);
+  }
+
+  /* explicit exponential part */
+  int64_t exp_number = 0; 
+  if (((uint64_t)(fmt & FFC_FORMAT_FLAG_SCIENTIFIC) && (p != pend) &&
+       (('e' == *p) || ('E' == *p))) ||
+      ((uint64_t)(fmt & FFC_FORMAT_FLAG_BASIC_FORTRAN) && (p != pend) &&
+       (('+' == *p) || ('-' == *p) || ('d' == *p) ||
+        ('D' == *p)))) {
+    char const *location_of_e = p;
+    if (('e' == *p) || ('E' == *p) || ('d' == *p) ||
+        ('D' == *p)) {
+      ++p;
+    }
+    bool neg_exp = false;
+    if ((p != pend) && ('-' == *p)) {
+      neg_exp = true;
+      ++p;
+    } else if ((p != pend) && ('+' == *p)) { // '+' on exponent is allowed by C++17 20.19.3.(7.1)
+      ++p;
+    }
+    if ((p == pend) || !ffc_is_integer(*p)) {
+      if (!(uint64_t)(fmt & FFC_FORMAT_FLAG_FIXED)) {
+        // The exponential part is invalid for scientific notation, so it must
+        // be a trailing token for fixed notation. However, fixed notation is
+        // disabled, so report a scientific notation error.
+        return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_MISSING_EXPONENTIAL_PART);
+      }
+      // Otherwise, we will be ignoring the 'e'.
+      p = location_of_e;
+    } else {
+      while ((p != pend) && ffc_is_integer(*p)) {
+        uint8_t digit = (uint8_t)(*p - '0');
+        if (exp_number < 0x10000000) {
+          exp_number = 10 * exp_number + digit;
+        }
+        ++p;
+      }
+      if (neg_exp) {
+        exp_number = -exp_number;
+      }
+      exponent += exp_number;
+    }
+  } else {
+    // If it scientific and not fixed, we have to bail out.
+    if ((uint64_t)(fmt & FFC_FORMAT_FLAG_SCIENTIFIC) &&
+        !(uint64_t)(fmt & FFC_FORMAT_FLAG_FIXED)) {
+      return ffc_report_parse_error(p, FFC_PARSE_OUTCOME_MISSING_EXPONENTIAL_PART);
+    }
+  }
+  answer.lastmatch = p;
+  answer.valid = true;
+
+  // If we frequently had to deal with long strings of digits,
+  // we could extend our code by using a 128-bit integer instead
+  // of a 64-bit integer. However, this is uncommon.
+  //
+  // We can deal with up to 19 digits.
+  if (digit_count > 19) { // this is uncommon
+    ffc_debug("high digit_count %lld\n", digit_count);
+    // It is possible that the integer had an overflow.
+    // We have to handle the case where we have 0.0000somenumber.
+    // We need to be mindful of the case where we only have zeroes...
+    // E.g., 0.000000000...000.
+    char const *start = start_digits;
+    while ((start != pend) && (*start == '0' || *start == decimal_point)) {
+      if (*start == '0') {
+        digit_count--;
+      }
+      start++;
+    }
+
+    if (digit_count > 19) {
+      answer.too_many_digits = true;
+      // Let us start again, this time, avoiding overflows.
+      // We don't need to call if is_integer, since we use the
+      // pre-tokenized spans from above.
+      i = 0;
+      p = answer.int_part_start;
+      char const *int_end = p + answer.int_part_len;
+      uint64_t const minimal_nineteen_digit_integer = 1000000000000000000;
+      while ((i < minimal_nineteen_digit_integer) && (p != int_end)) {
+        i = i * 10 + (uint64_t)(*p - '0');
+        ++p;
+      }
+      if (i >= minimal_nineteen_digit_integer) { // We have a big integer
+        exponent = end_of_integer_part - p + exp_number;
+      } else { // We have a value with a fractional component.
+        p = answer.fraction_part_start;
+        char const *frac_end = p + answer.fraction_part_len;
+        while ((i < minimal_nineteen_digit_integer) && (p != frac_end)) {
+          i = i * 10 + (uint64_t)(*p - '0');
+          ++p;
+        }
+        exponent = answer.fraction_part_start - p + exp_number;
+      }
+      // We have now corrected both exponent and i, to a truncated value
+    }
+  }
+  answer.exponent = exponent;
+  answer.mantissa = i;
+  return answer;
+}
+
+/**
+ * Special case +inf, -inf, nan, infinity, -infinity.
+ * The case comparisons could be made much faster given that we know that the
+ * strings a null-free and fixed.
+ **/
+ffc_internal ffc_inline
+ffc_result ffc_parse_infnan(
+    char *first, char *last,
+    ffc_value *value,
+    ffc_value_kind vk,
+    ffc_format fmt
+) {
+  ffc_debug("parse_infnan\n");
+  ffc_result answer;
+  answer.ptr = first;
+  answer.outcome = FFC_OUTCOME_OK; // be optimistic
+  // assume first < last, so dereference without checks;
+  bool const minus_sign = (*first == '-');
+  // C++17 20.19.3.(7.1) explicitly forbids '+' sign here
+  if ((*first == '-') ||
+      ((uint64_t)(fmt & FFC_FORMAT_FLAG_ALLOW_LEADING_PLUS) &&
+       (*first == '+'))) {
+    ++first;
+  }
+  if (last - first >= 3) {
+    if (ffc_strncasecmp3(first, "nan", 1)) {
+      answer.ptr = (first += 3);
+
+      // The macro casts the literal AFTER applying the negative sign. This is ok:
+      // -NAN is a double negative NaN, and (float)(-NAN) correctly produces a negative float NaN.
+      // Same for infinity — (float)(-INFINITY) gives negative float infinity
+      ffc_set_value(value, vk, minus_sign ? -NAN : NAN);
+      // Check for possible nan(n-char-seq-opt), C++17 20.19.3.7,
+      // C11 7.20.1.3.3. At least MSVC produces nan(ind) and nan(snan).
+      if (first != last && *first == '(') {
+        for (char *ptr = first + 1; ptr != last; ++ptr) {
+          if (*ptr == ')') {
+            answer.ptr = ptr + 1; // valid nan(n-char-seq-opt)
+            break;
+          } else if (!(('a' <= *ptr && *ptr <= 'z') ||
+                       ('A' <= *ptr && *ptr <= 'Z') ||
+                       ('0' <= *ptr && *ptr <= '9') || *ptr == '_'))
+            break; // forbidden char, not nan(n-char-seq-opt)
+        }
+      }
+      return answer;
+    }
+    if (ffc_strncasecmp3(first, "infinity", 1)) {
+      if ((last - first >= 8) &&
+          ffc_strncasecmp5(first + 3, (char*)&"infinity"[3], 1)) {
+        answer.ptr = first + 8;
+      } else {
+        answer.ptr = first + 3;
+      }
+      ffc_set_value(value, vk, minus_sign ? -INFINITY : INFINITY);
+      return answer;
+    }
+  }
+  answer.outcome = FFC_OUTCOME_INVALID_INPUT;
+  return answer;
+}
+
+ffc_internal ffc_inline
+ffc_result ffc_parse_int_string(
+    char const *p,
+    char const *pend,
+    ffc_int_value *value,
+    ffc_int_kind ik,
+    ffc_parse_options options,
+    int const base
+  ) {
+  ffc_debug("input '%.*s'... ", (int)(pend - p), p);
+  ffc_format const fmt = options.format;
+
+  if ((uint64_t)(fmt & FFC_FORMAT_FLAG_SKIP_WHITE_SPACE)) {
+    while ((p != pend) && ffc_is_space(*p)) {
+      p++;
+    }
+  }
+
+  if (p == pend || base < 2 || base > 36) {
+    ffc_result invalid_input_result;
+    invalid_input_result.ptr = (char*)p;
+    invalid_input_result.outcome = FFC_OUTCOME_INVALID_INPUT;
+    return invalid_input_result;
+  }
+
+  ffc_result answer;
+  char const *const first = p;
+
+  bool const negative = (*p == (char)('-'));
+
+  if (!ffc_int_kind_is_signed(ik) && negative) {
+    answer.outcome = FFC_OUTCOME_INVALID_INPUT;
+    answer.ptr = (char*)first;
+    return answer;
+  }
+  if ((*p == (char)('-')) ||
+      ((uint64_t)(fmt & FFC_FORMAT_FLAG_ALLOW_LEADING_PLUS) && (*p == (char)('+')))) {
+    ++p;
+  }
+
+  char const *const start_num = p;
+
+  while (p != pend && *p == (char)('0')) {
+    ++p;
+  }
+
+  bool const has_leading_zeros = p > start_num;
+
+  char const *const start_digits = p;
+
+  uint64_t i = 0;
+  if (base == 10) {
+    ffc_loop_parse_if_eight_digits(&p, pend, &i); // use SIMD if possible
+  }
+  while (p != pend) {
+    uint8_t digit = ffc_char_to_digit(*p);
+    if (digit >= base) {
+      break;
+    }
+    i = (uint64_t)(base) * i + digit; // might overflow, check this later
+    p++;
+  }
+
+  size_t digit_count = (size_t)(p - start_digits);
+
+  if (digit_count == 0) {
+    if (has_leading_zeros) {
+      value->u64 = 0; // Must zero the largest variant!
+      answer.outcome = FFC_OUTCOME_OK;
+      answer.ptr = p;
+    } else {
+      answer.outcome = FFC_OUTCOME_INVALID_INPUT;
+      answer.ptr = first;
+    }
+    return answer;
+  }
+
+  answer.ptr = p;
+
+  // check u64 overflow
+  size_t max_digits = ffc_max_digits_u64(base);
+  ffc_debug("digit_count %d, max_digits: %d\n", digit_count, max_digits);
+  if (digit_count > max_digits) {
+    answer.outcome = FFC_OUTCOME_OUT_OF_RANGE;
+    return answer;
+  }
+  // this check can be eliminated for all other types, but they will all require
+  // a max_digits(base) equivalent
+  if (digit_count == max_digits && i < ffc_min_safe_u64_of_base(base)) {
+    answer.outcome = FFC_OUTCOME_OUT_OF_RANGE;
+    return answer;
+  }
+
+  ffc_debug("i is %lld, ik is %s\n", i, (ik == FFC_INT_KIND_U64 ? "u64" :
+                                         ik == FFC_INT_KIND_S64 ? "s64" :
+                                         ik == FFC_INT_KIND_U32 ? "u32" :
+                                         ik == FFC_INT_KIND_S32 ? "s32" : "?"));
+  // check other types overflow
+  if (ik != FFC_INT_KIND_U64) {
+    // Allow 1 greater magnitude when negative
+    if (i > ffc_int_value_max(ik) + (uint64_t)(negative)) {
+      answer.outcome = FFC_OUTCOME_OUT_OF_RANGE;
+      return answer;
+    }
+  }
+
+  // All signed conversion goes through unsigned arithmetic to avoid UB
+  if (negative) {
+    uint64_t neg_i = ~i + 1; // This is the two's complement negation
+    // write into the signed slot via union
+    switch (ik) {
+      case FFC_INT_KIND_S64: value->s64 = (int64_t)neg_i; break; // implementation-defined, but works everywhere
+      case FFC_INT_KIND_S32: value->s32 = (int32_t)(int64_t)neg_i; break;
+      // unsigned kinds can't be negative — guarded by the range check above
+      // case FFC_INT_KIND_S16: value.i16 = (int16_t)(int64_t)neg_i; break;
+      // case FFC_INT_KIND_S8:  value.i8  = (int8_t)(int64_t)neg_i;  break;
+    }
+  } else {
+    switch (ik) {
+      case FFC_INT_KIND_S64: value->s64 = (int64_t)i;          break; 
+      case FFC_INT_KIND_S32: value->s32 = (int32_t)(int64_t)i; break;
+      case FFC_INT_KIND_U64: value->u64 = i;                   break;
+      case FFC_INT_KIND_U32: value->u32 = (uint32_t)i;         break;
+    }
+  }
+
+  answer.outcome = FFC_OUTCOME_OK;
+  return answer;
+}
+
+#ifdef FFC_DEBUG 
+
+#include <stdio.h>
+ffc_internal ffc_inline
+void ffc_dump_parsed(ffc_parsed const p) {
+  (void)p;
+  ffc_debug("mantissa: %llu\n", (unsigned long long)p.mantissa);
+  ffc_debug("exponent: %lld\n", (long long)p.exponent);
+  ffc_debug("negative: %d\n", p.negative);
+  ffc_debug("valid: %d\n", p.valid);
+  ffc_debug("too_many_digits: %d\n", p.too_many_digits);
+  ffc_debug("int_part_len: %zu\n", p.int_part_len);
+  ffc_debug("fraction_part_len: %zu\n", p.fraction_part_len);
+}
+
+#endif
+
+/* section end: parse */
+
+#endif // FFC_PARSE_H
+#ifndef FFC_DIGIT_COMPARISON_H
+#define FFC_DIGIT_COMPARISON_H
+
+#ifndef FFC_BIGINT_H
+#define FFC_BIGINT_H
+
+
+// rust style `try!()` macro, or `?` operator
+#define FFC_TRY(x)                                                       \
+  {                                                                      \
+    if (!(x))                                                            \
+      return false;                                                      \
+  }
+
+// the limb width: we want efficient multiplication of double the bits in
+// limb, or for 64-bit limbs, at least 64-bit multiplication where we can
+// extract the high and low parts efficiently. this is every 64-bit
+// architecture except for sparc, which emulates 128-bit multiplication.
+// we might have platforms where `CHAR_BIT` is not 8, so let's avoid
+// doing `8 * sizeof(limb)`.
+#if defined(FFC_64BIT) && !defined(__sparc)
+#define FFC_64BIT_LIMB 1
+typedef uint64_t ffc_bigint_limb;
+#define FFC_LIMB_BITS 64
+#else
+#define FFC_32BIT_LIMB
+typedef uint32_t ffc_bigint_limb;
+#define FFC_LIMB_BITS 32
+#endif
+
+typedef struct { ffc_bigint_limb* ptr; size_t len; } ffc_bigint_limb_span;
+
+ffc_internal ffc_inline
+ffc_bigint_limb ffc_limb_span_index(ffc_bigint_limb_span limb_span, size_t index) {
+  FFC_DEBUG_ASSERT(index < limb_span.len);
+  return limb_span.ptr[index];
+}
+#define ffc_span_index(span, index) ffc_limb_span_index(span, index)
+
+// number of bits in a bigint. this needs to be at least the number
+// of bits required to store the largest bigint, which is
+// `log2(10**(digits + max_exp))`, or `log2(10**(767 + 342))`, or
+// ~3600 bits, so we round to 4000.
+#define FFC_BIGINT_BITS 4000
+
+// vector-like type that is allocated on the stack. the entire
+// buffer is pre-allocated, and only the length changes.
+
+// SV_LIMB_COUNT should be 125 or 32-bit systems or 62 for 64-bit systems
+#define SV_LIMB_COUNT FFC_BIGINT_BITS / FFC_LIMB_BITS
+
+typedef struct ffc_sv {
+  ffc_bigint_limb data[SV_LIMB_COUNT];
+  // we never need more than 150 limbs
+  uint16_t len;
+} ffc_sv;
+
+// add items to the vector, from a span, without bounds checking
+ffc_internal ffc_inline
+void ffc_sv_extend_unchecked(ffc_sv* sv, ffc_bigint_limb_span s) {
+  ffc_bigint_limb *ptr = sv->data + sv->len;
+
+  size_t s_bytes = s.len * sizeof(ffc_bigint_limb);
+  memcpy(ptr, s.ptr, s_bytes);
+  sv->len += s.len;
+}
+
+// try to add items to the vector, returning if items were added
+ffc_internal ffc_inline
+bool ffc_sv_try_extend(ffc_sv* sv, ffc_bigint_limb_span s) {
+  if (sv->len + s.len <= SV_LIMB_COUNT) {
+    ffc_sv_extend_unchecked(sv, s);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+// create from existing limb span.
+ffc_internal ffc_inline
+ffc_sv ffc_sv_create(ffc_bigint_limb_span s) {
+  ffc_sv new_one;
+  new_one.len = 0;
+  ffc_sv_try_extend(&new_one, s);
+  return new_one;
+}
+
+ffc_internal ffc_inline
+ffc_bigint_limb ffc_sv_index(ffc_sv sv, size_t index) {
+  FFC_DEBUG_ASSERT(index < sv.len);
+  return sv.data[index];
+}
+
+// index from the end of the container
+ffc_internal ffc_inline
+ffc_bigint_limb ffc_sv_rindex(ffc_sv sv, size_t index) {
+  FFC_DEBUG_ASSERT(index < sv.len);
+  size_t rindex = sv.len - index - 1;
+  return sv.data[rindex];
+}
+
+// append item to vector, without bounds checking
+ffc_internal ffc_inline
+void ffc_sv_push_unchecked(ffc_sv* sv, ffc_bigint_limb value) {
+  sv->data[sv->len] = value;
+  sv->len++;
+}
+
+// append item to vector, returning if item was added
+ffc_internal ffc_inline
+bool ffc_sv_try_push(ffc_sv* sv, ffc_bigint_limb value) {
+  if (sv->len < SV_LIMB_COUNT) {
+    ffc_sv_push_unchecked(sv, value);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+// try to reserve new_len limbs, filling with limbs
+// FF_DIVERGE: We remove some extra helpers and simply fill with zeros
+ffc_internal ffc_inline
+bool ffc_sv_try_reserve(ffc_sv* sv, size_t new_len) {
+  if (new_len > SV_LIMB_COUNT) {
+    return false;
+  } else {
+    if (new_len > sv->len) {
+      size_t fill_count = new_len - sv->len;
+      ffc_bigint_limb *first = sv->data + sv->len;
+      ffc_bigint_limb *last = first + fill_count;
+      for (ffc_bigint_limb* p = first; p < last; p++) {
+        *p = 0;
+      }
+      sv->len = (uint16_t)new_len;
+    } else {
+      sv->len = (uint16_t)new_len;
+    }
+    return true;
+  }
+}
+
+// check if any limbs are non-zero after the given index.
+ffc_internal ffc_inline
+bool ffc_sv_exists_nonzero_after(ffc_sv sv, size_t index) {
+  while (index < sv.len) {
+    if (ffc_sv_rindex(sv, index) != 0) {
+      return true;
+    }
+    index++;
+  }
+  return false;
+}
+
+// normalize the big integer, so most-significant zero limbs are removed.
+ffc_internal ffc_inline
+void ffc_sv_normalize(ffc_sv* sv) {
+  while (sv->len > 0 && ffc_sv_rindex(*sv, 0) == 0) {
+    sv->len--;
+  }
+}
+
+ffc_internal ffc_inline
+uint64_t ffc_uint64_hi64_1(uint64_t r0, bool* truncated) {
+  FFC_DEBUG_ASSERT(r0 != 0);
+  *truncated = false;
+  int shl = (int)ffc_count_leading_zeroes(r0);
+  return r0 << shl;
+}
+
+ffc_internal ffc_inline
+uint64_t ffc_uint64_hi64_2(uint64_t r0, uint64_t r1, bool* truncated) {
+  FFC_DEBUG_ASSERT(r0 != 0);
+  int shl = (int)ffc_count_leading_zeroes(r0);
+  if (shl == 0) {
+    *truncated = r1 != 0;
+    return r0;
+  } else {
+    int shr = 64 - shl;
+    *truncated = (r1 << shl) != 0;
+    return (r0 << shl) | (r1 >> shr);
+  }
+}
+
+ffc_internal ffc_inline
+uint64_t ffc_uint32_hi64_1(uint32_t r0, bool* truncated) {
+  return ffc_uint64_hi64_1(r0, truncated);
+}
+
+ffc_internal ffc_inline
+uint64_t ffc_uint32_hi64_2(uint32_t r0, uint32_t r1, bool* truncated) {
+  uint64_t x0 = r0;
+  uint64_t x1 = r1;
+  return ffc_uint64_hi64_1((x0 << 32) | x1, truncated);
+}
+
+ffc_internal ffc_inline
+uint64_t ffc_uint32_hi64_3(uint32_t r0, uint32_t r1, uint32_t r2, bool* truncated) {
+  uint64_t x0 = r0;
+  uint64_t x1 = r1;
+  uint64_t x2 = r2;
+  return ffc_uint64_hi64_2(x0, (x1 << 32) | x2, truncated);
+}
+
+// add two small integers, checking for overflow.
+// we want an efficient operation. for msvc, where
+// we don't have built-in intrinsics, this is still
+// pretty fast.
+ffc_internal ffc_inline
+ffc_bigint_limb ffc_bigint_scalar_add(ffc_bigint_limb x, ffc_bigint_limb y, bool* overflow) {
+  ffc_bigint_limb z;
+// gcc and clang
+#if defined(__has_builtin)
+#if __has_builtin(__builtin_add_overflow)
+  *overflow = __builtin_add_overflow(x, y, &z);
+  return z;
+#endif
+#endif
+
+  // generic, this still optimizes correctly on MSVC.
+  z = x + y;
+  *overflow = z < x;
+  return z;
+}
+
+// multiply two small integers, getting both the high and low bits.
+ffc_inline ffc_internal
+ffc_bigint_limb ffc_bigint_scalar_mul(ffc_bigint_limb x, ffc_bigint_limb y, ffc_bigint_limb* carry) {
+#ifdef FFC_64BIT_LIMB
+#if defined(__SIZEOF_INT128__)
+  // GCC and clang both define it as an extension.
+  __uint128_t z = (__uint128_t)(x) * (__uint128_t)(y) + (__uint128_t)(*carry);
+  *carry = (ffc_bigint_limb)(z >> FFC_LIMB_BITS);
+  return (ffc_bigint_limb)(z);
+#else
+  // fallback, no native 128-bit integer multiplication with carry.
+  // on msvc, this optimizes identically, somehow.
+  ffc_u128 z = ffc_full_multiplication(x, y);
+  bool overflow;
+  z.low = ffc_bigint_scalar_add(z.low, *carry, &overflow);
+  z.high += (uint64_t)(overflow); // cannot overflow
+  *carry = z.high;
+  return z.low;
+#endif
+#else
+  uint64_t z = (uint64_t)(x) * (uint64_t)(y) + (uint64_t)(*carry);
+  *carry = (ffc_bigint_limb)(z >> FFC_LIMB_BITS);
+  return (ffc_bigint_limb)(z);
+#endif
+}
+
+// add scalar value to bigint starting from offset.
+// used in grade school multiplication
+ffc_internal ffc_inline
+bool ffc_bigint_small_add_from(ffc_sv* sv, ffc_bigint_limb y, size_t start) {
+  size_t index = start;
+  ffc_bigint_limb carry = y;
+  bool overflow;
+  while (carry != 0 && index < sv->len) {
+    sv->data[index] = ffc_bigint_scalar_add(sv->data[index], carry, &overflow);
+    carry = (ffc_bigint_limb)(overflow);
+    index += 1;
+  }
+  if (carry != 0) {
+    FFC_TRY(ffc_sv_try_push(sv, carry));
+  }
+  return true;
+}
+
+// add scalar value to bigint.
+ffc_internal ffc_inline
+bool ffc_bigint_small_add(ffc_sv* sv, ffc_bigint_limb y) {
+  return ffc_bigint_small_add_from(sv, y, 0);
+}
+
+// multiply bigint by scalar value.
+ffc_internal
+bool ffc_bigint_small_mul(ffc_sv* sv, ffc_bigint_limb y) {
+  ffc_bigint_limb carry = 0;
+  for (size_t index = 0; index < sv->len; index++) {
+    sv->data[index] = ffc_bigint_scalar_mul(sv->data[index], y, &carry);
+  }
+  if (carry != 0) {
+    FFC_TRY(ffc_sv_try_push(sv, carry));
+  }
+  return true;
+}
+
+// add bigint to bigint starting from index.
+// used in grade school multiplication
+ffc_internal
+bool ffc_bigint_large_add_from(ffc_sv* x, ffc_bigint_limb_span y, size_t start) {
+  // the effective x buffer is from `xstart..x.len()`, so exit early
+  // if we can't get that current range.
+
+  // FFC_DIVERGE: We are calling our try_reserve instead of the o.g. try_resize
+  if (x->len < start || y.len > x->len - start) {
+    FFC_TRY(ffc_sv_try_reserve(x, y.len + start));
+  }
+
+  bool carry = false;
+  for (size_t index = 0; index < y.len; index++) {
+    ffc_bigint_limb xi = x->data[index + start];
+    ffc_bigint_limb yi = ffc_span_index(y, index);
+    bool c1 = false;
+    bool c2 = false;
+    xi = ffc_bigint_scalar_add(xi, yi, &c1);
+    if (carry) {
+      xi = ffc_bigint_scalar_add(xi, 1, &c2);
+    }
+    x->data[index + start] = xi;
+    carry = c1 | c2;
+  }
+
+  // handle overflow
+  if (carry) {
+    FFC_TRY(ffc_bigint_small_add_from(x, 1, y.len + start));
+  }
+  return true;
+}
+
+// add bigint to bigint.
+ffc_internal ffc_inline
+bool ffc_sv_large_add_from_zero(ffc_sv* x, ffc_bigint_limb_span y) {
+  return ffc_bigint_large_add_from(x, y, 0);
+}
+
+// grade-school multiplication algorithm
+ffc_internal
+bool ffc_bigint_long_mul(ffc_sv* x, ffc_bigint_limb_span y) {
+  ffc_bigint_limb_span xs;
+  xs.ptr = x->data;
+  xs.len = x->len;
+
+  // full copy of x into z
+  ffc_sv z = ffc_sv_create(xs);
+
+  ffc_bigint_limb_span zs;
+  zs.ptr = z.data;
+  zs.len = z.len;
+
+  if (y.len != 0) {
+    ffc_bigint_limb y0 = ffc_span_index(y, 0);
+    FFC_TRY(ffc_bigint_small_mul(x, y0));
+    for (size_t index = 1; index < y.len; index++) {
+
+      ffc_bigint_limb yi = ffc_span_index(y, index);
+      ffc_sv zi; // re-use the same buffer throughout
+
+      if (yi != 0) {
+        zi.len = 0;
+        FFC_TRY(ffc_sv_try_extend(&zi, zs));
+        FFC_TRY(ffc_bigint_small_mul(&zi, yi));
+        ffc_bigint_limb_span zis;
+        zis.ptr = zi.data;
+        zis.len = zi.len;
+        FFC_TRY(ffc_bigint_large_add_from(x, zis, index));
+      }
+    }
+  }
+
+  ffc_sv_normalize(x);
+  return true;
+}
+
+// grade-school multiplication algorithm
+ffc_internal ffc_inline
+bool ffc_bigint_large_mul(ffc_sv* x, ffc_bigint_limb_span y) {
+  if (y.len == 1) {
+    FFC_TRY(ffc_bigint_small_mul(x, ffc_span_index(y,0)));
+  } else {
+    FFC_TRY(ffc_bigint_long_mul(x, y));
+  }
+  return true;
+}
+
+static const uint32_t pow5_tables_large_step = 135;
+static const uint64_t pow5_tables_small_powers[] = {
+    1ULL,
+    5ULL,
+    25ULL,
+    125ULL,
+    625ULL,
+    3125ULL,
+    15625ULL,
+    78125ULL,
+    390625ULL,
+    1953125ULL,
+    9765625ULL,
+    48828125ULL,
+    244140625ULL,
+    1220703125ULL,
+    6103515625ULL,
+    30517578125ULL,
+    152587890625ULL,
+    762939453125ULL,
+    3814697265625ULL,
+    19073486328125ULL,
+    95367431640625ULL,
+    476837158203125ULL,
+    2384185791015625ULL,
+    11920928955078125ULL,
+    59604644775390625ULL,
+    298023223876953125ULL,
+    1490116119384765625ULL,
+    7450580596923828125ULL,
+};
+#ifdef FFC_64BIT_LIMB
+  static const ffc_bigint_limb ffc_large_power_of_5[] = {
+      1414648277510068013ULL, 9180637584431281687ULL, 4539964771860779200ULL,
+      10482974169319127550ULL, 198276706040285095ULL};
+#else
+  static const ffc_bigint_limb ffc_large_power_of_5[] = {
+      4279965485U, 329373468U,  4020270615U, 2137533757U, 4287402176U,
+      1057042919U, 1071430142U, 2440757623U, 381945767U,  46164893U};
+#endif
+
+// big integer type. implements a small subset of big integer
+// arithmetic, using simple algorithms since asymptotically
+// faster algorithms are slower for a small number of limbs.
+// all operations assume the big-integer is normalized.
+typedef struct ffc_bigint {
+  // storage of the limbs, in little-endian order.
+  ffc_sv vec;
+} ffc_bigint;
+
+ffc_inline ffc_internal
+ffc_bigint ffc_bigint_empty(void) {
+  ffc_sv sv;
+  sv.len = 0;
+
+  ffc_bigint sv_bigint;
+  sv_bigint.vec = sv;
+  return sv_bigint;
+}
+
+ffc_inline ffc_internal
+ffc_bigint ffc_bigint_make(uint64_t value) {
+  ffc_sv sv;
+  sv.len = 0;
+#ifdef FFC_64BIT_LIMB
+  ffc_sv_push_unchecked(&sv, value);
+#else
+  ffc_sv_push_unchecked(&sv, (uint32_t)(value));
+  ffc_sv_push_unchecked(&sv, (uint32_t)(value >> 32));
+#endif
+  ffc_sv_normalize(&sv);
+
+  ffc_bigint sv_bigint;
+  sv_bigint.vec = sv;
+  return sv_bigint;
+}
+
+// get the high 64 bits from the vector, and if bits were truncated.
+// this is to get the significant digits for the float.
+ffc_inline ffc_internal
+uint64_t ffc_bigint_hi64(ffc_bigint me, bool* truncated) {
+  ffc_sv vec = me.vec;
+#ifdef FFC_64BIT_LIMB
+  if (vec.len == 0) {
+    *truncated = false;
+    return 0;
+  } else if (vec.len == 1) {
+    return ffc_uint64_hi64_1(ffc_sv_rindex(vec,0), truncated);
+  } else {
+    uint64_t result = ffc_uint64_hi64_2(ffc_sv_rindex(vec, 0), ffc_sv_rindex(vec, 1), truncated);
+    *truncated |= ffc_sv_exists_nonzero_after(vec, 2);
+    return result;
+  }
+#else
+  if (vec.len == 0) {
+    *truncated = false;
+    return 0;
+  } else if (vec.len == 1) {
+    return ffc_uint32_hi64_1(ffc_sv_rindex(vec,0), truncated);
+  } else if (vec.len == 2) {
+    return ffc_uint32_hi64_2(ffc_sv_rindex(vec,0), ffc_sv_rindex(vec,1), truncated);
+  } else {
+    uint64_t result = ffc_uint32_hi64_3(
+        ffc_sv_rindex(vec,0), 
+        ffc_sv_rindex(vec,1), 
+        ffc_sv_rindex(vec,2),
+        truncated
+    );
+    *truncated |= ffc_sv_exists_nonzero_after(vec , 3);
+    return result;
+  }
+#endif
+}
+
+// compare two big integers, returning the large value.
+// assumes both are normalized. if the return value is
+// negative, other is larger, if the return value is
+// positive, this is larger, otherwise they are equal.
+// the limbs are stored in little-endian order, so we
+// must compare the limbs in ever order.
+ffc_internal ffc_inline 
+int ffc_bigint_compare(ffc_bigint me, ffc_bigint const *other) {
+  if (me.vec.len > other->vec.len) {
+    return 1;
+  } else if (me.vec.len < other->vec.len) {
+    return -1;
+  } else {
+    for (size_t index = me.vec.len; index > 0; index--) {
+      ffc_bigint_limb xi = ffc_sv_index(me.vec, index - 1);
+      ffc_bigint_limb yi = ffc_sv_index(other->vec, index - 1);
+      if (xi > yi) {
+        return 1;
+      } else if (xi < yi) {
+        return -1;
+      }
+    }
+    return 0;
+  }
+}
+
+// shift left each limb n bits, carrying over to the new limb
+// returns true if we were able to shift all the digits.
+ffc_internal ffc_inline 
+bool ffc_bigint_shl_bits(ffc_bigint* me, size_t n) {
+  // Internally, for each item, we shift left by n, and add the previous
+  // right shifted limb-bits.
+  // For example, we transform (for u8) shifted left 2, to:
+  //      b10100100 b01000010
+  //      b10 b10010001 b00001000
+  FFC_DEBUG_ASSERT(n != 0);
+  FFC_DEBUG_ASSERT(n < sizeof(ffc_bigint_limb) * 8);
+
+  size_t shl = n;
+  size_t shr = FFC_LIMB_BITS - shl;
+  ffc_bigint_limb prev = 0;
+  for (size_t index = 0; index < me->vec.len; index++) {
+    ffc_bigint_limb xi = ffc_sv_index(me->vec, index);
+    me->vec.data[index] = (xi << shl) | (prev >> shr);
+    prev = xi;
+  }
+
+  ffc_bigint_limb carry = prev >> shr;
+  if (carry != 0) {
+    return ffc_sv_try_push(&me->vec, carry);
+  }
+  return true;
+}
+
+// move the limbs left by `n` limbs.
+ffc_internal ffc_inline 
+bool ffc_bigint_shl_limbs(ffc_bigint* me, size_t n) {
+  FFC_DEBUG_ASSERT(n != 0);
+  if (n + me->vec.len > SV_LIMB_COUNT) {
+    return false;
+  } else if (me->vec.len != 0) {
+    // move limbs
+    ffc_bigint_limb *dst = me->vec.data + n;
+    ffc_bigint_limb const *src = me->vec.data;
+    // std::copy_backward(src, src + vec.len(), dst + vec.len());
+    // memmove to handle the overlap
+    memmove(dst, src, me->vec.len * sizeof(ffc_bigint_limb));
+    
+    // fill in empty limbs
+    ffc_bigint_limb *first = me->vec.data;
+    // ffc_bigint_limb *last = first + n;
+    // ::std::fill(first, last, 0);
+    memset(first, 0, n * sizeof(ffc_bigint_limb));
+    me->vec.len += n;
+    return true;
+  } else {
+    return true;
+  }
+}
+
+// move the limbs left by `n` bits.
+ffc_internal ffc_inline 
+bool ffc_bigint_shl(ffc_bigint* me, size_t n) {
+  size_t rem = n % FFC_LIMB_BITS;
+  size_t div = n / FFC_LIMB_BITS;
+  if (rem != 0) {
+    FFC_TRY(ffc_bigint_shl_bits(me, rem));
+  }
+  if (div != 0) {
+    FFC_TRY(ffc_bigint_shl_limbs(me, div));
+  }
+  return true;
+}
+
+// get the number of leading zeros in the bigint.
+ffc_internal ffc_inline 
+int ffc_bigint_ctlz(ffc_bigint me) {
+  if (me.vec.len == 0) {
+    return 0;
+  } else {
+#ifdef FFC_64BIT_LIMB
+    return (int)ffc_count_leading_zeroes(ffc_sv_rindex(me.vec, 0));
+#else
+    // no use defining a specialized count_leading_zeros for a 32-bit type.
+    uint64_t r0 = ffc_sv_rindex(me.vec, 0);
+    return ffc_count_leading_zeroes(r0 << 32);
+#endif
+  }
+}
+
+// get the number of bits in the bigint.
+ffc_internal ffc_inline 
+int ffc_bigint_bit_length(ffc_bigint me) {
+  int lz = ffc_bigint_ctlz(me);
+  return (int)(FFC_LIMB_BITS * me.vec.len) - lz;
+}
+
+ffc_internal ffc_inline 
+bool ffc_bigint_mul(ffc_bigint* me, ffc_bigint_limb y) { return ffc_bigint_small_mul(&me->vec, y); }
+
+ffc_internal ffc_inline 
+bool ffc_bigint_add(ffc_bigint* me, ffc_bigint_limb y) { return ffc_bigint_small_add(&me->vec, y); }
+
+// multiply as if by 2 raised to a power.
+ffc_internal ffc_inline 
+bool ffc_bigint_pow2(ffc_bigint* me, uint32_t exp) { return ffc_bigint_shl(me, exp); }
+
+// multiply as if by 5 raised to a power.
+ffc_internal ffc_inline 
+bool ffc_bigint_pow5(ffc_bigint* me, uint32_t exp) {
+  // multiply by a power of 5
+  size_t large_length = sizeof(ffc_large_power_of_5) / sizeof(ffc_bigint_limb);
+
+  ffc_bigint_limb_span large;
+  large.ptr = (ffc_bigint_limb*)ffc_large_power_of_5;
+  large.len = large_length;
+
+  while (exp >= pow5_tables_large_step) {
+    FFC_TRY(ffc_bigint_large_mul(&me->vec, large));
+    exp -= pow5_tables_large_step;
+  }
+#ifdef FFC_64BIT_LIMB
+  uint32_t small_step = 27;
+  ffc_bigint_limb max_native = 7450580596923828125UL;
+#else
+  uint32_t small_step = 13;
+  ffc_bigint_limb max_native = 1220703125U;
+#endif
+  while (exp >= small_step) {
+    FFC_TRY(ffc_bigint_small_mul(&me->vec, max_native));
+    exp -= small_step;
+  }
+  if (exp != 0) {
+    FFC_TRY(
+      ffc_bigint_small_mul(&me->vec, (ffc_bigint_limb)(pow5_tables_small_powers[exp]))
+    );
+  }
+
+  return true;
+}
+
+// multiply as if by 10 raised to a power.
+ffc_internal ffc_inline 
+bool ffc_bigint_pow10(ffc_bigint* me, uint32_t exp) {
+  FFC_TRY(ffc_bigint_pow5(me, exp));
+  return ffc_bigint_pow2(me, exp);
+}
+
+#undef ffc_span_index
+#undef sv_rindex
+
+#endif // FFC_BIGINT_H
+
+// 1e0 to 1e19
+static const uint64_t ffc_powers_of_ten_uint64[] = {1UL,
+                                                    10UL,
+                                                    100UL,
+                                                    1000UL,
+                                                    10000UL,
+                                                    100000UL,
+                                                    1000000UL,
+                                                    10000000UL,
+                                                    100000000UL,
+                                                    1000000000UL,
+                                                    10000000000UL,
+                                                    100000000000UL,
+                                                    1000000000000UL,
+                                                    10000000000000UL,
+                                                    100000000000000UL,
+                                                    1000000000000000UL,
+                                                    10000000000000000UL,
+                                                    100000000000000000UL,
+                                                    1000000000000000000UL,
+                                                    10000000000000000000UL};
+
+// calculate the exponent, in scientific notation, of the number.
+// this algorithm is not even close to optimized, but it has no practical
+// effect on performance: in order to have a faster algorithm, we'd need
+// to slow down performance for faster algorithms, and this is still fast.
+ffc_inline ffc_internal int32_t
+ffc_scientific_exponent(uint64_t mantissa, int32_t exponent) {
+  while (mantissa >= 10000) {
+    mantissa /= 10000;
+    exponent += 4;
+  }
+  while (mantissa >= 100) {
+    mantissa /= 100;
+    exponent += 2;
+  }
+  while (mantissa >= 10) {
+    mantissa /= 10;
+    exponent += 1;
+  }
+  return exponent;
+}
+
+// this converts a native floating-point number to an extended-precision float.
+ffc_internal ffc_inline ffc_adjusted_mantissa
+ffc_to_extended(ffc_value value, ffc_value_kind vk) {
+  uint64_t const exponent_mask = ffc_const(vk, EXPONENT_MASK);
+  uint64_t const mantissa_mask = ffc_const(vk, MANTISSA_MASK);
+  uint64_t const hidden_bit_mask = ffc_const(vk, HIDDEN_BIT_MASK);
+
+  // Finish converting this one
+  ffc_adjusted_mantissa am;
+  int32_t bias = ffc_const(vk, MANTISSA_EXPLICIT_BITS) - ffc_const(vk, MINIMUM_EXPONENT);
+  //ffc_int_value bits = ffc_get_value_bits(value, vk);
+
+  switch (vk) {
+  case FFC_VALUE_KIND_DOUBLE: {
+    uint64_t bits = ffc_get_double_bits(value.d);
+    if ((bits & exponent_mask) == 0) {
+      // denormal
+      am.power2 = 1 - bias;
+      am.mantissa = bits & mantissa_mask;
+    } else {
+      // normal
+      am.power2 = (int32_t)((bits & exponent_mask) >> ffc_const(vk, MANTISSA_EXPLICIT_BITS));
+      am.power2 -= bias;
+      am.mantissa = (bits & mantissa_mask) | hidden_bit_mask;
+    }   
+    break;
+  }
+  case FFC_VALUE_KIND_FLOAT: {
+    uint32_t bits = ffc_get_float_bits(value.f);
+    if ((bits & exponent_mask) == 0) {
+      // denormal
+      am.power2 = 1 - bias;
+      am.mantissa = bits & mantissa_mask;
+    } else {
+      // normal
+      am.power2 = (int32_t)((bits & exponent_mask) >> ffc_const(vk, MANTISSA_EXPLICIT_BITS));
+      am.power2 -= bias;
+      am.mantissa = (bits & mantissa_mask) | hidden_bit_mask;
+    }   
+    break;
+  }
+  }
+
+  return am;
+}
+
+// get the extended precision value of the halfway point between b and b+u.
+// we are given a native float that represents b, so we need to adjust it
+// halfway between b and b+u.
+ffc_internal ffc_inline
+ffc_adjusted_mantissa ffc_to_extended_halfway(ffc_value value, ffc_value_kind vk) {
+  ffc_adjusted_mantissa am = ffc_to_extended(value, vk);
+  am.mantissa <<= 1;
+  am.mantissa += 1;
+  am.power2 -= 1;
+  return am;
+}
+
+ffc_internal ffc_inline
+void ffc_round_down_impl(ffc_adjusted_mantissa* am, int32_t shift) {
+  if (shift == 64) {
+    am->mantissa = 0;
+  } else {
+    am->mantissa >>= shift;
+  }
+  am->power2 += shift;
+}
+
+// round nearest tie even, resolving ties using the truncated flag
+ffc_internal ffc_inline
+void ffc_round_nearest_tie_even_truncated(ffc_adjusted_mantissa *am,
+                                             int32_t shift, bool truncated) {
+  uint64_t const mask = (shift == 64) ? UINT64_MAX : ((uint64_t)(1) << shift) - 1;
+  uint64_t const halfway = (shift == 0) ? 0 : (uint64_t)(1) << (shift - 1);
+  uint64_t truncated_bits = am->mantissa & mask;
+  bool is_above = truncated_bits > halfway;
+  bool is_halfway = truncated_bits == halfway;
+
+  // shift digits into position
+  if (shift == 64) {
+    am->mantissa = 0;
+  } else {
+    am->mantissa >>= shift;
+  }
+  am->power2 += shift;
+
+  bool is_odd = (am->mantissa & 1) == 1;
+  am->mantissa += (uint64_t)(is_above || (is_halfway && truncated) ||
+                             (is_odd && is_halfway));
+}
+
+// round nearest tie even, resolving ties using a precomputed comparison result
+ffc_internal ffc_inline
+void ffc_round_nearest_tie_even_cmp(ffc_adjusted_mantissa *am,
+                                       int32_t shift, int ord) {
+  // shift digits into position
+  if (shift == 64) {
+    am->mantissa = 0;
+  } else {
+    am->mantissa >>= shift;
+  }
+  am->power2 += shift;
+
+  bool is_odd = (am->mantissa & 1) == 1;
+  am->mantissa += (uint64_t)(ord > 0 || (ord == 0 && is_odd));
+}
+
+// Finalize rounding for a double after the shift callback has been applied.
+// This is the common tail of ffc_round_double factored out to avoid
+// duplicating the carry/infinite checks at every call site.
+ffc_internal ffc_inline
+void ffc_round_finish(ffc_adjusted_mantissa *am, ffc_value_kind vk) {
+  // check for carry
+  if (am->mantissa >= ((uint64_t)(2) << ffc_const(vk, MANTISSA_EXPLICIT_BITS))) {
+    am->mantissa = ((uint64_t)(1) << ffc_const(vk, MANTISSA_EXPLICIT_BITS));
+    am->power2++;
+  }
+
+  // check for infinite: we could have carried to an infinite power
+  am->mantissa &= ~((uint64_t)(1) << ffc_const(vk, MANTISSA_EXPLICIT_BITS));
+  if (am->power2 >= ffc_const(vk, INFINITE_POWER)) {
+    am->power2 = ffc_const(vk, INFINITE_POWER);
+    am->mantissa = 0;
+  }
+}
+
+// Common structure for round_double variants
+#define ffc_round_core(sub_routine_call, vk) \
+  do { \
+    int32_t mantissa_shift = 64 - ffc_const(vk, MANTISSA_EXPLICIT_BITS) - 1; \
+    if (-am->power2 >= mantissa_shift) { \
+      int32_t _shift = -am->power2 + 1; \
+      if (_shift > 64) _shift = 64; \
+      sub_routine_call; \
+      am->power2 = (am->mantissa < ((uint64_t)(1) << ffc_const(vk, MANTISSA_EXPLICIT_BITS))) \
+                      ? 0 \
+                      : 1; \
+      return; \
+    } \
+    { int32_t _shift = mantissa_shift; sub_routine_call; } \
+    ffc_round_finish(am, vk); \
+  } while (0)
+
+// round down variant of round_double
+ffc_internal ffc_inline
+void ffc_round_down(ffc_adjusted_mantissa *am, ffc_value_kind vk) {
+  ffc_round_core(ffc_round_down_impl(am, _shift), vk);
+}
+
+// tie-even (truncated) variant of round_double
+ffc_internal ffc_inline
+void ffc_round_tie_even_truncated(ffc_adjusted_mantissa *am,
+                                            bool truncated, ffc_value_kind vk) {
+  ffc_round_core(ffc_round_nearest_tie_even_truncated(am, _shift, truncated), vk);
+}
+
+// tie-even (comparison) variant of round_double
+ffc_internal ffc_inline
+void ffc_round_double_tie_even_cmp(ffc_adjusted_mantissa *am, int ord, ffc_value_kind vk) {
+  ffc_round_core(ffc_round_nearest_tie_even_cmp(am, _shift, ord), vk);
+}
+
+/* 1-byte chars (char, uint8_t) */
+#define FFC_INT_CMP_ZEROS_1 0x3030303030303030ULL
+#define FFC_INT_CMP_LEN_1   8
+
+/* 2-byte chars (uint16_t, wchar_t on Windows) */
+#define FFC_INT_CMP_ZEROS_2 0x0030003000300030ULL
+#define FFC_INT_CMP_LEN_2   4
+
+/* 4-byte chars (uint32_t, wchar_t on Linux) */
+#define FFC_INT_CMP_ZEROS_4 0x0000003000000030ULL
+#define FFC_INT_CMP_LEN_4   2
+
+ffc_internal ffc_inline
+bool ffc_char_eq_zero(char const *p, size_t char_width) {
+  switch (char_width) {
+    case 1: return *p == '0';
+    case 2: return *(uint16_t const *)p == 0x0030;
+    case 4: return *(uint32_t const *)p == 0x00000030;
+    default: return false;
+  }
+}
+
+ffc_internal ffc_inline
+void ffc_skip_zeros(char **first, char *last, size_t char_width) {
+  size_t cmp_len;
+  uint64_t cmp_mask;
+  switch (char_width) {
+    case 1:
+      cmp_len = FFC_INT_CMP_LEN_1;
+      cmp_mask = FFC_INT_CMP_ZEROS_1;
+      break;
+    case 2:
+      cmp_len = FFC_INT_CMP_LEN_2;
+      cmp_mask = FFC_INT_CMP_ZEROS_2;
+      break;
+    case 4:
+      cmp_len = FFC_INT_CMP_LEN_4;
+      cmp_mask = FFC_INT_CMP_ZEROS_4;
+      break;
+    default:
+      FFC_DEBUG_ASSERT(0);
+      return;
+  }
+
+  uint64_t val;
+  while ((size_t)(last - *first) >= (cmp_len * char_width)) {
+    memcpy(&val, *first, sizeof(uint64_t));
+    if (val != cmp_mask) {
+      break;
+    }
+    *first += cmp_len * char_width;
+  }
+  while (*first != last) {
+    if (!ffc_char_eq_zero(*first, char_width)) {
+      break;
+    }
+    *first += char_width;
+  }
+}
+
+// determine if any non-zero digits were truncated.
+// all characters must be valid digits.
+ffc_internal ffc_inline
+bool ffc_is_truncated(char const *first, char const *last, size_t char_width) {
+  size_t cmp_len;
+  uint64_t cmp_mask;
+  switch (char_width) {
+    case 1:
+      cmp_len = FFC_INT_CMP_LEN_1;
+      cmp_mask = FFC_INT_CMP_ZEROS_1;
+      break;
+    case 2:
+      cmp_len = FFC_INT_CMP_LEN_2;
+      cmp_mask = FFC_INT_CMP_ZEROS_2;
+      break;
+    case 4:
+      cmp_len = FFC_INT_CMP_LEN_4;
+      cmp_mask = FFC_INT_CMP_ZEROS_4;
+      break;
+    default:
+      FFC_DEBUG_ASSERT(0);
+      return 0;
+  }
+  // do 8-bit optimizations, can just compare to 8 literal 0s.
+  uint64_t val;
+  while ((size_t)(last - first) >= cmp_len) {
+    memcpy(&val, first, sizeof(uint64_t));
+    if (val != cmp_mask) {
+      return true;
+    }
+    first += cmp_len * char_width;
+  }
+  while (first != last) {
+    if (!ffc_char_eq_zero(first, char_width)) {
+      return true;
+    }
+    first += char_width;
+  }
+  return false;
+}
+
+ffc_internal ffc_inline
+void ffc_parse_eight_digits(char **p, ffc_bigint_limb *value, size_t *counter, size_t *count) {
+  *value = *value * 100000000 + ffc_parse_eight_digits_unrolled(*p);
+  *p += 8;
+  *counter += 8;
+  *count += 8;
+}
+
+ffc_internal ffc_inline
+void ffc_parse_one_digit(char **p, ffc_bigint_limb *value, size_t *counter, size_t *count) {
+  *value = *value * 10 + (ffc_bigint_limb)(**p - '0');
+  *p += 1;
+  *counter += 1;
+  *count += 1;
+}
+
+ffc_internal ffc_inline
+void ffc_add_native(ffc_bigint *big, ffc_bigint_limb power, ffc_bigint_limb value) {
+  ffc_bigint_mul(big, power);
+  ffc_bigint_add(big, value);
+}
+
+ffc_internal ffc_inline
+void ffc_round_up_bigint(ffc_bigint *big, size_t *count) {
+  // need to round-up the digits, but need to avoid rounding
+  // ....9999 to ...10000, which could cause a false halfway point.
+  ffc_add_native(big, 10, 1);
+  (*count)++;
+}
+
+// parse the significant digits into a big integer
+ffc_internal ffc_inline
+void ffc_parse_mantissa(ffc_bigint *result, ffc_parsed num,
+               size_t max_digits, size_t *const digits) {
+  // try to minimize the number of big integer and scalar multiplication.
+  // therefore, try to parse 8 digits at a time, and multiply by the largest
+  // scalar value (9 or 19 digits) for each step.
+  size_t counter = 0;
+  *digits = 0;
+  ffc_bigint_limb value = 0;
+#ifdef FFC_64BIT_LIMB
+  size_t step = 19;
+#else
+  size_t step = 9;
+#endif
+
+  // process all integer digits.
+  char *p = num.int_part_start;
+  char *pend = p + num.int_part_len;
+  ffc_skip_zeros(&p, pend, 1);
+  // process all digits, in increments of step per loop
+  while (p != pend) {
+    while ((pend - p >= 8) && (step - counter >= 8) &&
+           (max_digits - *digits >= 8)) {
+      ffc_parse_eight_digits(&p, &value, &counter, digits);
+    }
+    while (counter < step && p != pend && *digits < max_digits) {
+      ffc_parse_one_digit(&p, &value, &counter, digits);
+    }
+    if (*digits == max_digits) {
+      // add the temporary value, then check if we've truncated any digits
+      ffc_add_native(result, (ffc_bigint_limb)(ffc_powers_of_ten_uint64[counter]), value);
+      bool truncated = ffc_is_truncated(p, pend, 1);
+      if (num.fraction_part_start != NULL) {
+        truncated |= ffc_is_truncated(num.fraction_part_start, num.fraction_part_start + num.fraction_part_len, 1);
+      }
+      if (truncated) {
+        ffc_round_up_bigint(result, digits);
+      }
+      return;
+    } else {
+      ffc_add_native(result, (ffc_bigint_limb)(ffc_powers_of_ten_uint64[counter]), value);
+      counter = 0;
+      value = 0;
+    }
+  }
+
+  // add our fraction digits, if they're available.
+  if (num.fraction_part_start != NULL) {
+    p = num.fraction_part_start;
+    pend = p + num.fraction_part_len;
+    if (*digits == 0) {
+      ffc_skip_zeros(&p, pend, 1);
+    }
+    // process all digits, in increments of step per loop
+    while (p != pend) {
+      while ((pend - p >= 8) && (step - counter >= 8) &&
+             (max_digits - *digits >= 8)) {
+        ffc_parse_eight_digits(&p, &value, &counter, digits);
+      }
+      while (counter < step && p != pend && *digits < max_digits) {
+        ffc_parse_one_digit(&p, &value, &counter, digits);
+      }
+      if (*digits == max_digits) {
+        // add the temporary value, then check if we've truncated any digits
+        ffc_add_native(result, (ffc_bigint_limb)(ffc_powers_of_ten_uint64[counter]), value);
+        bool truncated = ffc_is_truncated(p, pend, 1);
+        if (truncated) {
+          ffc_round_up_bigint(result, digits);
+        }
+        return;
+      } else {
+        ffc_add_native(result, (ffc_bigint_limb)(ffc_powers_of_ten_uint64[counter]), value);
+        counter = 0;
+        value = 0;
+      }
+    }
+  }
+
+  if (counter != 0) {
+    ffc_add_native(result, (ffc_bigint_limb)(ffc_powers_of_ten_uint64[counter]), value);
+  }
+}
+
+ffc_internal ffc_inline
+ffc_adjusted_mantissa ffc_positive_digit_comp(ffc_bigint *bigmant, int32_t exponent, ffc_value_kind vk) {
+  for (int i = 0; i < bigmant->vec.len; i++) {
+    ffc_debug("limb %d: %lld\n", i, bigmant->vec.data[i]);
+  }
+  FFC_ASSERT(ffc_bigint_pow10(bigmant, (uint32_t)(exponent)));
+  ffc_adjusted_mantissa answer;
+  bool truncated;
+  answer.mantissa = ffc_bigint_hi64(*bigmant, &truncated);
+  int bias = ffc_const(vk, MANTISSA_EXPLICIT_BITS) - ffc_const(vk, MINIMUM_EXPONENT);
+  answer.power2 = ffc_bigint_bit_length(*bigmant) - 64 + bias;
+
+  ffc_round_tie_even_truncated(&answer, truncated, vk);
+
+  return answer;
+}
+
+// the scaling here is quite simple: we have, for the real digits `m * 10^e`,
+// and for the theoretical digits `n * 2^f`. Since `e` is always negative,
+// to scale them identically, we do `n * 2^f * 5^-f`, so we now have `m * 2^e`.
+// we then need to scale by `2^(f- e)`, and then the two significant digits
+// are of the same magnitude.
+ffc_internal ffc_inline
+ffc_adjusted_mantissa ffc_negative_digit_comp(
+    ffc_bigint *bigmant, ffc_adjusted_mantissa am, int32_t exponent, ffc_value_kind vk) {
+  ffc_bigint *real_digits = bigmant;
+  int32_t real_exp = exponent;
+
+  // get the value of `b`, rounded down, and get a bigint representation of b+h
+  ffc_adjusted_mantissa am_b = am;
+  ffc_round_down(&am_b, vk);
+  ffc_value b;
+  ffc_am_to_float(false, am_b, &b, FFC_VALUE_KIND_DOUBLE);
+
+  ffc_adjusted_mantissa theor = ffc_to_extended_halfway(b, vk);
+  ffc_bigint theor_digits = ffc_bigint_make(theor.mantissa);
+  int32_t theor_exp = theor.power2;
+
+  // scale real digits and theor digits to be same power.
+  int32_t pow2_exp = theor_exp - real_exp;
+  uint32_t pow5_exp = (uint32_t)(-real_exp);
+  if (pow5_exp != 0) {
+    FFC_ASSERT(ffc_bigint_pow5(&theor_digits, pow5_exp));
+  }
+  if (pow2_exp > 0) {
+    FFC_ASSERT(ffc_bigint_pow2(&theor_digits, (uint32_t)(pow2_exp)));
+  } else if (pow2_exp < 0) {
+    FFC_ASSERT(ffc_bigint_pow2(real_digits,(uint32_t)(-pow2_exp)));
+  }
+
+  // compare digits, and use it to direct rounding
+  int ord = ffc_bigint_compare(*real_digits, &theor_digits);
+  ffc_adjusted_mantissa answer = am;
+  ffc_round_double_tie_even_cmp(&answer, ord, vk);
+
+  return answer;
+}
+
+// parse the significant digits as a big integer to unambiguously round
+// the significant digits. here, we are trying to determine how to round
+// an extended float representation close to `b+h`, halfway between `b`
+// (the float rounded-down) and `b+u`, the next positive float. this
+// algorithm is always correct, and uses one of two approaches. when
+// the exponent is positive relative to the significant digits (such as
+// 1234), we create a big-integer representation, get the high 64-bits,
+// determine if any lower bits are truncated, and use that to direct
+// rounding. in case of a negative exponent relative to the significant
+// digits (such as 1.2345), we create a theoretical representation of
+// `b` as a big-integer type, scaled to the same binary exponent as
+// the actual digits. we then compare the big integer representations
+// of both, and use that to direct rounding.
+ffc_internal
+ffc_adjusted_mantissa ffc_digit_comp(ffc_parsed num, ffc_adjusted_mantissa am, ffc_value_kind vk) {
+  ffc_debug("digit_comp\n");
+  // remove the invalid exponent bias
+  am.power2 -= FFC_INVALID_AM_BIAS;
+
+  int32_t sci_exp =
+      ffc_scientific_exponent(num.mantissa, (int32_t)(num.exponent));
+  size_t max_digits = ffc_const(vk, MAX_DIGITS);
+  size_t digits = 0;
+  ffc_bigint bigmant = ffc_bigint_empty();
+  ffc_parse_mantissa(&bigmant, num, max_digits, &digits);
+  // can't underflow, since digits is at most max_digits.
+  int32_t exponent = sci_exp + 1 - (int32_t)(digits);
+  if (exponent >= 0) {
+    return ffc_positive_digit_comp(&bigmant, exponent, vk);
+  } else {
+    return ffc_negative_digit_comp(&bigmant, am, exponent, vk);
+  }
+}
+
+#endif // FFC_DIGIT_COMPARISON_H
+
+/* section: decimal to binary */
+
+ffc_inline ffc_internal
+ffc_u128 ffc_compute_product_approximation(int64_t q, uint64_t w, ffc_value_kind vk) {
+  // The required precision is mantissa_explicit_bits + 3 because
+  // 1. We need the implicit bit
+  // 2. We need an extra bit for rounding purposes
+  // 3. We might lose a bit due to the "upperbit" routine (result too small,
+  // requiring a shift)
+  uint64_t bit_precision = ffc_const(vk, MANTISSA_EXPLICIT_BITS) + 3;
+  uint64_t precision_mask = ((uint64_t)(0xFFFFFFFFFFFFFFFF) >> bit_precision);
+
+  // Use FFC_DOUBLE_SMALLEST_POWER_OF_10 (-342) regardless of value kind
+  // to index into the shared 128 bit table
+  int const index = 2 * (int)(q - FFC_DOUBLE_SMALLEST_POWER_OF_10);
+
+  // For small values of q, e.g., q in [0,27], the answer is always exact
+  // because ffc_mul_u64(w, powers_of_five[index]) gives the exact answer.
+  ffc_u128 firstproduct = ffc_mul_u64(w, FFC_POWERS_OF_FIVE[index]);
+                           
+  if ((firstproduct.high & precision_mask) == precision_mask) {
+    // could further guard with  (lower + w < lower)
+    // regarding the second product, we only need secondproduct.hi, but our
+    // expectation is that the compiler will optimize this extra work away if
+    // needed.
+    ffc_u128 secondproduct = ffc_mul_u64(w, FFC_POWERS_OF_FIVE[index + 1]);
+
+    firstproduct.low += secondproduct.high;
+    if (secondproduct.high > firstproduct.low) {
+      firstproduct.high++;
+    }
+  }
+  return firstproduct;
+}
+
+/**
+ * For q in (0,350), we have that
+ *  f = (((152170 + 65536) * q ) >> 16);
+ * is equal to
+ *   floor(p) + q
+ * where
+ *   p = log(5**q)/log(2) = q * log(5)/log(2)
+ *
+ * For negative values of q in (-400,0), we have that
+ *  f = (((152170 + 65536) * q ) >> 16);
+ * is equal to
+ *   -ceil(p) + q
+ * where
+ *   p = log(5**-q)/log(2) = -q * log(5)/log(2)
+
+ * FF_DIVERGE: renamed from detail::power to b10_to_b2
+ */
+ffc_internal ffc_inline int32_t ffc_b10_to_b2(int32_t q) {
+  return (((152170 + 65536) * q) >> 16) + 63;
+}
+
+// Computes w * 10 ** q.
+// The returned value should be a valid number that simply needs to be
+// packed. However, in some very rare cases, the computation will fail. In such
+// cases, we return an adjusted_mantissa with a negative power of 2: the caller
+// should recompute in such cases.
+ffc_inline ffc_internal
+ffc_adjusted_mantissa ffc_compute_float(int64_t q, uint64_t w, ffc_value_kind vk) {
+  ffc_adjusted_mantissa answer;
+  if ((w == 0) || (q < ffc_const(vk, SMALLEST_POWER_OF_10))) {
+    answer.power2 = 0;
+    answer.mantissa = 0;
+    // result should be zero
+    return answer;
+  }
+  if (q > ffc_const(vk, LARGEST_POWER_OF_10)) {
+    // we want to get infinity:
+    answer.power2 = ffc_const(vk, INFINITE_POWER);
+    answer.mantissa = 0;
+    return answer;
+  }
+  // At this point in time q is in [powers::smallest_power_of_five,
+  // powers::largest_power_of_five].
+
+  // We want the most significant bit of i to be 1. Shift if needed.
+  int32_t lz = (int32_t)ffc_count_leading_zeroes(w);
+  w <<= lz;
+
+  ffc_u128 product = ffc_compute_product_approximation(q, w, vk);
+
+  // The computed 'product' is always sufficient.
+  // Mathematical proof:
+  // Noble Mushtak and Daniel Lemire, Fast Number Parsing Without Fallback (to
+  // appear) See script/mushtak_lemire.py
+
+  // The "compute_product_approximation" function can be slightly slower than a
+  // branchless approach: value128 product = compute_product(q, w); but in
+  // practice, we can win big with the compute_product_approximation if its
+  // additional branch is easily predicted. Which is data specific.
+  int upperbit = (int)(product.high >> 63);
+  int shift = upperbit + 64 - ffc_const(vk, MANTISSA_EXPLICIT_BITS) - 3;
+
+  answer.mantissa = product.high >> shift;
+
+  // compute a biased-up power of 2
+  answer.power2 = (int32_t)(ffc_b10_to_b2((int32_t)(q)) + upperbit - lz - ffc_const(vk, MINIMUM_EXPONENT));
+
+  if (answer.power2 <= 0) { // subnormal path
+
+    if (-answer.power2 + 1 >= 64) {
+      // if we have more than 64 bits below the minimum exponent, you
+      // have a zero for sure.
+      answer.power2 = 0;
+      answer.mantissa = 0;
+      // result should be zero
+      return answer;
+    }
+    // shift is safe because -answer.power2 + 1 < 64
+    answer.mantissa >>= -answer.power2 + 1;
+
+    // Thankfully, we can't have both "round-to-even" and subnormals because
+    // "round-to-even" only occurs for powers close to 0 in the 32-bit and
+    // and 64-bit case (with no more than 19 digits), so we round up.
+    answer.mantissa += (answer.mantissa & 1); // round up
+    answer.mantissa >>= 1;
+
+    // weird scenario:
+    // Suppose we start with 2.2250738585072013e-308, we end up
+    // with 0x3fffffffffffff x 2^-1023-53 which is technically subnormal
+    // whereas 0x40000000000000 x 2^-1023-53  is normal. Now, we need to round
+    // up 0x3fffffffffffff x 2^-1023-53  and once we do, we are no longer
+    // subnormal, but we can only know this after rounding.
+    // So we only declare a subnormal if we are smaller than the threshold.
+    answer.power2 =
+      (answer.mantissa < ((uint64_t)(1) << ffc_const(vk, MANTISSA_EXPLICIT_BITS))) ? 0 : 1;
+    return answer;
+  } // subnormal
+
+  // usually, we round *up*, but if we fall right in between and we have an
+  // even basis, we need to round down
+  // We are only concerned with the cases where 5**q fits in single 64-bit word.
+
+  // 'extremely sparse low bits in our product' and
+  // 'q is within the round to even range' and
+  // 'mantissa lowest 2 bits are exactly 01'
+  if ((product.low <= 1) && (q >= ffc_const(vk, MIN_EXPONENT_ROUND_TO_EVEN)) &&
+      (q <= ffc_const(vk, MAX_EXPONENT_ROUND_TO_EVEN)) &&
+      ((answer.mantissa & 3) == 1)) { // we may fall between two floats!
+                                      //
+    // To be in-between two floats we need that in doing
+    //   answer.mantissa = product.high >> (upperbit + 64 -
+    //   binary::mantissa_explicit_bits() - 3);
+    // ... we dropped out only zeroes. But if this happened, then we can go
+    // back!!!
+
+    // mask off last bit
+    if ((answer.mantissa << shift) == product.high) {
+      answer.mantissa &= ~(uint64_t)(1);
+    }
+  }
+
+  answer.mantissa += (answer.mantissa & 1); // round up
+  answer.mantissa >>= 1;
+  if (answer.mantissa >= ((uint64_t)(2) << ffc_const(vk, MANTISSA_EXPLICIT_BITS))) {
+    answer.mantissa = ((uint64_t)(1) << ffc_const(vk, MANTISSA_EXPLICIT_BITS));
+    answer.power2++; // undo previous addition
+  }
+
+  // normalize to pos INF?
+  answer.mantissa &= ~((uint64_t)(1) << ffc_const(vk, MANTISSA_EXPLICIT_BITS));
+  if (answer.power2 >= ffc_const(vk, INFINITE_POWER)) { // infinity
+    answer.power2 = ffc_const(vk, INFINITE_POWER);
+    answer.mantissa = 0;
+  }
+  return answer;
+}
+
+// create an adjusted mantissa, biased by the invalid power2
+// for significant digits already multiplied by 10 ** q.
+ffc_internal ffc_inline
+ffc_adjusted_mantissa ffc_compute_error_scaled(int64_t q, uint64_t w, int lz, ffc_value_kind vk) {
+  int hilz = (int)(w >> 63) ^ 1;
+  ffc_adjusted_mantissa answer;
+  answer.mantissa = w << hilz;
+  int bias = ffc_const(vk, MANTISSA_EXPLICIT_BITS) - ffc_const(vk, MINIMUM_EXPONENT);
+  answer.power2 = (int32_t)(ffc_b10_to_b2((int32_t)(q)) + bias - hilz - lz - 62 +
+                          FFC_INVALID_AM_BIAS);
+  return answer;
+}
+
+// w * 10 ** q, without rounding the representation up.
+// the power2 in the exponent will be adjusted by invalid_am_bias.
+ffc_internal ffc_inline
+ffc_adjusted_mantissa ffc_compute_error(int64_t q, uint64_t w, ffc_value_kind vk) {
+  int32_t lz = (int32_t)ffc_count_leading_zeroes(w);
+  w <<= lz;
+  ffc_u128 product = ffc_compute_product_approximation(q, w, vk);
+  return ffc_compute_error_scaled(q, product.high, lz, vk);
+}
+
+/* end section: decimal to binary */
+
+/* section: entrypoint */
+
+ffc_internal ffc_inline
+bool ffc_clinger_fast_path_impl(uint64_t mantissa, int64_t exponent, bool is_negative,
+                       ffc_value *value, ffc_value_kind value_kind) {
+  bool is_double = value_kind == FFC_VALUE_KIND_DOUBLE;
+  // The implementation of the Clinger's fast path is convoluted because
+  // we want round-to-nearest in all cases, irrespective of the rounding mode
+  // selected on the thread.
+  // We proceed optimistically, assuming that detail::rounds_to_nearest()
+  // returns true.
+  if (ffc_const(value_kind, MIN_EXPONENT_FAST_PATH) <= exponent &&
+      exponent <= ffc_const(value_kind, MAX_EXPONENT_FAST_PATH)) {
+    // Unfortunately, the conventional Clinger's fast path is only possible
+    // when the system rounds to the nearest float.
+    //
+    // We expect the next branch to almost always be selected.
+    // We could check it first (before the previous branch), but
+    // there might be performance advantages at having the check
+    // be last.
+    if (ffc_rounds_to_nearest()) {
+      // We have that fegetround() == FE_TONEAREST.
+      // Next is Clinger's fast path.
+      if (mantissa <= ffc_const(value_kind, MAX_MANTISSA_FAST_PATH)) {
+        ffc_set_value(value, value_kind, mantissa);
+
+        if (exponent < 0) {
+          if (is_double) {
+            value->d = value->d / FFC_DOUBLE_POWERS_OF_TEN[-exponent];
+          } else {
+            value->f = value->f / FFC_FLOAT_POWERS_OF_TEN[-exponent];
+          };
+        } else {
+          if (is_double) {
+            value->d = value->d * FFC_DOUBLE_POWERS_OF_TEN[exponent];
+          } else {
+            value->f = value->f * FFC_FLOAT_POWERS_OF_TEN[exponent];
+          };
+        }
+        if (is_negative) {
+          ffc_set_value(value, value_kind, -ffc_read_value(value, value_kind));
+        }
+        return true;
+      }
+    } else {
+      // We do not have that fegetround() == FE_TONEAREST.
+      // Next is a modified Clinger's fast path, inspired by Jakub Jelínek's
+      // proposal
+      if (exponent >= 0 &&
+          mantissa <= ffc_const(value_kind, MAX_MANTISSA)[exponent]) {
+#if defined(__clang__) || defined(FFC_32BIT)
+        // Clang may map 0 to -0.0 when fegetround() == FE_DOWNWARD
+        if (mantissa == 0) {
+          ffc_set_value(value, value_kind, is_negative ? -0. : 0.);
+          return true;
+        }
+#endif
+        if (is_double) {
+          value->d = (double)mantissa * FFC_DOUBLE_POWERS_OF_TEN[exponent];
+        } else {
+          value->f = (float)mantissa * FFC_FLOAT_POWERS_OF_TEN[exponent];
+        }
+        if (is_negative) {
+          ffc_set_value(value, value_kind, -ffc_read_value(value, value_kind));
+        }
+        return true;
+      }
+    }
+  }
+  return false;
+}
+
+ffc_internal ffc_inline
+ffc_result ffc_from_chars_advanced(ffc_parsed const pns, ffc_value* value, ffc_value_kind vk) {
+  ffc_result answer;
+
+  answer.outcome = FFC_OUTCOME_OK; // be optimistic :')
+  answer.ptr = (char*)pns.lastmatch;
+
+  if (!pns.too_many_digits &&
+      ffc_clinger_fast_path_impl(pns.mantissa, pns.exponent, pns.negative, value, vk)) {
+    ffc_debug("fast path hit");
+    return answer;
+  }
+
+  ffc_adjusted_mantissa am = ffc_compute_float(pns.exponent, pns.mantissa, vk);
+  ffc_debug("am.mantissa: %llu\n", am.mantissa);
+  ffc_debug("am.power2:   %d\n", am.power2);
+  if (pns.too_many_digits && am.power2 >= 0) {
+    ffc_adjusted_mantissa am_plus_one = ffc_compute_float(pns.exponent, pns.mantissa + 1, vk);
+    bool equal = am.mantissa == am_plus_one.mantissa && am.power2 == am_plus_one.power2;
+    if (!equal) {
+      am = ffc_compute_error(pns.exponent, pns.mantissa, vk);
+    }
+  }
+  // If we called ffc_compute_float(pns.exponent, pns.mantissa)
+  // and we have an invalid power (am.power2 < 0), then we need to go the long
+  // way around again. This is very uncommon.
+  if (am.power2 < 0) {
+    am = ffc_digit_comp(pns, am, vk);
+  }
+  ffc_debug("am post mantissa: %llu\n", am.mantissa);
+  ffc_debug("am post power2:   %d\n", am.power2);
+  ffc_am_to_float(pns.negative, am, value, vk);
+
+  // Test for over/underflow.
+  if ((pns.mantissa != 0 && am.mantissa == 0 && am.power2 == 0) ||
+      am.power2 == ffc_const(vk, INFINITE_POWER)) {
+    answer.outcome = FFC_OUTCOME_OUT_OF_RANGE;
+  }
+  return answer;
+}
+
+ffc_internal ffc_inline
+ffc_result ffc_from_chars(char* first, char* last, ffc_parse_options options, ffc_value *value, ffc_value_kind vk) {
+
+  // Alias for parity with cpp code, no feature macros to apply
+  ffc_format const fmt = options.format;
+
+  ffc_result answer;
+  if ((uint64_t)(fmt & FFC_FORMAT_FLAG_SKIP_WHITE_SPACE)) {
+    while ((first != last) && ffc_is_space(*first)) {
+      first++;
+    }
+  }
+  if (first == last) {
+    answer.outcome = FFC_OUTCOME_INVALID_INPUT;
+    answer.ptr = first;
+    return answer;
+  }
+  uint64_t json_mode = (uint64_t)(fmt & FFC_FORMAT_FLAG_BASIC_JSON);
+  ffc_parsed pns = ffc_parse_number_string(first, last, options, json_mode);
+
+  #ifdef FFC_DEBUG
+  ffc_dump_parsed(pns);
+  #endif                          
+
+  if (!pns.valid) {
+    if ((uint64_t)(fmt & FFC_FORMAT_FLAG_NO_INFNAN)) {
+      answer.outcome = FFC_OUTCOME_INVALID_INPUT;
+      answer.ptr = first;
+      return answer;
+    } else {
+      return ffc_parse_infnan(first, last, value, vk, fmt);
+    }
+  }
+
+  // call overload that takes parsed_number_string directly.
+  return ffc_from_chars_advanced(pns, value, vk);
+}
+
+ffc_result ffc_from_chars_double_options(const char *start, const char *end, double* out, ffc_parse_options options) {
+  // It would be UB to directly use *out as our ffc_value, even though its the same layout
+  ffc_value out_value = {0};
+
+  // The all-important call with a constant VALUE_KIND that should cascade in tons of inlining
+  ffc_result result = ffc_from_chars((char*)start, (char*)end, options, &out_value, FFC_VALUE_KIND_DOUBLE);
+  *out = out_value.d;
+  return result;
+}
+ffc_result ffc_from_chars_double(char const* first, char const* last, double* out) {
+  ffc_parse_options options = ffc_parse_options_default();
+  return ffc_from_chars_double_options(first, last, out, options);
+}
+ffc_result ffc_parse_double(size_t len, const char *s, double *out) {
+  char *pend = (char*)(s + len);
+  return ffc_from_chars_double(s, pend, out);
+}
+double ffc_parse_double_simple(size_t len, const char *s, ffc_outcome *outcome) {
+  double out = 0.0;
+  ffc_result result = ffc_parse_double(len, s, &out);
+  if (outcome) {
+    *outcome = result.outcome;
+  }
+  return out;
+}
+
+ffc_result ffc_from_chars_float_options(const char *start,  const char *end, float* out, ffc_parse_options options) {
+  ffc_value out_value = {0};
+  ffc_result result = ffc_from_chars((char*)start, (char*)end, options, &out_value, FFC_VALUE_KIND_FLOAT);
+  *out = out_value.f;
+  return result;
+}
+ffc_result ffc_from_chars_float(char const* first, char const* last, float* out) {
+  ffc_parse_options options = ffc_parse_options_default();
+  return ffc_from_chars_float_options(first, last, out, options);
+}
+ffc_result ffc_parse_float(size_t len, const char *s, float *out) {
+  char *pend = (char*)(s + len);
+  return ffc_from_chars_float(s, pend, out);
+}
+float ffc_parse_float_simple(size_t len, const char *s, ffc_outcome *outcome) {
+  float out = 0.0;
+  ffc_result result = ffc_parse_float(len, s, &out);
+  if (outcome) {
+    *outcome = result.outcome;
+  }
+  return out;
+}
+
+ffc_result ffc_parse_i64(size_t len, const char *input, int base, int64_t  *out) {
+  char *pend = (char*)(input + len);
+  ffc_int_value value_out = {0};
+  ffc_result result = ffc_parse_int_string(input, pend, &value_out, FFC_INT_KIND_S64, ffc_parse_options_default(), base);
+  *out = value_out.s64;
+  return result;
+}
+ffc_result ffc_parse_u64(size_t len, const char *input, int base, uint64_t *out) {
+  char *pend = (char*)(input + len);
+  ffc_int_value value_out = {0};
+  ffc_result result = ffc_parse_int_string(input, pend, &value_out, FFC_INT_KIND_U64, ffc_parse_options_default(), base);
+  *out = value_out.u64;
+  return result;
+}
+ffc_result ffc_parse_i32(size_t len, const char *input, int base, int32_t  *out) {
+  char *pend = (char*)(input + len);
+  ffc_int_value value_out = {0};
+  ffc_result result = ffc_parse_int_string(input, pend, &value_out, FFC_INT_KIND_S32, ffc_parse_options_default(), base);
+  *out = value_out.s32;
+  return result;
+}
+ffc_result ffc_parse_u32(size_t len, const char *input, int base, uint32_t *out) {
+  char *pend = (char*)(input + len);
+  ffc_int_value value_out = {0};
+  ffc_result result = ffc_parse_int_string(input, pend, &value_out, FFC_INT_KIND_U32, ffc_parse_options_default(), base);
+  *out = value_out.u32;
+  return result;
+}
+
+int64_t  ffc_parse_i64_simple(size_t len, const char *input, int base, ffc_outcome *outcome) {
+  int64_t out = 0;
+  ffc_result result = ffc_parse_i64(len, input, base, &out);
+  if (outcome) {
+    *outcome = result.outcome;
+  }
+  return out;
+}
+uint64_t ffc_parse_u64_simple(size_t len, const char *input, int base, ffc_outcome *outcome) {
+  uint64_t out = 0;
+  ffc_result result = ffc_parse_u64(len, input, base, &out);
+  if (outcome) {
+    *outcome = result.outcome;
+  }
+  return out;
+}
+int32_t  ffc_parse_i32_simple(size_t len, const char *input, int base, ffc_outcome *outcome) {
+  int32_t out = 0;
+  ffc_result result = ffc_parse_i32(len, input, base, &out);
+  if (outcome) {
+    *outcome = result.outcome;
+  }
+  return out;
+}
+uint32_t ffc_parse_u32_simple(size_t len, const char *input, int base, ffc_outcome *outcome) {
+  uint32_t out = 0;
+  ffc_result result = ffc_parse_u32(len, input, base, &out);
+  if (outcome) {
+    *outcome = result.outcome;
+  }
+  return out;
+}
+
+#undef FFC_DOUBLE_SMALLEST_POWER_OF_10        
+#undef FFC_DOUBLE_LARGEST_POWER_OF_10         
+#undef FFC_DOUBLE_SIGN_INDEX                  
+#undef FFC_DOUBLE_INFINITE_POWER              
+#undef FFC_DOUBLE_MANTISSA_EXPLICIT_BITS      
+#undef FFC_DOUBLE_MINIMUM_EXPONENT            
+#undef FFC_DOUBLE_MIN_EXPONENT_ROUND_TO_EVEN  
+#undef FFC_DOUBLE_MAX_EXPONENT_ROUND_TO_EVEN  
+#undef FFC_DOUBLE_MAX_EXPONENT_FAST_PATH      
+#undef FFC_DOUBLE_MAX_MANTISSA_FAST_PATH      
+#undef FFC_DOUBLE_EXPONENT_MASK               
+#undef FFC_DOUBLE_MANTISSA_MASK               
+#undef FFC_DOUBLE_HIDDEN_BIT_MASK             
+#undef FFC_DOUBLE_MAX_DIGITS                  
+
+#undef FFC_FLOAT_SMALLEST_POWER_OF_10         
+#undef FFC_FLOAT_LARGEST_POWER_OF_10          
+#undef FFC_FLOAT_SIGN_INDEX                   
+#undef FFC_FLOAT_INFINITE_POWER               
+#undef FFC_FLOAT_MANTISSA_EXPLICIT_BITS       
+#undef FFC_FLOAT_MINIMUM_EXPONENT             
+#undef FFC_FLOAT_MIN_EXPONENT_ROUND_TO_EVEN   
+#undef FFC_FLOAT_MAX_EXPONENT_ROUND_TO_EVEN   
+#undef FFC_FLOAT_MAX_EXPONENT_FAST_PATH       
+#undef FFC_FLOAT_MAX_MANTISSA_FAST_PATH       
+#undef FFC_FLOAT_EXPONENT_MASK                
+#undef FFC_FLOAT_MANTISSA_MASK                
+#undef FFC_FLOAT_HIDDEN_BIT_MASK              
+#undef FFC_FLOAT_MAX_DIGITS                   
+
+#undef FFC_POWERS_OF_5_NUMBER_OF_ENTRIES
+
+#endif /* FFC_IMPL */
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* FFC_H */
+
diff --git a/c_src/jiffy.c b/c_src/jiffy.c
index 3083455..c44952a 100644
--- a/c_src/jiffy.c
+++ b/c_src/jiffy.c
@@ -5,6 +5,10 @@
 // a single translation unit so the compiler can optimize across file
 // boundaries without lto
 
+// Pull in ffc.h implementation once for the whole unity build.
+#define FFC_IMPL
+#include "ffc.h"
+
 #include "decoder.c"
 #include "encoder.c"
 #include "objects.c"


(couchdb-jiffy) 36/49: RFC 8259 Tests

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit a03cbf4d3c9591575645e96a07dd9bf0011b4da6
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Sat Apr 11 01:39:51 2026 -0400

    RFC 8259 Tests
    
    Add and run tests from ttps://github.com/nst/JSONTestSuite
    
    It turns out jiffy is a bit linient about accepting truncated exponents like
    `0.3e+`, where RFC 8259 would reject it we accept it as `0.3`. So, had to added
    those to a deviations list. Otherwise it looks pretty good and brings us to a
    total of about 780 tests.
    
    While adding the license for these from the author add the license for the "big
    naughty list" and Ryu as well, while removing the double conversion one.
---
 LICENSE                                            | 312 +++++++++++++++++++--
 .../i_number_double_huge_neg_exp.json              |   1 +
 test/cases/json_test_suite/i_number_huge_exp.json  |   1 +
 .../json_test_suite/i_number_neg_int_huge_exp.json |   1 +
 .../i_number_pos_double_huge_exp.json              |   1 +
 .../i_number_real_neg_overflow.json                |   1 +
 .../i_number_real_pos_overflow.json                |   1 +
 .../json_test_suite/i_number_real_underflow.json   |   1 +
 .../json_test_suite/i_number_too_big_neg_int.json  |   1 +
 .../json_test_suite/i_number_too_big_pos_int.json  |   1 +
 .../i_number_very_big_negative_int.json            |   1 +
 .../i_object_key_lone_2nd_surrogate.json           |   1 +
 .../i_string_1st_surrogate_but_2nd_missing.json    |   1 +
 .../i_string_1st_valid_surrogate_2nd_invalid.json  |   1 +
 .../i_string_UTF-16LE_with_BOM.json                | Bin 0 -> 12 bytes
 .../i_string_UTF-8_invalid_sequence.json           |   1 +
 .../i_string_UTF8_surrogate_U+D800.json            |   1 +
 ...ring_incomplete_surrogate_and_escape_valid.json |   1 +
 .../i_string_incomplete_surrogate_pair.json        |   1 +
 ..._string_incomplete_surrogates_escape_valid.json |   1 +
 .../i_string_invalid_lonely_surrogate.json         |   1 +
 .../i_string_invalid_surrogate.json                |   1 +
 .../json_test_suite/i_string_invalid_utf-8.json    |   1 +
 .../i_string_inverted_surrogates_U+1D11E.json      |   1 +
 .../json_test_suite/i_string_iso_latin_1.json      |   1 +
 .../i_string_lone_second_surrogate.json            |   1 +
 .../i_string_lone_utf8_continuation_byte.json      |   1 +
 .../i_string_not_in_unicode_range.json             |   1 +
 .../i_string_overlong_sequence_2_bytes.json        |   1 +
 .../i_string_overlong_sequence_6_bytes.json        |   1 +
 .../i_string_overlong_sequence_6_bytes_null.json   |   1 +
 .../json_test_suite/i_string_truncated-utf-8.json  |   1 +
 .../json_test_suite/i_string_utf16BE_no_BOM.json   | Bin 0 -> 10 bytes
 .../json_test_suite/i_string_utf16LE_no_BOM.json   | Bin 0 -> 10 bytes
 .../i_structure_500_nested_arrays.json             |   1 +
 .../i_structure_UTF-8_BOM_empty_object.json        |   1 +
 .../n_array_1_true_without_comma.json              |   1 +
 .../json_test_suite/n_array_a_invalid_utf8.json    |   1 +
 .../n_array_colon_instead_of_comma.json            |   1 +
 .../json_test_suite/n_array_comma_after_close.json |   1 +
 .../json_test_suite/n_array_comma_and_number.json  |   1 +
 .../json_test_suite/n_array_double_comma.json      |   1 +
 .../n_array_double_extra_comma.json                |   1 +
 .../cases/json_test_suite/n_array_extra_close.json |   1 +
 .../cases/json_test_suite/n_array_extra_comma.json |   1 +
 test/cases/json_test_suite/n_array_incomplete.json |   1 +
 .../n_array_incomplete_invalid_value.json          |   1 +
 .../n_array_inner_array_no_comma.json              |   1 +
 .../json_test_suite/n_array_invalid_utf8.json      |   1 +
 .../n_array_items_separated_by_semicolon.json      |   1 +
 test/cases/json_test_suite/n_array_just_comma.json |   1 +
 test/cases/json_test_suite/n_array_just_minus.json |   1 +
 .../json_test_suite/n_array_missing_value.json     |   1 +
 .../json_test_suite/n_array_newlines_unclosed.json |   3 +
 .../json_test_suite/n_array_number_and_comma.json  |   1 +
 .../n_array_number_and_several_commas.json         |   1 +
 .../n_array_spaces_vertical_tab_formfeed.json      |   1 +
 .../cases/json_test_suite/n_array_star_inside.json |   1 +
 test/cases/json_test_suite/n_array_unclosed.json   |   1 +
 .../n_array_unclosed_trailing_comma.json           |   1 +
 .../n_array_unclosed_with_new_lines.json           |   3 +
 .../n_array_unclosed_with_object_inside.json       |   1 +
 test/cases/json_test_suite/n_incomplete_false.json |   1 +
 test/cases/json_test_suite/n_incomplete_null.json  |   1 +
 test/cases/json_test_suite/n_incomplete_true.json  |   1 +
 .../n_multidigit_number_then_00.json               | Bin 0 -> 4 bytes
 test/cases/json_test_suite/n_number_++.json        |   1 +
 test/cases/json_test_suite/n_number_+1.json        |   1 +
 test/cases/json_test_suite/n_number_+Inf.json      |   1 +
 test/cases/json_test_suite/n_number_-01.json       |   1 +
 test/cases/json_test_suite/n_number_-1.0..json     |   1 +
 test/cases/json_test_suite/n_number_-2..json       |   1 +
 test/cases/json_test_suite/n_number_-NaN.json      |   1 +
 test/cases/json_test_suite/n_number_.-1.json       |   1 +
 test/cases/json_test_suite/n_number_.2e-3.json     |   1 +
 test/cases/json_test_suite/n_number_0.1.2.json     |   1 +
 test/cases/json_test_suite/n_number_0.3e+.json     |   1 +
 test/cases/json_test_suite/n_number_0.3e.json      |   1 +
 test/cases/json_test_suite/n_number_0.e1.json      |   1 +
 .../json_test_suite/n_number_0_capital_E+.json     |   1 +
 .../json_test_suite/n_number_0_capital_E.json      |   1 +
 test/cases/json_test_suite/n_number_0e+.json       |   1 +
 test/cases/json_test_suite/n_number_0e.json        |   1 +
 test/cases/json_test_suite/n_number_1.0e+.json     |   1 +
 test/cases/json_test_suite/n_number_1.0e-.json     |   1 +
 test/cases/json_test_suite/n_number_1.0e.json      |   1 +
 test/cases/json_test_suite/n_number_1_000.json     |   1 +
 test/cases/json_test_suite/n_number_1eE2.json      |   1 +
 test/cases/json_test_suite/n_number_2.e+3.json     |   1 +
 test/cases/json_test_suite/n_number_2.e-3.json     |   1 +
 test/cases/json_test_suite/n_number_2.e3.json      |   1 +
 test/cases/json_test_suite/n_number_9.e+.json      |   1 +
 test/cases/json_test_suite/n_number_Inf.json       |   1 +
 test/cases/json_test_suite/n_number_NaN.json       |   1 +
 .../n_number_U+FF11_fullwidth_digit_one.json       |   1 +
 .../cases/json_test_suite/n_number_expression.json |   1 +
 .../json_test_suite/n_number_hex_1_digit.json      |   1 +
 .../json_test_suite/n_number_hex_2_digits.json     |   1 +
 test/cases/json_test_suite/n_number_infinity.json  |   1 +
 test/cases/json_test_suite/n_number_invalid+-.json |   1 +
 .../n_number_invalid-negative-real.json            |   1 +
 .../n_number_invalid-utf-8-in-bigger-int.json      |   1 +
 .../n_number_invalid-utf-8-in-exponent.json        |   1 +
 .../n_number_invalid-utf-8-in-int.json             |   1 +
 .../json_test_suite/n_number_minus_infinity.json   |   1 +
 .../n_number_minus_sign_with_trailing_garbage.json |   1 +
 .../json_test_suite/n_number_minus_space_1.json    |   1 +
 .../n_number_neg_int_starting_with_zero.json       |   1 +
 .../n_number_neg_real_without_int_part.json        |   1 +
 .../n_number_neg_with_garbage_at_end.json          |   1 +
 .../n_number_real_garbage_after_e.json             |   1 +
 .../n_number_real_with_invalid_utf8_after_e.json   |   1 +
 .../n_number_real_without_fractional_part.json     |   1 +
 .../n_number_starting_with_dot.json                |   1 +
 .../cases/json_test_suite/n_number_with_alpha.json |   1 +
 .../json_test_suite/n_number_with_alpha_char.json  |   1 +
 .../n_number_with_leading_zero.json                |   1 +
 test/cases/json_test_suite/n_object_bad_value.json |   1 +
 .../json_test_suite/n_object_bracket_key.json      |   1 +
 .../n_object_comma_instead_of_colon.json           |   1 +
 .../json_test_suite/n_object_double_colon.json     |   1 +
 test/cases/json_test_suite/n_object_emoji.json     |   1 +
 .../json_test_suite/n_object_garbage_at_end.json   |   1 +
 .../n_object_key_with_single_quotes.json           |   1 +
 ...ontinuation_byte_in_key_and_trailing_comma.json |   1 +
 .../json_test_suite/n_object_missing_colon.json    |   1 +
 .../json_test_suite/n_object_missing_key.json      |   1 +
 .../n_object_missing_semicolon.json                |   1 +
 .../json_test_suite/n_object_missing_value.json    |   1 +
 test/cases/json_test_suite/n_object_no-colon.json  |   1 +
 .../json_test_suite/n_object_non_string_key.json   |   1 +
 ...ect_non_string_key_but_huge_number_instead.json |   1 +
 .../n_object_repeated_null_null.json               |   1 +
 .../n_object_several_trailing_commas.json          |   1 +
 .../json_test_suite/n_object_single_quote.json     |   1 +
 .../json_test_suite/n_object_trailing_comma.json   |   1 +
 .../json_test_suite/n_object_trailing_comment.json |   1 +
 .../n_object_trailing_comment_open.json            |   1 +
 .../n_object_trailing_comment_slash_open.json      |   1 +
 ...ect_trailing_comment_slash_open_incomplete.json |   1 +
 .../n_object_two_commas_in_a_row.json              |   1 +
 .../json_test_suite/n_object_unquoted_key.json     |   1 +
 .../n_object_unterminated-value.json               |   1 +
 .../n_object_with_single_string.json               |   1 +
 .../n_object_with_trailing_garbage.json            |   1 +
 test/cases/json_test_suite/n_single_space.json     |   1 +
 .../n_string_1_surrogate_then_escape.json          |   1 +
 .../n_string_1_surrogate_then_escape_u.json        |   1 +
 .../n_string_1_surrogate_then_escape_u1.json       |   1 +
 .../n_string_1_surrogate_then_escape_u1x.json      |   1 +
 .../n_string_accentuated_char_no_quotes.json       |   1 +
 .../json_test_suite/n_string_backslash_00.json     | Bin 0 -> 6 bytes
 test/cases/json_test_suite/n_string_escape_x.json  |   1 +
 .../n_string_escaped_backslash_bad.json            |   1 +
 .../n_string_escaped_ctrl_char_tab.json            |   1 +
 .../json_test_suite/n_string_escaped_emoji.json    |   1 +
 .../n_string_incomplete_escape.json                |   1 +
 .../n_string_incomplete_escaped_character.json     |   1 +
 .../n_string_incomplete_surrogate.json             |   1 +
 ...string_incomplete_surrogate_escape_invalid.json |   1 +
 .../n_string_invalid-utf-8-in-escape.json          |   1 +
 .../n_string_invalid_backslash_esc.json            |   1 +
 .../n_string_invalid_unicode_escape.json           |   1 +
 .../n_string_invalid_utf8_after_escape.json        |   1 +
 .../n_string_leading_uescaped_thinspace.json       |   1 +
 .../n_string_no_quotes_with_bad_escape.json        |   1 +
 .../n_string_single_doublequote.json               |   1 +
 .../json_test_suite/n_string_single_quote.json     |   1 +
 .../n_string_single_string_no_double_quotes.json   |   1 +
 .../n_string_start_escape_unclosed.json            |   1 +
 .../n_string_unescaped_ctrl_char.json              | Bin 0 -> 7 bytes
 .../n_string_unescaped_newline.json                |   2 +
 .../json_test_suite/n_string_unescaped_tab.json    |   1 +
 .../json_test_suite/n_string_unicode_CapitalU.json |   1 +
 .../n_string_with_trailing_garbage.json            |   1 +
 .../n_structure_100000_opening_arrays.json         |   1 +
 .../n_structure_U+2060_word_joined.json            |   1 +
 .../n_structure_UTF8_BOM_no_data.json              |   1 +
 .../n_structure_angle_bracket_..json               |   1 +
 .../n_structure_angle_bracket_null.json            |   1 +
 .../n_structure_array_trailing_garbage.json        |   1 +
 .../n_structure_array_with_extra_array_close.json  |   1 +
 .../n_structure_array_with_unclosed_string.json    |   1 +
 .../n_structure_ascii-unicode-identifier.json      |   1 +
 .../n_structure_capitalized_True.json              |   1 +
 .../n_structure_close_unopened_array.json          |   1 +
 ...n_structure_comma_instead_of_closing_brace.json |   1 +
 .../json_test_suite/n_structure_double_array.json  |   1 +
 .../json_test_suite/n_structure_end_array.json     |   1 +
 .../n_structure_incomplete_UTF8_BOM.json           |   1 +
 .../n_structure_lone-invalid-utf-8.json            |   1 +
 .../n_structure_lone-open-bracket.json             |   1 +
 .../cases/json_test_suite/n_structure_no_data.json |   0
 .../n_structure_null-byte-outside-string.json      | Bin 0 -> 3 bytes
 .../n_structure_number_with_trailing_garbage.json  |   1 +
 ...tructure_object_followed_by_closing_object.json |   1 +
 .../n_structure_object_unclosed_no_value.json      |   1 +
 .../n_structure_object_with_comment.json           |   1 +
 .../n_structure_object_with_trailing_garbage.json  |   1 +
 .../n_structure_open_array_apostrophe.json         |   1 +
 .../n_structure_open_array_comma.json              |   1 +
 .../n_structure_open_array_object.json             |   1 +
 .../n_structure_open_array_open_object.json        |   1 +
 .../n_structure_open_array_open_string.json        |   1 +
 .../n_structure_open_array_string.json             |   1 +
 .../json_test_suite/n_structure_open_object.json   |   1 +
 .../n_structure_open_object_close_array.json       |   1 +
 .../n_structure_open_object_comma.json             |   1 +
 .../n_structure_open_object_open_array.json        |   1 +
 .../n_structure_open_object_open_string.json       |   1 +
 ...ucture_open_object_string_with_apostrophes.json |   1 +
 .../json_test_suite/n_structure_open_open.json     |   1 +
 .../json_test_suite/n_structure_single_eacute.json |   1 +
 .../json_test_suite/n_structure_single_star.json   |   1 +
 .../json_test_suite/n_structure_trailing_#.json    |   1 +
 .../n_structure_uescaped_LF_before_string.json     |   1 +
 .../n_structure_unclosed_array.json                |   1 +
 .../n_structure_unclosed_array_partial_null.json   |   1 +
 ..._structure_unclosed_array_unfinished_false.json |   1 +
 ...n_structure_unclosed_array_unfinished_true.json |   1 +
 .../n_structure_unclosed_object.json               |   1 +
 .../n_structure_unicode-identifier.json            |   1 +
 .../n_structure_whitespace_U+2060_word_joiner.json |   1 +
 .../n_structure_whitespace_formfeed.json           |   1 +
 .../json_test_suite/y_array_arraysWithSpaces.json  |   1 +
 .../json_test_suite/y_array_empty-string.json      |   1 +
 test/cases/json_test_suite/y_array_empty.json      |   1 +
 .../y_array_ending_with_newline.json               |   1 +
 test/cases/json_test_suite/y_array_false.json      |   1 +
 .../json_test_suite/y_array_heterogeneous.json     |   1 +
 test/cases/json_test_suite/y_array_null.json       |   1 +
 .../y_array_with_1_and_newline.json                |   2 +
 .../y_array_with_leading_space.json                |   1 +
 .../json_test_suite/y_array_with_several_null.json |   1 +
 .../y_array_with_trailing_space.json               |   1 +
 test/cases/json_test_suite/y_number.json           |   1 +
 test/cases/json_test_suite/y_number_0e+1.json      |   1 +
 test/cases/json_test_suite/y_number_0e1.json       |   1 +
 .../json_test_suite/y_number_after_space.json      |   1 +
 .../y_number_double_close_to_zero.json             |   1 +
 .../json_test_suite/y_number_int_with_exp.json     |   1 +
 .../cases/json_test_suite/y_number_minus_zero.json |   1 +
 .../json_test_suite/y_number_negative_int.json     |   1 +
 .../json_test_suite/y_number_negative_one.json     |   1 +
 .../json_test_suite/y_number_negative_zero.json    |   1 +
 .../json_test_suite/y_number_real_capital_e.json   |   1 +
 .../y_number_real_capital_e_neg_exp.json           |   1 +
 .../y_number_real_capital_e_pos_exp.json           |   1 +
 .../json_test_suite/y_number_real_exponent.json    |   1 +
 .../y_number_real_fraction_exponent.json           |   1 +
 .../json_test_suite/y_number_real_neg_exp.json     |   1 +
 .../y_number_real_pos_exponent.json                |   1 +
 .../cases/json_test_suite/y_number_simple_int.json |   1 +
 .../json_test_suite/y_number_simple_real.json      |   1 +
 test/cases/json_test_suite/y_object.json           |   1 +
 test/cases/json_test_suite/y_object_basic.json     |   1 +
 .../json_test_suite/y_object_duplicated_key.json   |   1 +
 .../y_object_duplicated_key_and_value.json         |   1 +
 test/cases/json_test_suite/y_object_empty.json     |   1 +
 test/cases/json_test_suite/y_object_empty_key.json |   1 +
 .../y_object_escaped_null_in_key.json              |   1 +
 .../json_test_suite/y_object_extreme_numbers.json  |   1 +
 .../json_test_suite/y_object_long_strings.json     |   1 +
 test/cases/json_test_suite/y_object_simple.json    |   1 +
 .../json_test_suite/y_object_string_unicode.json   |   1 +
 .../json_test_suite/y_object_with_newlines.json    |   3 +
 .../y_string_1_2_3_bytes_UTF-8_sequences.json      |   1 +
 .../y_string_accepted_surrogate_pair.json          |   1 +
 .../y_string_accepted_surrogate_pairs.json         |   1 +
 .../json_test_suite/y_string_allowed_escapes.json  |   1 +
 .../y_string_backslash_and_u_escaped_zero.json     |   1 +
 .../y_string_backslash_doublequotes.json           |   1 +
 test/cases/json_test_suite/y_string_comments.json  |   1 +
 .../json_test_suite/y_string_double_escape_a.json  |   1 +
 .../json_test_suite/y_string_double_escape_n.json  |   1 +
 .../y_string_escaped_control_character.json        |   1 +
 .../y_string_escaped_noncharacter.json             |   1 +
 test/cases/json_test_suite/y_string_in_array.json  |   1 +
 .../y_string_in_array_with_leading_space.json      |   1 +
 .../y_string_last_surrogates_1_and_2.json          |   1 +
 .../json_test_suite/y_string_nbsp_uescaped.json    |   1 +
 .../y_string_nonCharacterInUTF-8_U+10FFFF.json     |   1 +
 .../y_string_nonCharacterInUTF-8_U+FFFF.json       |   1 +
 .../json_test_suite/y_string_null_escape.json      |   1 +
 .../json_test_suite/y_string_one-byte-utf-8.json   |   1 +
 test/cases/json_test_suite/y_string_pi.json        |   1 +
 .../y_string_reservedCharacterInUTF-8_U+1BFFF.json |   1 +
 .../json_test_suite/y_string_simple_ascii.json     |   1 +
 test/cases/json_test_suite/y_string_space.json     |   1 +
 ...g_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json |   1 +
 .../json_test_suite/y_string_three-byte-utf-8.json |   1 +
 .../json_test_suite/y_string_two-byte-utf-8.json   |   1 +
 .../json_test_suite/y_string_u+2028_line_sep.json  |   1 +
 .../json_test_suite/y_string_u+2029_par_sep.json   |   1 +
 test/cases/json_test_suite/y_string_uEscape.json   |   1 +
 .../json_test_suite/y_string_uescaped_newline.json |   1 +
 .../y_string_unescaped_char_delete.json            |   1 +
 test/cases/json_test_suite/y_string_unicode.json   |   1 +
 .../y_string_unicodeEscapedBackslash.json          |   1 +
 test/cases/json_test_suite/y_string_unicode_2.json |   1 +
 .../y_string_unicode_U+10FFFE_nonchar.json         |   1 +
 .../y_string_unicode_U+1FFFE_nonchar.json          |   1 +
 .../y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json  |   1 +
 .../y_string_unicode_U+2064_invisible_plus.json    |   1 +
 .../y_string_unicode_U+FDD0_nonchar.json           |   1 +
 .../y_string_unicode_U+FFFE_nonchar.json           |   1 +
 .../y_string_unicode_escaped_double_quote.json     |   1 +
 test/cases/json_test_suite/y_string_utf8.json      |   1 +
 .../y_string_with_del_character.json               |   1 +
 .../json_test_suite/y_structure_lonely_false.json  |   1 +
 .../json_test_suite/y_structure_lonely_int.json    |   1 +
 .../y_structure_lonely_negative_real.json          |   1 +
 .../json_test_suite/y_structure_lonely_null.json   |   1 +
 .../json_test_suite/y_structure_lonely_string.json |   1 +
 .../json_test_suite/y_structure_lonely_true.json   |   1 +
 .../json_test_suite/y_structure_string_empty.json  |   1 +
 .../y_structure_trailing_newline.json              |   1 +
 .../json_test_suite/y_structure_true_in_array.json |   1 +
 .../y_structure_whitespace_array.json              |   1 +
 test/jiffy_22_json_test_suite_tests.erl            | 101 +++++++
 320 files changed, 701 insertions(+), 30 deletions(-)

diff --git a/LICENSE b/LICENSE
index 2ae36f9..088a534 100644
--- a/LICENSE
+++ b/LICENSE
@@ -26,36 +26,6 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.
 
-Google double-conversion
-========================
-
-Copyright 2006-2011, the V8 project authors. All rights reserved.
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
-    * Redistributions of source code must retain the above copyright
-      notice, this list of conditions and the following disclaimer.
-    * Redistributions in binary form must reproduce the above
-      copyright notice, this list of conditions and the following
-      disclaimer in the documentation and/or other materials provided
-      with the distribution.
-    * Neither the name of Google Inc. nor the names of its
-      contributors may be used to endorse or promote products derived
-      from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
 Yajl Test Cases
 ===============
 
@@ -90,3 +60,285 @@ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
+
+Ryu Apache 2 License
+====================
+
+Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+Ryu Boost License
+=================
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+JSON Test Suite License
+=======================
+
+MIT License
+
+Copyright (c) 2016 Nicolas Seriot
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+Big List Of Naughty String (BLNS) License
+=========================================
+
+The MIT License (MIT)
+
+Copyright (c) 2015-2020 Max Woolf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/test/cases/json_test_suite/i_number_double_huge_neg_exp.json b/test/cases/json_test_suite/i_number_double_huge_neg_exp.json
new file mode 100644
index 0000000..ae4c7b7
--- /dev/null
+++ b/test/cases/json_test_suite/i_number_double_huge_neg_exp.json
@@ -0,0 +1 @@
+[123.456e-789]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_number_huge_exp.json b/test/cases/json_test_suite/i_number_huge_exp.json
new file mode 100644
index 0000000..9b5efa2
--- /dev/null
+++ b/test/cases/json_test_suite/i_number_huge_exp.json
@@ -0,0 +1 @@
+[0.4e00669999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999969999999006]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_number_neg_int_huge_exp.json b/test/cases/json_test_suite/i_number_neg_int_huge_exp.json
new file mode 100644
index 0000000..3abd58a
--- /dev/null
+++ b/test/cases/json_test_suite/i_number_neg_int_huge_exp.json
@@ -0,0 +1 @@
+[-1e+9999]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_number_pos_double_huge_exp.json b/test/cases/json_test_suite/i_number_pos_double_huge_exp.json
new file mode 100644
index 0000000..e10a7eb
--- /dev/null
+++ b/test/cases/json_test_suite/i_number_pos_double_huge_exp.json
@@ -0,0 +1 @@
+[1.5e+9999]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_number_real_neg_overflow.json b/test/cases/json_test_suite/i_number_real_neg_overflow.json
new file mode 100644
index 0000000..3d628a9
--- /dev/null
+++ b/test/cases/json_test_suite/i_number_real_neg_overflow.json
@@ -0,0 +1 @@
+[-123123e100000]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_number_real_pos_overflow.json b/test/cases/json_test_suite/i_number_real_pos_overflow.json
new file mode 100644
index 0000000..54d7d3d
--- /dev/null
+++ b/test/cases/json_test_suite/i_number_real_pos_overflow.json
@@ -0,0 +1 @@
+[123123e100000]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_number_real_underflow.json b/test/cases/json_test_suite/i_number_real_underflow.json
new file mode 100644
index 0000000..c5236eb
--- /dev/null
+++ b/test/cases/json_test_suite/i_number_real_underflow.json
@@ -0,0 +1 @@
+[123e-10000000]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_number_too_big_neg_int.json b/test/cases/json_test_suite/i_number_too_big_neg_int.json
new file mode 100644
index 0000000..dfa3846
--- /dev/null
+++ b/test/cases/json_test_suite/i_number_too_big_neg_int.json
@@ -0,0 +1 @@
+[-123123123123123123123123123123]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_number_too_big_pos_int.json b/test/cases/json_test_suite/i_number_too_big_pos_int.json
new file mode 100644
index 0000000..338a8c3
--- /dev/null
+++ b/test/cases/json_test_suite/i_number_too_big_pos_int.json
@@ -0,0 +1 @@
+[100000000000000000000]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_number_very_big_negative_int.json b/test/cases/json_test_suite/i_number_very_big_negative_int.json
new file mode 100644
index 0000000..e2d9738
--- /dev/null
+++ b/test/cases/json_test_suite/i_number_very_big_negative_int.json
@@ -0,0 +1 @@
+[-237462374673276894279832749832423479823246327846]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_object_key_lone_2nd_surrogate.json b/test/cases/json_test_suite/i_object_key_lone_2nd_surrogate.json
new file mode 100644
index 0000000..5be7eba
--- /dev/null
+++ b/test/cases/json_test_suite/i_object_key_lone_2nd_surrogate.json
@@ -0,0 +1 @@
+{"\uDFAA":0}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_1st_surrogate_but_2nd_missing.json b/test/cases/json_test_suite/i_string_1st_surrogate_but_2nd_missing.json
new file mode 100644
index 0000000..3b9e37c
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_1st_surrogate_but_2nd_missing.json
@@ -0,0 +1 @@
+["\uDADA"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_1st_valid_surrogate_2nd_invalid.json b/test/cases/json_test_suite/i_string_1st_valid_surrogate_2nd_invalid.json
new file mode 100644
index 0000000..4875928
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_1st_valid_surrogate_2nd_invalid.json
@@ -0,0 +1 @@
+["\uD888\u1234"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_UTF-16LE_with_BOM.json b/test/cases/json_test_suite/i_string_UTF-16LE_with_BOM.json
new file mode 100644
index 0000000..2a79c06
Binary files /dev/null and b/test/cases/json_test_suite/i_string_UTF-16LE_with_BOM.json differ
diff --git a/test/cases/json_test_suite/i_string_UTF-8_invalid_sequence.json b/test/cases/json_test_suite/i_string_UTF-8_invalid_sequence.json
new file mode 100644
index 0000000..e2a968a
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_UTF-8_invalid_sequence.json
@@ -0,0 +1 @@
+["日ш�"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_UTF8_surrogate_U+D800.json b/test/cases/json_test_suite/i_string_UTF8_surrogate_U+D800.json
new file mode 100644
index 0000000..916bff9
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_UTF8_surrogate_U+D800.json
@@ -0,0 +1 @@
+["���"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_incomplete_surrogate_and_escape_valid.json b/test/cases/json_test_suite/i_string_incomplete_surrogate_and_escape_valid.json
new file mode 100644
index 0000000..3cb11d2
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_incomplete_surrogate_and_escape_valid.json
@@ -0,0 +1 @@
+["\uD800\n"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_incomplete_surrogate_pair.json b/test/cases/json_test_suite/i_string_incomplete_surrogate_pair.json
new file mode 100644
index 0000000..38ec23b
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_incomplete_surrogate_pair.json
@@ -0,0 +1 @@
+["\uDd1ea"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_incomplete_surrogates_escape_valid.json b/test/cases/json_test_suite/i_string_incomplete_surrogates_escape_valid.json
new file mode 100644
index 0000000..c9cd6f6
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_incomplete_surrogates_escape_valid.json
@@ -0,0 +1 @@
+["\uD800\uD800\n"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_invalid_lonely_surrogate.json b/test/cases/json_test_suite/i_string_invalid_lonely_surrogate.json
new file mode 100644
index 0000000..3abbd8d
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_invalid_lonely_surrogate.json
@@ -0,0 +1 @@
+["\ud800"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_invalid_surrogate.json b/test/cases/json_test_suite/i_string_invalid_surrogate.json
new file mode 100644
index 0000000..ffddc04
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_invalid_surrogate.json
@@ -0,0 +1 @@
+["\ud800abc"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_invalid_utf-8.json b/test/cases/json_test_suite/i_string_invalid_utf-8.json
new file mode 100644
index 0000000..8e45a7e
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_invalid_utf-8.json
@@ -0,0 +1 @@
+["�"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_inverted_surrogates_U+1D11E.json b/test/cases/json_test_suite/i_string_inverted_surrogates_U+1D11E.json
new file mode 100644
index 0000000..0d5456c
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_inverted_surrogates_U+1D11E.json
@@ -0,0 +1 @@
+["\uDd1e\uD834"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_iso_latin_1.json b/test/cases/json_test_suite/i_string_iso_latin_1.json
new file mode 100644
index 0000000..9389c98
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_iso_latin_1.json
@@ -0,0 +1 @@
+["�"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_lone_second_surrogate.json b/test/cases/json_test_suite/i_string_lone_second_surrogate.json
new file mode 100644
index 0000000..1dbd397
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_lone_second_surrogate.json
@@ -0,0 +1 @@
+["\uDFAA"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_lone_utf8_continuation_byte.json b/test/cases/json_test_suite/i_string_lone_utf8_continuation_byte.json
new file mode 100644
index 0000000..729337c
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_lone_utf8_continuation_byte.json
@@ -0,0 +1 @@
+["�"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_not_in_unicode_range.json b/test/cases/json_test_suite/i_string_not_in_unicode_range.json
new file mode 100644
index 0000000..df90a29
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_not_in_unicode_range.json
@@ -0,0 +1 @@
+["����"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_overlong_sequence_2_bytes.json b/test/cases/json_test_suite/i_string_overlong_sequence_2_bytes.json
new file mode 100644
index 0000000..c8cee5e
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_overlong_sequence_2_bytes.json
@@ -0,0 +1 @@
+["��"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_overlong_sequence_6_bytes.json b/test/cases/json_test_suite/i_string_overlong_sequence_6_bytes.json
new file mode 100644
index 0000000..9a91da7
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_overlong_sequence_6_bytes.json
@@ -0,0 +1 @@
+["������"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_overlong_sequence_6_bytes_null.json b/test/cases/json_test_suite/i_string_overlong_sequence_6_bytes_null.json
new file mode 100644
index 0000000..d24fffd
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_overlong_sequence_6_bytes_null.json
@@ -0,0 +1 @@
+["������"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_truncated-utf-8.json b/test/cases/json_test_suite/i_string_truncated-utf-8.json
new file mode 100644
index 0000000..63c7777
--- /dev/null
+++ b/test/cases/json_test_suite/i_string_truncated-utf-8.json
@@ -0,0 +1 @@
+["��"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_string_utf16BE_no_BOM.json b/test/cases/json_test_suite/i_string_utf16BE_no_BOM.json
new file mode 100644
index 0000000..57e5392
Binary files /dev/null and b/test/cases/json_test_suite/i_string_utf16BE_no_BOM.json differ
diff --git a/test/cases/json_test_suite/i_string_utf16LE_no_BOM.json b/test/cases/json_test_suite/i_string_utf16LE_no_BOM.json
new file mode 100644
index 0000000..c49c1b2
Binary files /dev/null and b/test/cases/json_test_suite/i_string_utf16LE_no_BOM.json differ
diff --git a/test/cases/json_test_suite/i_structure_500_nested_arrays.json b/test/cases/json_test_suite/i_structure_500_nested_arrays.json
new file mode 100644
index 0000000..7118405
--- /dev/null
+++ b/test/cases/json_test_suite/i_structure_500_nested_arrays.json
@@ -0,0 +1 @@
+[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ [...]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/i_structure_UTF-8_BOM_empty_object.json b/test/cases/json_test_suite/i_structure_UTF-8_BOM_empty_object.json
new file mode 100644
index 0000000..22fdca1
--- /dev/null
+++ b/test/cases/json_test_suite/i_structure_UTF-8_BOM_empty_object.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_1_true_without_comma.json b/test/cases/json_test_suite/n_array_1_true_without_comma.json
new file mode 100644
index 0000000..c14e3f6
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_1_true_without_comma.json
@@ -0,0 +1 @@
+[1 true]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_a_invalid_utf8.json b/test/cases/json_test_suite/n_array_a_invalid_utf8.json
new file mode 100644
index 0000000..38a86e2
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_a_invalid_utf8.json
@@ -0,0 +1 @@
+[a�]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_colon_instead_of_comma.json b/test/cases/json_test_suite/n_array_colon_instead_of_comma.json
new file mode 100644
index 0000000..0d02ad4
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_colon_instead_of_comma.json
@@ -0,0 +1 @@
+["": 1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_comma_after_close.json b/test/cases/json_test_suite/n_array_comma_after_close.json
new file mode 100644
index 0000000..2ccba8d
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_comma_after_close.json
@@ -0,0 +1 @@
+[""],
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_comma_and_number.json b/test/cases/json_test_suite/n_array_comma_and_number.json
new file mode 100644
index 0000000..d2c84e3
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_comma_and_number.json
@@ -0,0 +1 @@
+[,1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_double_comma.json b/test/cases/json_test_suite/n_array_double_comma.json
new file mode 100644
index 0000000..0431712
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_double_comma.json
@@ -0,0 +1 @@
+[1,,2]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_double_extra_comma.json b/test/cases/json_test_suite/n_array_double_extra_comma.json
new file mode 100644
index 0000000..3f01d31
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_double_extra_comma.json
@@ -0,0 +1 @@
+["x",,]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_extra_close.json b/test/cases/json_test_suite/n_array_extra_close.json
new file mode 100644
index 0000000..c12f9fa
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_extra_close.json
@@ -0,0 +1 @@
+["x"]]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_extra_comma.json b/test/cases/json_test_suite/n_array_extra_comma.json
new file mode 100644
index 0000000..5f8ce18
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_extra_comma.json
@@ -0,0 +1 @@
+["",]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_incomplete.json b/test/cases/json_test_suite/n_array_incomplete.json
new file mode 100644
index 0000000..cc65b0b
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_incomplete.json
@@ -0,0 +1 @@
+["x"
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_incomplete_invalid_value.json b/test/cases/json_test_suite/n_array_incomplete_invalid_value.json
new file mode 100644
index 0000000..c21a8f6
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_incomplete_invalid_value.json
@@ -0,0 +1 @@
+[x
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_inner_array_no_comma.json b/test/cases/json_test_suite/n_array_inner_array_no_comma.json
new file mode 100644
index 0000000..c70b716
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_inner_array_no_comma.json
@@ -0,0 +1 @@
+[3[4]]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_invalid_utf8.json b/test/cases/json_test_suite/n_array_invalid_utf8.json
new file mode 100644
index 0000000..6099d34
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_invalid_utf8.json
@@ -0,0 +1 @@
+[�]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_items_separated_by_semicolon.json b/test/cases/json_test_suite/n_array_items_separated_by_semicolon.json
new file mode 100644
index 0000000..d4bd731
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_items_separated_by_semicolon.json
@@ -0,0 +1 @@
+[1:2]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_just_comma.json b/test/cases/json_test_suite/n_array_just_comma.json
new file mode 100644
index 0000000..9d7077c
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_just_comma.json
@@ -0,0 +1 @@
+[,]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_just_minus.json b/test/cases/json_test_suite/n_array_just_minus.json
new file mode 100644
index 0000000..29501c6
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_just_minus.json
@@ -0,0 +1 @@
+[-]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_missing_value.json b/test/cases/json_test_suite/n_array_missing_value.json
new file mode 100644
index 0000000..3a6ba86
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_missing_value.json
@@ -0,0 +1 @@
+[   , ""]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_newlines_unclosed.json b/test/cases/json_test_suite/n_array_newlines_unclosed.json
new file mode 100644
index 0000000..6466800
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_newlines_unclosed.json
@@ -0,0 +1,3 @@
+["a",
+4
+,1,
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_number_and_comma.json b/test/cases/json_test_suite/n_array_number_and_comma.json
new file mode 100644
index 0000000..13f6f1d
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_number_and_comma.json
@@ -0,0 +1 @@
+[1,]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_number_and_several_commas.json b/test/cases/json_test_suite/n_array_number_and_several_commas.json
new file mode 100644
index 0000000..0ac408c
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_number_and_several_commas.json
@@ -0,0 +1 @@
+[1,,]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_spaces_vertical_tab_formfeed.json b/test/cases/json_test_suite/n_array_spaces_vertical_tab_formfeed.json
new file mode 100644
index 0000000..6cd7cf5
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_spaces_vertical_tab_formfeed.json
@@ -0,0 +1 @@
+["a"\f]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_star_inside.json b/test/cases/json_test_suite/n_array_star_inside.json
new file mode 100644
index 0000000..5a51946
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_star_inside.json
@@ -0,0 +1 @@
+[*]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_unclosed.json b/test/cases/json_test_suite/n_array_unclosed.json
new file mode 100644
index 0000000..0607330
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_unclosed.json
@@ -0,0 +1 @@
+[""
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_unclosed_trailing_comma.json b/test/cases/json_test_suite/n_array_unclosed_trailing_comma.json
new file mode 100644
index 0000000..6604698
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_unclosed_trailing_comma.json
@@ -0,0 +1 @@
+[1,
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_unclosed_with_new_lines.json b/test/cases/json_test_suite/n_array_unclosed_with_new_lines.json
new file mode 100644
index 0000000..4f61de3
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_unclosed_with_new_lines.json
@@ -0,0 +1,3 @@
+[1,
+1
+,1
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_array_unclosed_with_object_inside.json b/test/cases/json_test_suite/n_array_unclosed_with_object_inside.json
new file mode 100644
index 0000000..043a87e
--- /dev/null
+++ b/test/cases/json_test_suite/n_array_unclosed_with_object_inside.json
@@ -0,0 +1 @@
+[{}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_incomplete_false.json b/test/cases/json_test_suite/n_incomplete_false.json
new file mode 100644
index 0000000..eb18c6a
--- /dev/null
+++ b/test/cases/json_test_suite/n_incomplete_false.json
@@ -0,0 +1 @@
+[fals]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_incomplete_null.json b/test/cases/json_test_suite/n_incomplete_null.json
new file mode 100644
index 0000000..c18ef53
--- /dev/null
+++ b/test/cases/json_test_suite/n_incomplete_null.json
@@ -0,0 +1 @@
+[nul]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_incomplete_true.json b/test/cases/json_test_suite/n_incomplete_true.json
new file mode 100644
index 0000000..f451ac6
--- /dev/null
+++ b/test/cases/json_test_suite/n_incomplete_true.json
@@ -0,0 +1 @@
+[tru]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_multidigit_number_then_00.json b/test/cases/json_test_suite/n_multidigit_number_then_00.json
new file mode 100644
index 0000000..c22507b
Binary files /dev/null and b/test/cases/json_test_suite/n_multidigit_number_then_00.json differ
diff --git a/test/cases/json_test_suite/n_number_++.json b/test/cases/json_test_suite/n_number_++.json
new file mode 100644
index 0000000..bdb62aa
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_++.json
@@ -0,0 +1 @@
+[++1234]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_+1.json b/test/cases/json_test_suite/n_number_+1.json
new file mode 100644
index 0000000..3cbe58c
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_+1.json
@@ -0,0 +1 @@
+[+1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_+Inf.json b/test/cases/json_test_suite/n_number_+Inf.json
new file mode 100644
index 0000000..871ae14
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_+Inf.json
@@ -0,0 +1 @@
+[+Inf]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_-01.json b/test/cases/json_test_suite/n_number_-01.json
new file mode 100644
index 0000000..0df32ba
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_-01.json
@@ -0,0 +1 @@
+[-01]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_-1.0..json b/test/cases/json_test_suite/n_number_-1.0..json
new file mode 100644
index 0000000..7cf55a8
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_-1.0..json
@@ -0,0 +1 @@
+[-1.0.]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_-2..json b/test/cases/json_test_suite/n_number_-2..json
new file mode 100644
index 0000000..9be8436
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_-2..json
@@ -0,0 +1 @@
+[-2.]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_-NaN.json b/test/cases/json_test_suite/n_number_-NaN.json
new file mode 100644
index 0000000..f61615d
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_-NaN.json
@@ -0,0 +1 @@
+[-NaN]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_.-1.json b/test/cases/json_test_suite/n_number_.-1.json
new file mode 100644
index 0000000..1c9f2dd
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_.-1.json
@@ -0,0 +1 @@
+[.-1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_.2e-3.json b/test/cases/json_test_suite/n_number_.2e-3.json
new file mode 100644
index 0000000..c6c976f
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_.2e-3.json
@@ -0,0 +1 @@
+[.2e-3]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_0.1.2.json b/test/cases/json_test_suite/n_number_0.1.2.json
new file mode 100644
index 0000000..c83a256
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_0.1.2.json
@@ -0,0 +1 @@
+[0.1.2]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_0.3e+.json b/test/cases/json_test_suite/n_number_0.3e+.json
new file mode 100644
index 0000000..a55a1bf
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_0.3e+.json
@@ -0,0 +1 @@
+[0.3e+]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_0.3e.json b/test/cases/json_test_suite/n_number_0.3e.json
new file mode 100644
index 0000000..3dd5df4
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_0.3e.json
@@ -0,0 +1 @@
+[0.3e]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_0.e1.json b/test/cases/json_test_suite/n_number_0.e1.json
new file mode 100644
index 0000000..c92c71c
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_0.e1.json
@@ -0,0 +1 @@
+[0.e1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_0_capital_E+.json b/test/cases/json_test_suite/n_number_0_capital_E+.json
new file mode 100644
index 0000000..3ba2c7d
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_0_capital_E+.json
@@ -0,0 +1 @@
+[0E+]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_0_capital_E.json b/test/cases/json_test_suite/n_number_0_capital_E.json
new file mode 100644
index 0000000..5301840
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_0_capital_E.json
@@ -0,0 +1 @@
+[0E]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_0e+.json b/test/cases/json_test_suite/n_number_0e+.json
new file mode 100644
index 0000000..8ab0bc4
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_0e+.json
@@ -0,0 +1 @@
+[0e+]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_0e.json b/test/cases/json_test_suite/n_number_0e.json
new file mode 100644
index 0000000..47ec421
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_0e.json
@@ -0,0 +1 @@
+[0e]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_1.0e+.json b/test/cases/json_test_suite/n_number_1.0e+.json
new file mode 100644
index 0000000..cd84b9f
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_1.0e+.json
@@ -0,0 +1 @@
+[1.0e+]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_1.0e-.json b/test/cases/json_test_suite/n_number_1.0e-.json
new file mode 100644
index 0000000..4eb7afa
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_1.0e-.json
@@ -0,0 +1 @@
+[1.0e-]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_1.0e.json b/test/cases/json_test_suite/n_number_1.0e.json
new file mode 100644
index 0000000..21753f4
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_1.0e.json
@@ -0,0 +1 @@
+[1.0e]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_1_000.json b/test/cases/json_test_suite/n_number_1_000.json
new file mode 100644
index 0000000..7b18b66
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_1_000.json
@@ -0,0 +1 @@
+[1 000.0]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_1eE2.json b/test/cases/json_test_suite/n_number_1eE2.json
new file mode 100644
index 0000000..4318a34
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_1eE2.json
@@ -0,0 +1 @@
+[1eE2]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_2.e+3.json b/test/cases/json_test_suite/n_number_2.e+3.json
new file mode 100644
index 0000000..4442f39
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_2.e+3.json
@@ -0,0 +1 @@
+[2.e+3]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_2.e-3.json b/test/cases/json_test_suite/n_number_2.e-3.json
new file mode 100644
index 0000000..a65060e
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_2.e-3.json
@@ -0,0 +1 @@
+[2.e-3]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_2.e3.json b/test/cases/json_test_suite/n_number_2.e3.json
new file mode 100644
index 0000000..66f7cf7
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_2.e3.json
@@ -0,0 +1 @@
+[2.e3]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_9.e+.json b/test/cases/json_test_suite/n_number_9.e+.json
new file mode 100644
index 0000000..732a7b1
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_9.e+.json
@@ -0,0 +1 @@
+[9.e+]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_Inf.json b/test/cases/json_test_suite/n_number_Inf.json
new file mode 100644
index 0000000..c40c734
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_Inf.json
@@ -0,0 +1 @@
+[Inf]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_NaN.json b/test/cases/json_test_suite/n_number_NaN.json
new file mode 100644
index 0000000..4992317
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_NaN.json
@@ -0,0 +1 @@
+[NaN]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_U+FF11_fullwidth_digit_one.json b/test/cases/json_test_suite/n_number_U+FF11_fullwidth_digit_one.json
new file mode 100644
index 0000000..b14587e
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_U+FF11_fullwidth_digit_one.json
@@ -0,0 +1 @@
+[1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_expression.json b/test/cases/json_test_suite/n_number_expression.json
new file mode 100644
index 0000000..76fdbc8
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_expression.json
@@ -0,0 +1 @@
+[1+2]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_hex_1_digit.json b/test/cases/json_test_suite/n_number_hex_1_digit.json
new file mode 100644
index 0000000..3b21488
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_hex_1_digit.json
@@ -0,0 +1 @@
+[0x1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_hex_2_digits.json b/test/cases/json_test_suite/n_number_hex_2_digits.json
new file mode 100644
index 0000000..83e516a
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_hex_2_digits.json
@@ -0,0 +1 @@
+[0x42]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_infinity.json b/test/cases/json_test_suite/n_number_infinity.json
new file mode 100644
index 0000000..8c2baf7
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_infinity.json
@@ -0,0 +1 @@
+[Infinity]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_invalid+-.json b/test/cases/json_test_suite/n_number_invalid+-.json
new file mode 100644
index 0000000..1cce602
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_invalid+-.json
@@ -0,0 +1 @@
+[0e+-1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_invalid-negative-real.json b/test/cases/json_test_suite/n_number_invalid-negative-real.json
new file mode 100644
index 0000000..5fc3c1e
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_invalid-negative-real.json
@@ -0,0 +1 @@
+[-123.123foo]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_invalid-utf-8-in-bigger-int.json b/test/cases/json_test_suite/n_number_invalid-utf-8-in-bigger-int.json
new file mode 100644
index 0000000..3b97e58
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_invalid-utf-8-in-bigger-int.json
@@ -0,0 +1 @@
+[123�]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_invalid-utf-8-in-exponent.json b/test/cases/json_test_suite/n_number_invalid-utf-8-in-exponent.json
new file mode 100644
index 0000000..ea35d72
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_invalid-utf-8-in-exponent.json
@@ -0,0 +1 @@
+[1e1�]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_invalid-utf-8-in-int.json b/test/cases/json_test_suite/n_number_invalid-utf-8-in-int.json
new file mode 100644
index 0000000..371226e
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_invalid-utf-8-in-int.json
@@ -0,0 +1 @@
+[0�]
diff --git a/test/cases/json_test_suite/n_number_minus_infinity.json b/test/cases/json_test_suite/n_number_minus_infinity.json
new file mode 100644
index 0000000..cf4133d
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_minus_infinity.json
@@ -0,0 +1 @@
+[-Infinity]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_minus_sign_with_trailing_garbage.json b/test/cases/json_test_suite/n_number_minus_sign_with_trailing_garbage.json
new file mode 100644
index 0000000..a6d8e78
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_minus_sign_with_trailing_garbage.json
@@ -0,0 +1 @@
+[-foo]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_minus_space_1.json b/test/cases/json_test_suite/n_number_minus_space_1.json
new file mode 100644
index 0000000..9a5ebed
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_minus_space_1.json
@@ -0,0 +1 @@
+[- 1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_neg_int_starting_with_zero.json b/test/cases/json_test_suite/n_number_neg_int_starting_with_zero.json
new file mode 100644
index 0000000..67af096
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_neg_int_starting_with_zero.json
@@ -0,0 +1 @@
+[-012]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_neg_real_without_int_part.json b/test/cases/json_test_suite/n_number_neg_real_without_int_part.json
new file mode 100644
index 0000000..1f2a434
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_neg_real_without_int_part.json
@@ -0,0 +1 @@
+[-.123]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_neg_with_garbage_at_end.json b/test/cases/json_test_suite/n_number_neg_with_garbage_at_end.json
new file mode 100644
index 0000000..2aa7311
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_neg_with_garbage_at_end.json
@@ -0,0 +1 @@
+[-1x]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_real_garbage_after_e.json b/test/cases/json_test_suite/n_number_real_garbage_after_e.json
new file mode 100644
index 0000000..9213dfc
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_real_garbage_after_e.json
@@ -0,0 +1 @@
+[1ea]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_real_with_invalid_utf8_after_e.json b/test/cases/json_test_suite/n_number_real_with_invalid_utf8_after_e.json
new file mode 100644
index 0000000..1e52ef9
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_real_with_invalid_utf8_after_e.json
@@ -0,0 +1 @@
+[1e�]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_real_without_fractional_part.json b/test/cases/json_test_suite/n_number_real_without_fractional_part.json
new file mode 100644
index 0000000..1de287c
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_real_without_fractional_part.json
@@ -0,0 +1 @@
+[1.]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_starting_with_dot.json b/test/cases/json_test_suite/n_number_starting_with_dot.json
new file mode 100644
index 0000000..f682dbd
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_starting_with_dot.json
@@ -0,0 +1 @@
+[.123]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_with_alpha.json b/test/cases/json_test_suite/n_number_with_alpha.json
new file mode 100644
index 0000000..1e42d81
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_with_alpha.json
@@ -0,0 +1 @@
+[1.2a-3]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_with_alpha_char.json b/test/cases/json_test_suite/n_number_with_alpha_char.json
new file mode 100644
index 0000000..b79dacc
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_with_alpha_char.json
@@ -0,0 +1 @@
+[1.8011670033376514H-308]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_number_with_leading_zero.json b/test/cases/json_test_suite/n_number_with_leading_zero.json
new file mode 100644
index 0000000..7106da1
--- /dev/null
+++ b/test/cases/json_test_suite/n_number_with_leading_zero.json
@@ -0,0 +1 @@
+[012]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_bad_value.json b/test/cases/json_test_suite/n_object_bad_value.json
new file mode 100644
index 0000000..a03a8c0
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_bad_value.json
@@ -0,0 +1 @@
+["x", truth]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_bracket_key.json b/test/cases/json_test_suite/n_object_bracket_key.json
new file mode 100644
index 0000000..cc443b4
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_bracket_key.json
@@ -0,0 +1 @@
+{[: "x"}
diff --git a/test/cases/json_test_suite/n_object_comma_instead_of_colon.json b/test/cases/json_test_suite/n_object_comma_instead_of_colon.json
new file mode 100644
index 0000000..8d56377
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_comma_instead_of_colon.json
@@ -0,0 +1 @@
+{"x", null}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_double_colon.json b/test/cases/json_test_suite/n_object_double_colon.json
new file mode 100644
index 0000000..80e8c7b
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_double_colon.json
@@ -0,0 +1 @@
+{"x"::"b"}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_emoji.json b/test/cases/json_test_suite/n_object_emoji.json
new file mode 100644
index 0000000..cb4078e
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_emoji.json
@@ -0,0 +1 @@
+{🇨🇭}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_garbage_at_end.json b/test/cases/json_test_suite/n_object_garbage_at_end.json
new file mode 100644
index 0000000..80c42cb
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_garbage_at_end.json
@@ -0,0 +1 @@
+{"a":"a" 123}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_key_with_single_quotes.json b/test/cases/json_test_suite/n_object_key_with_single_quotes.json
new file mode 100644
index 0000000..77c3275
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_key_with_single_quotes.json
@@ -0,0 +1 @@
+{key: 'value'}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_lone_continuation_byte_in_key_and_trailing_comma.json b/test/cases/json_test_suite/n_object_lone_continuation_byte_in_key_and_trailing_comma.json
new file mode 100644
index 0000000..aa2cb63
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_lone_continuation_byte_in_key_and_trailing_comma.json
@@ -0,0 +1 @@
+{"�":"0",}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_missing_colon.json b/test/cases/json_test_suite/n_object_missing_colon.json
new file mode 100644
index 0000000..b98eff6
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_missing_colon.json
@@ -0,0 +1 @@
+{"a" b}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_missing_key.json b/test/cases/json_test_suite/n_object_missing_key.json
new file mode 100644
index 0000000..b4fb0f5
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_missing_key.json
@@ -0,0 +1 @@
+{:"b"}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_missing_semicolon.json b/test/cases/json_test_suite/n_object_missing_semicolon.json
new file mode 100644
index 0000000..e345138
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_missing_semicolon.json
@@ -0,0 +1 @@
+{"a" "b"}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_missing_value.json b/test/cases/json_test_suite/n_object_missing_value.json
new file mode 100644
index 0000000..3ef538a
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_missing_value.json
@@ -0,0 +1 @@
+{"a":
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_no-colon.json b/test/cases/json_test_suite/n_object_no-colon.json
new file mode 100644
index 0000000..f3797b3
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_no-colon.json
@@ -0,0 +1 @@
+{"a"
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_non_string_key.json b/test/cases/json_test_suite/n_object_non_string_key.json
new file mode 100644
index 0000000..b9945b3
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_non_string_key.json
@@ -0,0 +1 @@
+{1:1}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_non_string_key_but_huge_number_instead.json b/test/cases/json_test_suite/n_object_non_string_key_but_huge_number_instead.json
new file mode 100644
index 0000000..b37fa86
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_non_string_key_but_huge_number_instead.json
@@ -0,0 +1 @@
+{9999E9999:1}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_repeated_null_null.json b/test/cases/json_test_suite/n_object_repeated_null_null.json
new file mode 100644
index 0000000..f7d2959
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_repeated_null_null.json
@@ -0,0 +1 @@
+{null:null,null:null}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_several_trailing_commas.json b/test/cases/json_test_suite/n_object_several_trailing_commas.json
new file mode 100644
index 0000000..3c9afe8
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_several_trailing_commas.json
@@ -0,0 +1 @@
+{"id":0,,,,,}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_single_quote.json b/test/cases/json_test_suite/n_object_single_quote.json
new file mode 100644
index 0000000..e5cdf97
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_single_quote.json
@@ -0,0 +1 @@
+{'a':0}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_trailing_comma.json b/test/cases/json_test_suite/n_object_trailing_comma.json
new file mode 100644
index 0000000..a4b0250
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_trailing_comma.json
@@ -0,0 +1 @@
+{"id":0,}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_trailing_comment.json b/test/cases/json_test_suite/n_object_trailing_comment.json
new file mode 100644
index 0000000..a372c65
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_trailing_comment.json
@@ -0,0 +1 @@
+{"a":"b"}/**/
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_trailing_comment_open.json b/test/cases/json_test_suite/n_object_trailing_comment_open.json
new file mode 100644
index 0000000..d557f41
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_trailing_comment_open.json
@@ -0,0 +1 @@
+{"a":"b"}/**//
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_trailing_comment_slash_open.json b/test/cases/json_test_suite/n_object_trailing_comment_slash_open.json
new file mode 100644
index 0000000..e335136
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_trailing_comment_slash_open.json
@@ -0,0 +1 @@
+{"a":"b"}//
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_trailing_comment_slash_open_incomplete.json b/test/cases/json_test_suite/n_object_trailing_comment_slash_open_incomplete.json
new file mode 100644
index 0000000..d892e49
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_trailing_comment_slash_open_incomplete.json
@@ -0,0 +1 @@
+{"a":"b"}/
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_two_commas_in_a_row.json b/test/cases/json_test_suite/n_object_two_commas_in_a_row.json
new file mode 100644
index 0000000..7c639ae
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_two_commas_in_a_row.json
@@ -0,0 +1 @@
+{"a":"b",,"c":"d"}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_unquoted_key.json b/test/cases/json_test_suite/n_object_unquoted_key.json
new file mode 100644
index 0000000..8ba1372
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_unquoted_key.json
@@ -0,0 +1 @@
+{a: "b"}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_unterminated-value.json b/test/cases/json_test_suite/n_object_unterminated-value.json
new file mode 100644
index 0000000..7fe699a
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_unterminated-value.json
@@ -0,0 +1 @@
+{"a":"a
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_with_single_string.json b/test/cases/json_test_suite/n_object_with_single_string.json
new file mode 100644
index 0000000..d63f7fb
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_with_single_string.json
@@ -0,0 +1 @@
+{ "foo" : "bar", "a" }
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_object_with_trailing_garbage.json b/test/cases/json_test_suite/n_object_with_trailing_garbage.json
new file mode 100644
index 0000000..787c8f0
--- /dev/null
+++ b/test/cases/json_test_suite/n_object_with_trailing_garbage.json
@@ -0,0 +1 @@
+{"a":"b"}#
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_single_space.json b/test/cases/json_test_suite/n_single_space.json
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/test/cases/json_test_suite/n_single_space.json
@@ -0,0 +1 @@
+ 
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_1_surrogate_then_escape.json b/test/cases/json_test_suite/n_string_1_surrogate_then_escape.json
new file mode 100644
index 0000000..acec66d
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_1_surrogate_then_escape.json
@@ -0,0 +1 @@
+["\uD800\"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_1_surrogate_then_escape_u.json b/test/cases/json_test_suite/n_string_1_surrogate_then_escape_u.json
new file mode 100644
index 0000000..e834b05
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_1_surrogate_then_escape_u.json
@@ -0,0 +1 @@
+["\uD800\u"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_1_surrogate_then_escape_u1.json b/test/cases/json_test_suite/n_string_1_surrogate_then_escape_u1.json
new file mode 100644
index 0000000..a04cd34
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_1_surrogate_then_escape_u1.json
@@ -0,0 +1 @@
+["\uD800\u1"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_1_surrogate_then_escape_u1x.json b/test/cases/json_test_suite/n_string_1_surrogate_then_escape_u1x.json
new file mode 100644
index 0000000..bfbd234
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_1_surrogate_then_escape_u1x.json
@@ -0,0 +1 @@
+["\uD800\u1x"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_accentuated_char_no_quotes.json b/test/cases/json_test_suite/n_string_accentuated_char_no_quotes.json
new file mode 100644
index 0000000..fd68956
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_accentuated_char_no_quotes.json
@@ -0,0 +1 @@
+[é]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_backslash_00.json b/test/cases/json_test_suite/n_string_backslash_00.json
new file mode 100644
index 0000000..b5bf267
Binary files /dev/null and b/test/cases/json_test_suite/n_string_backslash_00.json differ
diff --git a/test/cases/json_test_suite/n_string_escape_x.json b/test/cases/json_test_suite/n_string_escape_x.json
new file mode 100644
index 0000000..fae2919
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_escape_x.json
@@ -0,0 +1 @@
+["\x00"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_escaped_backslash_bad.json b/test/cases/json_test_suite/n_string_escaped_backslash_bad.json
new file mode 100644
index 0000000..016fcb4
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_escaped_backslash_bad.json
@@ -0,0 +1 @@
+["\\\"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_escaped_ctrl_char_tab.json b/test/cases/json_test_suite/n_string_escaped_ctrl_char_tab.json
new file mode 100644
index 0000000..f35ea38
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_escaped_ctrl_char_tab.json
@@ -0,0 +1 @@
+["\	"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_escaped_emoji.json b/test/cases/json_test_suite/n_string_escaped_emoji.json
new file mode 100644
index 0000000..a277754
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_escaped_emoji.json
@@ -0,0 +1 @@
+["\🌀"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_incomplete_escape.json b/test/cases/json_test_suite/n_string_incomplete_escape.json
new file mode 100644
index 0000000..3415c33
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_incomplete_escape.json
@@ -0,0 +1 @@
+["\"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_incomplete_escaped_character.json b/test/cases/json_test_suite/n_string_incomplete_escaped_character.json
new file mode 100644
index 0000000..0f2197e
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_incomplete_escaped_character.json
@@ -0,0 +1 @@
+["\u00A"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_incomplete_surrogate.json b/test/cases/json_test_suite/n_string_incomplete_surrogate.json
new file mode 100644
index 0000000..75504a6
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_incomplete_surrogate.json
@@ -0,0 +1 @@
+["\uD834\uDd"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_incomplete_surrogate_escape_invalid.json b/test/cases/json_test_suite/n_string_incomplete_surrogate_escape_invalid.json
new file mode 100644
index 0000000..bd96560
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_incomplete_surrogate_escape_invalid.json
@@ -0,0 +1 @@
+["\uD800\uD800\x"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_invalid-utf-8-in-escape.json b/test/cases/json_test_suite/n_string_invalid-utf-8-in-escape.json
new file mode 100644
index 0000000..0c43006
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_invalid-utf-8-in-escape.json
@@ -0,0 +1 @@
+["\u�"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_invalid_backslash_esc.json b/test/cases/json_test_suite/n_string_invalid_backslash_esc.json
new file mode 100644
index 0000000..d1eb609
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_invalid_backslash_esc.json
@@ -0,0 +1 @@
+["\a"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_invalid_unicode_escape.json b/test/cases/json_test_suite/n_string_invalid_unicode_escape.json
new file mode 100644
index 0000000..7608cb6
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_invalid_unicode_escape.json
@@ -0,0 +1 @@
+["\uqqqq"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_invalid_utf8_after_escape.json b/test/cases/json_test_suite/n_string_invalid_utf8_after_escape.json
new file mode 100644
index 0000000..2f757a2
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_invalid_utf8_after_escape.json
@@ -0,0 +1 @@
+["\�"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_leading_uescaped_thinspace.json b/test/cases/json_test_suite/n_string_leading_uescaped_thinspace.json
new file mode 100644
index 0000000..7b297c6
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_leading_uescaped_thinspace.json
@@ -0,0 +1 @@
+[\u0020"asd"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_no_quotes_with_bad_escape.json b/test/cases/json_test_suite/n_string_no_quotes_with_bad_escape.json
new file mode 100644
index 0000000..01bc70a
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_no_quotes_with_bad_escape.json
@@ -0,0 +1 @@
+[\n]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_single_doublequote.json b/test/cases/json_test_suite/n_string_single_doublequote.json
new file mode 100644
index 0000000..9d68933
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_single_doublequote.json
@@ -0,0 +1 @@
+"
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_single_quote.json b/test/cases/json_test_suite/n_string_single_quote.json
new file mode 100644
index 0000000..caff239
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_single_quote.json
@@ -0,0 +1 @@
+['single quote']
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_single_string_no_double_quotes.json b/test/cases/json_test_suite/n_string_single_string_no_double_quotes.json
new file mode 100644
index 0000000..f2ba8f8
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_single_string_no_double_quotes.json
@@ -0,0 +1 @@
+abc
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_start_escape_unclosed.json b/test/cases/json_test_suite/n_string_start_escape_unclosed.json
new file mode 100644
index 0000000..db62a46
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_start_escape_unclosed.json
@@ -0,0 +1 @@
+["\
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_unescaped_ctrl_char.json b/test/cases/json_test_suite/n_string_unescaped_ctrl_char.json
new file mode 100644
index 0000000..9f21348
Binary files /dev/null and b/test/cases/json_test_suite/n_string_unescaped_ctrl_char.json differ
diff --git a/test/cases/json_test_suite/n_string_unescaped_newline.json b/test/cases/json_test_suite/n_string_unescaped_newline.json
new file mode 100644
index 0000000..700d360
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_unescaped_newline.json
@@ -0,0 +1,2 @@
+["new
+line"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_unescaped_tab.json b/test/cases/json_test_suite/n_string_unescaped_tab.json
new file mode 100644
index 0000000..160264a
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_unescaped_tab.json
@@ -0,0 +1 @@
+["	"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_unicode_CapitalU.json b/test/cases/json_test_suite/n_string_unicode_CapitalU.json
new file mode 100644
index 0000000..17332bb
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_unicode_CapitalU.json
@@ -0,0 +1 @@
+"\UA66D"
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_string_with_trailing_garbage.json b/test/cases/json_test_suite/n_string_with_trailing_garbage.json
new file mode 100644
index 0000000..efe3bd2
--- /dev/null
+++ b/test/cases/json_test_suite/n_string_with_trailing_garbage.json
@@ -0,0 +1 @@
+""x
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_100000_opening_arrays.json b/test/cases/json_test_suite/n_structure_100000_opening_arrays.json
new file mode 100644
index 0000000..a4823ee
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_100000_opening_arrays.json
@@ -0,0 +1 @@
+[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ [...]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_U+2060_word_joined.json b/test/cases/json_test_suite/n_structure_U+2060_word_joined.json
new file mode 100644
index 0000000..81156a6
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_U+2060_word_joined.json
@@ -0,0 +1 @@
+[⁠]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_UTF8_BOM_no_data.json b/test/cases/json_test_suite/n_structure_UTF8_BOM_no_data.json
new file mode 100644
index 0000000..5f28270
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_UTF8_BOM_no_data.json
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_angle_bracket_..json b/test/cases/json_test_suite/n_structure_angle_bracket_..json
new file mode 100644
index 0000000..a56fef0
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_angle_bracket_..json
@@ -0,0 +1 @@
+<.>
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_angle_bracket_null.json b/test/cases/json_test_suite/n_structure_angle_bracket_null.json
new file mode 100644
index 0000000..617f262
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_angle_bracket_null.json
@@ -0,0 +1 @@
+[<null>]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_array_trailing_garbage.json b/test/cases/json_test_suite/n_structure_array_trailing_garbage.json
new file mode 100644
index 0000000..5a745e6
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_array_trailing_garbage.json
@@ -0,0 +1 @@
+[1]x
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_array_with_extra_array_close.json b/test/cases/json_test_suite/n_structure_array_with_extra_array_close.json
new file mode 100644
index 0000000..6cfb139
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_array_with_extra_array_close.json
@@ -0,0 +1 @@
+[1]]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_array_with_unclosed_string.json b/test/cases/json_test_suite/n_structure_array_with_unclosed_string.json
new file mode 100644
index 0000000..ba6b178
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_array_with_unclosed_string.json
@@ -0,0 +1 @@
+["asd]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_ascii-unicode-identifier.json b/test/cases/json_test_suite/n_structure_ascii-unicode-identifier.json
new file mode 100644
index 0000000..ef2ab62
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_ascii-unicode-identifier.json
@@ -0,0 +1 @@
+aå
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_capitalized_True.json b/test/cases/json_test_suite/n_structure_capitalized_True.json
new file mode 100644
index 0000000..7cd8846
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_capitalized_True.json
@@ -0,0 +1 @@
+[True]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_close_unopened_array.json b/test/cases/json_test_suite/n_structure_close_unopened_array.json
new file mode 100644
index 0000000..d2af0c6
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_close_unopened_array.json
@@ -0,0 +1 @@
+1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_comma_instead_of_closing_brace.json b/test/cases/json_test_suite/n_structure_comma_instead_of_closing_brace.json
new file mode 100644
index 0000000..ac61b82
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_comma_instead_of_closing_brace.json
@@ -0,0 +1 @@
+{"x": true,
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_double_array.json b/test/cases/json_test_suite/n_structure_double_array.json
new file mode 100644
index 0000000..058d162
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_double_array.json
@@ -0,0 +1 @@
+[][]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_end_array.json b/test/cases/json_test_suite/n_structure_end_array.json
new file mode 100644
index 0000000..54caf60
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_end_array.json
@@ -0,0 +1 @@
+]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_incomplete_UTF8_BOM.json b/test/cases/json_test_suite/n_structure_incomplete_UTF8_BOM.json
new file mode 100644
index 0000000..bfcdd51
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_incomplete_UTF8_BOM.json
@@ -0,0 +1 @@
+�{}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_lone-invalid-utf-8.json b/test/cases/json_test_suite/n_structure_lone-invalid-utf-8.json
new file mode 100644
index 0000000..8b1296c
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_lone-invalid-utf-8.json
@@ -0,0 +1 @@
+�
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_lone-open-bracket.json b/test/cases/json_test_suite/n_structure_lone-open-bracket.json
new file mode 100644
index 0000000..8e2f0be
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_lone-open-bracket.json
@@ -0,0 +1 @@
+[
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_no_data.json b/test/cases/json_test_suite/n_structure_no_data.json
new file mode 100644
index 0000000..e69de29
diff --git a/test/cases/json_test_suite/n_structure_null-byte-outside-string.json b/test/cases/json_test_suite/n_structure_null-byte-outside-string.json
new file mode 100644
index 0000000..326db14
Binary files /dev/null and b/test/cases/json_test_suite/n_structure_null-byte-outside-string.json differ
diff --git a/test/cases/json_test_suite/n_structure_number_with_trailing_garbage.json b/test/cases/json_test_suite/n_structure_number_with_trailing_garbage.json
new file mode 100644
index 0000000..0746539
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_number_with_trailing_garbage.json
@@ -0,0 +1 @@
+2@
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_object_followed_by_closing_object.json b/test/cases/json_test_suite/n_structure_object_followed_by_closing_object.json
new file mode 100644
index 0000000..aa9ebae
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_object_followed_by_closing_object.json
@@ -0,0 +1 @@
+{}}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_object_unclosed_no_value.json b/test/cases/json_test_suite/n_structure_object_unclosed_no_value.json
new file mode 100644
index 0000000..17d0451
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_object_unclosed_no_value.json
@@ -0,0 +1 @@
+{"":
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_object_with_comment.json b/test/cases/json_test_suite/n_structure_object_with_comment.json
new file mode 100644
index 0000000..ed1b569
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_object_with_comment.json
@@ -0,0 +1 @@
+{"a":/*comment*/"b"}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_object_with_trailing_garbage.json b/test/cases/json_test_suite/n_structure_object_with_trailing_garbage.json
new file mode 100644
index 0000000..9ca2336
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_object_with_trailing_garbage.json
@@ -0,0 +1 @@
+{"a": true} "x"
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_open_array_apostrophe.json b/test/cases/json_test_suite/n_structure_open_array_apostrophe.json
new file mode 100644
index 0000000..8bebe3a
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_array_apostrophe.json
@@ -0,0 +1 @@
+['
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_open_array_comma.json b/test/cases/json_test_suite/n_structure_open_array_comma.json
new file mode 100644
index 0000000..6295fdc
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_array_comma.json
@@ -0,0 +1 @@
+[,
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_open_array_object.json b/test/cases/json_test_suite/n_structure_open_array_object.json
new file mode 100644
index 0000000..e870445
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_array_object.json
@@ -0,0 +1 @@
+[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{"":[{ [...]
diff --git a/test/cases/json_test_suite/n_structure_open_array_open_object.json b/test/cases/json_test_suite/n_structure_open_array_open_object.json
new file mode 100644
index 0000000..7a63c8c
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_array_open_object.json
@@ -0,0 +1 @@
+[{
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_open_array_open_string.json b/test/cases/json_test_suite/n_structure_open_array_open_string.json
new file mode 100644
index 0000000..9822a6b
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_array_open_string.json
@@ -0,0 +1 @@
+["a
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_open_array_string.json b/test/cases/json_test_suite/n_structure_open_array_string.json
new file mode 100644
index 0000000..42a6193
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_array_string.json
@@ -0,0 +1 @@
+["a"
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_open_object.json b/test/cases/json_test_suite/n_structure_open_object.json
new file mode 100644
index 0000000..81750b9
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_object.json
@@ -0,0 +1 @@
+{
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_open_object_close_array.json b/test/cases/json_test_suite/n_structure_open_object_close_array.json
new file mode 100644
index 0000000..eebc700
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_object_close_array.json
@@ -0,0 +1 @@
+{]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_open_object_comma.json b/test/cases/json_test_suite/n_structure_open_object_comma.json
new file mode 100644
index 0000000..47bc910
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_object_comma.json
@@ -0,0 +1 @@
+{,
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_open_object_open_array.json b/test/cases/json_test_suite/n_structure_open_object_open_array.json
new file mode 100644
index 0000000..381ede5
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_object_open_array.json
@@ -0,0 +1 @@
+{[
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_open_object_open_string.json b/test/cases/json_test_suite/n_structure_open_object_open_string.json
new file mode 100644
index 0000000..328c30c
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_object_open_string.json
@@ -0,0 +1 @@
+{"a
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_open_object_string_with_apostrophes.json b/test/cases/json_test_suite/n_structure_open_object_string_with_apostrophes.json
new file mode 100644
index 0000000..9dba170
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_object_string_with_apostrophes.json
@@ -0,0 +1 @@
+{'a'
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_open_open.json b/test/cases/json_test_suite/n_structure_open_open.json
new file mode 100644
index 0000000..841fd5f
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_open_open.json
@@ -0,0 +1 @@
+["\{["\{["\{["\{
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_single_eacute.json b/test/cases/json_test_suite/n_structure_single_eacute.json
new file mode 100644
index 0000000..92a39f3
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_single_eacute.json
@@ -0,0 +1 @@
+�
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_single_star.json b/test/cases/json_test_suite/n_structure_single_star.json
new file mode 100644
index 0000000..f59ec20
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_single_star.json
@@ -0,0 +1 @@
+*
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_trailing_#.json b/test/cases/json_test_suite/n_structure_trailing_#.json
new file mode 100644
index 0000000..8986110
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_trailing_#.json
@@ -0,0 +1 @@
+{"a":"b"}#{}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_uescaped_LF_before_string.json b/test/cases/json_test_suite/n_structure_uescaped_LF_before_string.json
new file mode 100644
index 0000000..df2f0f2
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_uescaped_LF_before_string.json
@@ -0,0 +1 @@
+[\u000A""]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_unclosed_array.json b/test/cases/json_test_suite/n_structure_unclosed_array.json
new file mode 100644
index 0000000..1120951
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_unclosed_array.json
@@ -0,0 +1 @@
+[1
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_unclosed_array_partial_null.json b/test/cases/json_test_suite/n_structure_unclosed_array_partial_null.json
new file mode 100644
index 0000000..0d59176
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_unclosed_array_partial_null.json
@@ -0,0 +1 @@
+[ false, nul
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_unclosed_array_unfinished_false.json b/test/cases/json_test_suite/n_structure_unclosed_array_unfinished_false.json
new file mode 100644
index 0000000..a2ff850
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_unclosed_array_unfinished_false.json
@@ -0,0 +1 @@
+[ true, fals
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_unclosed_array_unfinished_true.json b/test/cases/json_test_suite/n_structure_unclosed_array_unfinished_true.json
new file mode 100644
index 0000000..3149e8f
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_unclosed_array_unfinished_true.json
@@ -0,0 +1 @@
+[ false, tru
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_unclosed_object.json b/test/cases/json_test_suite/n_structure_unclosed_object.json
new file mode 100644
index 0000000..694d69d
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_unclosed_object.json
@@ -0,0 +1 @@
+{"asd":"asd"
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_unicode-identifier.json b/test/cases/json_test_suite/n_structure_unicode-identifier.json
new file mode 100644
index 0000000..7284aea
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_unicode-identifier.json
@@ -0,0 +1 @@
+å
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_whitespace_U+2060_word_joiner.json b/test/cases/json_test_suite/n_structure_whitespace_U+2060_word_joiner.json
new file mode 100644
index 0000000..81156a6
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_whitespace_U+2060_word_joiner.json
@@ -0,0 +1 @@
+[⁠]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/n_structure_whitespace_formfeed.json b/test/cases/json_test_suite/n_structure_whitespace_formfeed.json
new file mode 100644
index 0000000..a9ea535
--- /dev/null
+++ b/test/cases/json_test_suite/n_structure_whitespace_formfeed.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_array_arraysWithSpaces.json b/test/cases/json_test_suite/y_array_arraysWithSpaces.json
new file mode 100644
index 0000000..5822907
--- /dev/null
+++ b/test/cases/json_test_suite/y_array_arraysWithSpaces.json
@@ -0,0 +1 @@
+[[]   ]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_array_empty-string.json b/test/cases/json_test_suite/y_array_empty-string.json
new file mode 100644
index 0000000..93b6be2
--- /dev/null
+++ b/test/cases/json_test_suite/y_array_empty-string.json
@@ -0,0 +1 @@
+[""]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_array_empty.json b/test/cases/json_test_suite/y_array_empty.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/test/cases/json_test_suite/y_array_empty.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_array_ending_with_newline.json b/test/cases/json_test_suite/y_array_ending_with_newline.json
new file mode 100644
index 0000000..eac5f7b
--- /dev/null
+++ b/test/cases/json_test_suite/y_array_ending_with_newline.json
@@ -0,0 +1 @@
+["a"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_array_false.json b/test/cases/json_test_suite/y_array_false.json
new file mode 100644
index 0000000..67b2f07
--- /dev/null
+++ b/test/cases/json_test_suite/y_array_false.json
@@ -0,0 +1 @@
+[false]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_array_heterogeneous.json b/test/cases/json_test_suite/y_array_heterogeneous.json
new file mode 100644
index 0000000..d3c1e26
--- /dev/null
+++ b/test/cases/json_test_suite/y_array_heterogeneous.json
@@ -0,0 +1 @@
+[null, 1, "1", {}]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_array_null.json b/test/cases/json_test_suite/y_array_null.json
new file mode 100644
index 0000000..500db4a
--- /dev/null
+++ b/test/cases/json_test_suite/y_array_null.json
@@ -0,0 +1 @@
+[null]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_array_with_1_and_newline.json b/test/cases/json_test_suite/y_array_with_1_and_newline.json
new file mode 100644
index 0000000..9948255
--- /dev/null
+++ b/test/cases/json_test_suite/y_array_with_1_and_newline.json
@@ -0,0 +1,2 @@
+[1
+]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_array_with_leading_space.json b/test/cases/json_test_suite/y_array_with_leading_space.json
new file mode 100644
index 0000000..18bfe64
--- /dev/null
+++ b/test/cases/json_test_suite/y_array_with_leading_space.json
@@ -0,0 +1 @@
+ [1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_array_with_several_null.json b/test/cases/json_test_suite/y_array_with_several_null.json
new file mode 100644
index 0000000..99f6c5d
--- /dev/null
+++ b/test/cases/json_test_suite/y_array_with_several_null.json
@@ -0,0 +1 @@
+[1,null,null,null,2]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_array_with_trailing_space.json b/test/cases/json_test_suite/y_array_with_trailing_space.json
new file mode 100644
index 0000000..de9e7a9
--- /dev/null
+++ b/test/cases/json_test_suite/y_array_with_trailing_space.json
@@ -0,0 +1 @@
+[2] 
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number.json b/test/cases/json_test_suite/y_number.json
new file mode 100644
index 0000000..e5f5cc3
--- /dev/null
+++ b/test/cases/json_test_suite/y_number.json
@@ -0,0 +1 @@
+[123e65]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_0e+1.json b/test/cases/json_test_suite/y_number_0e+1.json
new file mode 100644
index 0000000..d1d3967
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_0e+1.json
@@ -0,0 +1 @@
+[0e+1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_0e1.json b/test/cases/json_test_suite/y_number_0e1.json
new file mode 100644
index 0000000..3283a79
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_0e1.json
@@ -0,0 +1 @@
+[0e1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_after_space.json b/test/cases/json_test_suite/y_number_after_space.json
new file mode 100644
index 0000000..623570d
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_after_space.json
@@ -0,0 +1 @@
+[ 4]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_double_close_to_zero.json b/test/cases/json_test_suite/y_number_double_close_to_zero.json
new file mode 100644
index 0000000..96555ff
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_double_close_to_zero.json
@@ -0,0 +1 @@
+[-0.000000000000000000000000000000000000000000000000000000000000000000000000000001]
diff --git a/test/cases/json_test_suite/y_number_int_with_exp.json b/test/cases/json_test_suite/y_number_int_with_exp.json
new file mode 100644
index 0000000..a4ca9e7
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_int_with_exp.json
@@ -0,0 +1 @@
+[20e1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_minus_zero.json b/test/cases/json_test_suite/y_number_minus_zero.json
new file mode 100644
index 0000000..37af131
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_minus_zero.json
@@ -0,0 +1 @@
+[-0]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_negative_int.json b/test/cases/json_test_suite/y_number_negative_int.json
new file mode 100644
index 0000000..8e30f8b
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_negative_int.json
@@ -0,0 +1 @@
+[-123]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_negative_one.json b/test/cases/json_test_suite/y_number_negative_one.json
new file mode 100644
index 0000000..99d21a2
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_negative_one.json
@@ -0,0 +1 @@
+[-1]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_negative_zero.json b/test/cases/json_test_suite/y_number_negative_zero.json
new file mode 100644
index 0000000..37af131
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_negative_zero.json
@@ -0,0 +1 @@
+[-0]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_real_capital_e.json b/test/cases/json_test_suite/y_number_real_capital_e.json
new file mode 100644
index 0000000..6edbdfc
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_real_capital_e.json
@@ -0,0 +1 @@
+[1E22]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_real_capital_e_neg_exp.json b/test/cases/json_test_suite/y_number_real_capital_e_neg_exp.json
new file mode 100644
index 0000000..0a01bd3
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_real_capital_e_neg_exp.json
@@ -0,0 +1 @@
+[1E-2]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_real_capital_e_pos_exp.json b/test/cases/json_test_suite/y_number_real_capital_e_pos_exp.json
new file mode 100644
index 0000000..5a8fc09
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_real_capital_e_pos_exp.json
@@ -0,0 +1 @@
+[1E+2]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_real_exponent.json b/test/cases/json_test_suite/y_number_real_exponent.json
new file mode 100644
index 0000000..da2522d
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_real_exponent.json
@@ -0,0 +1 @@
+[123e45]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_real_fraction_exponent.json b/test/cases/json_test_suite/y_number_real_fraction_exponent.json
new file mode 100644
index 0000000..3944a7a
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_real_fraction_exponent.json
@@ -0,0 +1 @@
+[123.456e78]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_real_neg_exp.json b/test/cases/json_test_suite/y_number_real_neg_exp.json
new file mode 100644
index 0000000..ca40d3c
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_real_neg_exp.json
@@ -0,0 +1 @@
+[1e-2]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_real_pos_exponent.json b/test/cases/json_test_suite/y_number_real_pos_exponent.json
new file mode 100644
index 0000000..343601d
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_real_pos_exponent.json
@@ -0,0 +1 @@
+[1e+2]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_simple_int.json b/test/cases/json_test_suite/y_number_simple_int.json
new file mode 100644
index 0000000..e47f69a
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_simple_int.json
@@ -0,0 +1 @@
+[123]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_number_simple_real.json b/test/cases/json_test_suite/y_number_simple_real.json
new file mode 100644
index 0000000..b02878e
--- /dev/null
+++ b/test/cases/json_test_suite/y_number_simple_real.json
@@ -0,0 +1 @@
+[123.456789]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_object.json b/test/cases/json_test_suite/y_object.json
new file mode 100644
index 0000000..78262ed
--- /dev/null
+++ b/test/cases/json_test_suite/y_object.json
@@ -0,0 +1 @@
+{"asd":"sdf", "dfg":"fgh"}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_object_basic.json b/test/cases/json_test_suite/y_object_basic.json
new file mode 100644
index 0000000..646bbe7
--- /dev/null
+++ b/test/cases/json_test_suite/y_object_basic.json
@@ -0,0 +1 @@
+{"asd":"sdf"}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_object_duplicated_key.json b/test/cases/json_test_suite/y_object_duplicated_key.json
new file mode 100644
index 0000000..bbc2e1c
--- /dev/null
+++ b/test/cases/json_test_suite/y_object_duplicated_key.json
@@ -0,0 +1 @@
+{"a":"b","a":"c"}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_object_duplicated_key_and_value.json b/test/cases/json_test_suite/y_object_duplicated_key_and_value.json
new file mode 100644
index 0000000..211581c
--- /dev/null
+++ b/test/cases/json_test_suite/y_object_duplicated_key_and_value.json
@@ -0,0 +1 @@
+{"a":"b","a":"b"}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_object_empty.json b/test/cases/json_test_suite/y_object_empty.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/test/cases/json_test_suite/y_object_empty.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_object_empty_key.json b/test/cases/json_test_suite/y_object_empty_key.json
new file mode 100644
index 0000000..c0013d3
--- /dev/null
+++ b/test/cases/json_test_suite/y_object_empty_key.json
@@ -0,0 +1 @@
+{"":0}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_object_escaped_null_in_key.json b/test/cases/json_test_suite/y_object_escaped_null_in_key.json
new file mode 100644
index 0000000..593f0f6
--- /dev/null
+++ b/test/cases/json_test_suite/y_object_escaped_null_in_key.json
@@ -0,0 +1 @@
+{"foo\u0000bar": 42}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_object_extreme_numbers.json b/test/cases/json_test_suite/y_object_extreme_numbers.json
new file mode 100644
index 0000000..a0d3531
--- /dev/null
+++ b/test/cases/json_test_suite/y_object_extreme_numbers.json
@@ -0,0 +1 @@
+{ "min": -1.0e+28, "max": 1.0e+28 }
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_object_long_strings.json b/test/cases/json_test_suite/y_object_long_strings.json
new file mode 100644
index 0000000..bdc4a08
--- /dev/null
+++ b/test/cases/json_test_suite/y_object_long_strings.json
@@ -0,0 +1 @@
+{"x":[{"id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}], "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_object_simple.json b/test/cases/json_test_suite/y_object_simple.json
new file mode 100644
index 0000000..dacac91
--- /dev/null
+++ b/test/cases/json_test_suite/y_object_simple.json
@@ -0,0 +1 @@
+{"a":[]}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_object_string_unicode.json b/test/cases/json_test_suite/y_object_string_unicode.json
new file mode 100644
index 0000000..8effdb2
--- /dev/null
+++ b/test/cases/json_test_suite/y_object_string_unicode.json
@@ -0,0 +1 @@
+{"title":"\u041f\u043e\u043b\u0442\u043e\u0440\u0430 \u0417\u0435\u043c\u043b\u0435\u043a\u043e\u043f\u0430" }
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_object_with_newlines.json b/test/cases/json_test_suite/y_object_with_newlines.json
new file mode 100644
index 0000000..246ec6b
--- /dev/null
+++ b/test/cases/json_test_suite/y_object_with_newlines.json
@@ -0,0 +1,3 @@
+{
+"a": "b"
+}
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_1_2_3_bytes_UTF-8_sequences.json b/test/cases/json_test_suite/y_string_1_2_3_bytes_UTF-8_sequences.json
new file mode 100644
index 0000000..9967dde
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_1_2_3_bytes_UTF-8_sequences.json
@@ -0,0 +1 @@
+["\u0060\u012a\u12AB"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_accepted_surrogate_pair.json b/test/cases/json_test_suite/y_string_accepted_surrogate_pair.json
new file mode 100644
index 0000000..996875c
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_accepted_surrogate_pair.json
@@ -0,0 +1 @@
+["\uD801\udc37"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_accepted_surrogate_pairs.json b/test/cases/json_test_suite/y_string_accepted_surrogate_pairs.json
new file mode 100644
index 0000000..3401021
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_accepted_surrogate_pairs.json
@@ -0,0 +1 @@
+["\ud83d\ude39\ud83d\udc8d"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_allowed_escapes.json b/test/cases/json_test_suite/y_string_allowed_escapes.json
new file mode 100644
index 0000000..7f49553
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_allowed_escapes.json
@@ -0,0 +1 @@
+["\"\\\/\b\f\n\r\t"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_backslash_and_u_escaped_zero.json b/test/cases/json_test_suite/y_string_backslash_and_u_escaped_zero.json
new file mode 100644
index 0000000..d4439ed
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_backslash_and_u_escaped_zero.json
@@ -0,0 +1 @@
+["\\u0000"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_backslash_doublequotes.json b/test/cases/json_test_suite/y_string_backslash_doublequotes.json
new file mode 100644
index 0000000..ae03243
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_backslash_doublequotes.json
@@ -0,0 +1 @@
+["\""]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_comments.json b/test/cases/json_test_suite/y_string_comments.json
new file mode 100644
index 0000000..2260c20
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_comments.json
@@ -0,0 +1 @@
+["a/*b*/c/*d//e"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_double_escape_a.json b/test/cases/json_test_suite/y_string_double_escape_a.json
new file mode 100644
index 0000000..6715d6f
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_double_escape_a.json
@@ -0,0 +1 @@
+["\\a"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_double_escape_n.json b/test/cases/json_test_suite/y_string_double_escape_n.json
new file mode 100644
index 0000000..44ca56c
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_double_escape_n.json
@@ -0,0 +1 @@
+["\\n"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_escaped_control_character.json b/test/cases/json_test_suite/y_string_escaped_control_character.json
new file mode 100644
index 0000000..5b014a9
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_escaped_control_character.json
@@ -0,0 +1 @@
+["\u0012"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_escaped_noncharacter.json b/test/cases/json_test_suite/y_string_escaped_noncharacter.json
new file mode 100644
index 0000000..2ff52e2
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_escaped_noncharacter.json
@@ -0,0 +1 @@
+["\uFFFF"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_in_array.json b/test/cases/json_test_suite/y_string_in_array.json
new file mode 100644
index 0000000..21d7ae4
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_in_array.json
@@ -0,0 +1 @@
+["asd"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_in_array_with_leading_space.json b/test/cases/json_test_suite/y_string_in_array_with_leading_space.json
new file mode 100644
index 0000000..9e1887c
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_in_array_with_leading_space.json
@@ -0,0 +1 @@
+[ "asd"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_last_surrogates_1_and_2.json b/test/cases/json_test_suite/y_string_last_surrogates_1_and_2.json
new file mode 100644
index 0000000..3919cef
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_last_surrogates_1_and_2.json
@@ -0,0 +1 @@
+["\uDBFF\uDFFF"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_nbsp_uescaped.json b/test/cases/json_test_suite/y_string_nbsp_uescaped.json
new file mode 100644
index 0000000..2085ab1
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_nbsp_uescaped.json
@@ -0,0 +1 @@
+["new\u00A0line"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_nonCharacterInUTF-8_U+10FFFF.json b/test/cases/json_test_suite/y_string_nonCharacterInUTF-8_U+10FFFF.json
new file mode 100644
index 0000000..059e4d9
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_nonCharacterInUTF-8_U+10FFFF.json
@@ -0,0 +1 @@
+["􏿿"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_nonCharacterInUTF-8_U+FFFF.json b/test/cases/json_test_suite/y_string_nonCharacterInUTF-8_U+FFFF.json
new file mode 100644
index 0000000..4c913bd
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_nonCharacterInUTF-8_U+FFFF.json
@@ -0,0 +1 @@
+["￿"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_null_escape.json b/test/cases/json_test_suite/y_string_null_escape.json
new file mode 100644
index 0000000..c1ad844
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_null_escape.json
@@ -0,0 +1 @@
+["\u0000"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_one-byte-utf-8.json b/test/cases/json_test_suite/y_string_one-byte-utf-8.json
new file mode 100644
index 0000000..1571859
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_one-byte-utf-8.json
@@ -0,0 +1 @@
+["\u002c"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_pi.json b/test/cases/json_test_suite/y_string_pi.json
new file mode 100644
index 0000000..9df11ae
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_pi.json
@@ -0,0 +1 @@
+["π"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_reservedCharacterInUTF-8_U+1BFFF.json b/test/cases/json_test_suite/y_string_reservedCharacterInUTF-8_U+1BFFF.json
new file mode 100644
index 0000000..10a33a1
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_reservedCharacterInUTF-8_U+1BFFF.json
@@ -0,0 +1 @@
+["𛿿"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_simple_ascii.json b/test/cases/json_test_suite/y_string_simple_ascii.json
new file mode 100644
index 0000000..8cadf7d
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_simple_ascii.json
@@ -0,0 +1 @@
+["asd "]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_space.json b/test/cases/json_test_suite/y_string_space.json
new file mode 100644
index 0000000..efd782c
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_space.json
@@ -0,0 +1 @@
+" "
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json b/test/cases/json_test_suite/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json
new file mode 100644
index 0000000..7620b66
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_surrogates_U+1D11E_MUSICAL_SYMBOL_G_CLEF.json
@@ -0,0 +1 @@
+["\uD834\uDd1e"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_three-byte-utf-8.json b/test/cases/json_test_suite/y_string_three-byte-utf-8.json
new file mode 100644
index 0000000..108f1d6
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_three-byte-utf-8.json
@@ -0,0 +1 @@
+["\u0821"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_two-byte-utf-8.json b/test/cases/json_test_suite/y_string_two-byte-utf-8.json
new file mode 100644
index 0000000..461503c
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_two-byte-utf-8.json
@@ -0,0 +1 @@
+["\u0123"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_u+2028_line_sep.json b/test/cases/json_test_suite/y_string_u+2028_line_sep.json
new file mode 100644
index 0000000..897b602
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_u+2028_line_sep.json
@@ -0,0 +1 @@
+["
"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_u+2029_par_sep.json b/test/cases/json_test_suite/y_string_u+2029_par_sep.json
new file mode 100644
index 0000000..8cd998c
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_u+2029_par_sep.json
@@ -0,0 +1 @@
+["
"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_uEscape.json b/test/cases/json_test_suite/y_string_uEscape.json
new file mode 100644
index 0000000..f7b41a0
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_uEscape.json
@@ -0,0 +1 @@
+["\u0061\u30af\u30EA\u30b9"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_uescaped_newline.json b/test/cases/json_test_suite/y_string_uescaped_newline.json
new file mode 100644
index 0000000..3a5a220
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_uescaped_newline.json
@@ -0,0 +1 @@
+["new\u000Aline"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_unescaped_char_delete.json b/test/cases/json_test_suite/y_string_unescaped_char_delete.json
new file mode 100644
index 0000000..7d064f4
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_unescaped_char_delete.json
@@ -0,0 +1 @@
+[""]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_unicode.json b/test/cases/json_test_suite/y_string_unicode.json
new file mode 100644
index 0000000..3598095
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_unicode.json
@@ -0,0 +1 @@
+["\uA66D"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_unicodeEscapedBackslash.json b/test/cases/json_test_suite/y_string_unicodeEscapedBackslash.json
new file mode 100644
index 0000000..0bb3b51
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_unicodeEscapedBackslash.json
@@ -0,0 +1 @@
+["\u005C"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_unicode_2.json b/test/cases/json_test_suite/y_string_unicode_2.json
new file mode 100644
index 0000000..a7dcb97
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_unicode_2.json
@@ -0,0 +1 @@
+["⍂㈴⍂"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_unicode_U+10FFFE_nonchar.json b/test/cases/json_test_suite/y_string_unicode_U+10FFFE_nonchar.json
new file mode 100644
index 0000000..9a8370b
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_unicode_U+10FFFE_nonchar.json
@@ -0,0 +1 @@
+["\uDBFF\uDFFE"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_unicode_U+1FFFE_nonchar.json b/test/cases/json_test_suite/y_string_unicode_U+1FFFE_nonchar.json
new file mode 100644
index 0000000..c51f8ae
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_unicode_U+1FFFE_nonchar.json
@@ -0,0 +1 @@
+["\uD83F\uDFFE"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json b/test/cases/json_test_suite/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json
new file mode 100644
index 0000000..626d5f8
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_unicode_U+200B_ZERO_WIDTH_SPACE.json
@@ -0,0 +1 @@
+["\u200B"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_unicode_U+2064_invisible_plus.json b/test/cases/json_test_suite/y_string_unicode_U+2064_invisible_plus.json
new file mode 100644
index 0000000..1e23972
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_unicode_U+2064_invisible_plus.json
@@ -0,0 +1 @@
+["\u2064"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_unicode_U+FDD0_nonchar.json b/test/cases/json_test_suite/y_string_unicode_U+FDD0_nonchar.json
new file mode 100644
index 0000000..18ef151
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_unicode_U+FDD0_nonchar.json
@@ -0,0 +1 @@
+["\uFDD0"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_unicode_U+FFFE_nonchar.json b/test/cases/json_test_suite/y_string_unicode_U+FFFE_nonchar.json
new file mode 100644
index 0000000..13d261f
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_unicode_U+FFFE_nonchar.json
@@ -0,0 +1 @@
+["\uFFFE"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_unicode_escaped_double_quote.json b/test/cases/json_test_suite/y_string_unicode_escaped_double_quote.json
new file mode 100644
index 0000000..4e62578
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_unicode_escaped_double_quote.json
@@ -0,0 +1 @@
+["\u0022"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_utf8.json b/test/cases/json_test_suite/y_string_utf8.json
new file mode 100644
index 0000000..4087843
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_utf8.json
@@ -0,0 +1 @@
+["€𝄞"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_string_with_del_character.json b/test/cases/json_test_suite/y_string_with_del_character.json
new file mode 100644
index 0000000..8bd2490
--- /dev/null
+++ b/test/cases/json_test_suite/y_string_with_del_character.json
@@ -0,0 +1 @@
+["aa"]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_structure_lonely_false.json b/test/cases/json_test_suite/y_structure_lonely_false.json
new file mode 100644
index 0000000..02e4a84
--- /dev/null
+++ b/test/cases/json_test_suite/y_structure_lonely_false.json
@@ -0,0 +1 @@
+false
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_structure_lonely_int.json b/test/cases/json_test_suite/y_structure_lonely_int.json
new file mode 100644
index 0000000..f70d7bb
--- /dev/null
+++ b/test/cases/json_test_suite/y_structure_lonely_int.json
@@ -0,0 +1 @@
+42
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_structure_lonely_negative_real.json b/test/cases/json_test_suite/y_structure_lonely_negative_real.json
new file mode 100644
index 0000000..b5135a2
--- /dev/null
+++ b/test/cases/json_test_suite/y_structure_lonely_negative_real.json
@@ -0,0 +1 @@
+-0.1
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_structure_lonely_null.json b/test/cases/json_test_suite/y_structure_lonely_null.json
new file mode 100644
index 0000000..ec747fa
--- /dev/null
+++ b/test/cases/json_test_suite/y_structure_lonely_null.json
@@ -0,0 +1 @@
+null
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_structure_lonely_string.json b/test/cases/json_test_suite/y_structure_lonely_string.json
new file mode 100644
index 0000000..b6e982c
--- /dev/null
+++ b/test/cases/json_test_suite/y_structure_lonely_string.json
@@ -0,0 +1 @@
+"asd"
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_structure_lonely_true.json b/test/cases/json_test_suite/y_structure_lonely_true.json
new file mode 100644
index 0000000..f32a580
--- /dev/null
+++ b/test/cases/json_test_suite/y_structure_lonely_true.json
@@ -0,0 +1 @@
+true
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_structure_string_empty.json b/test/cases/json_test_suite/y_structure_string_empty.json
new file mode 100644
index 0000000..3cc762b
--- /dev/null
+++ b/test/cases/json_test_suite/y_structure_string_empty.json
@@ -0,0 +1 @@
+""
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_structure_trailing_newline.json b/test/cases/json_test_suite/y_structure_trailing_newline.json
new file mode 100644
index 0000000..0c3426d
--- /dev/null
+++ b/test/cases/json_test_suite/y_structure_trailing_newline.json
@@ -0,0 +1 @@
+["a"]
diff --git a/test/cases/json_test_suite/y_structure_true_in_array.json b/test/cases/json_test_suite/y_structure_true_in_array.json
new file mode 100644
index 0000000..de601e3
--- /dev/null
+++ b/test/cases/json_test_suite/y_structure_true_in_array.json
@@ -0,0 +1 @@
+[true]
\ No newline at end of file
diff --git a/test/cases/json_test_suite/y_structure_whitespace_array.json b/test/cases/json_test_suite/y_structure_whitespace_array.json
new file mode 100644
index 0000000..2bedf7f
--- /dev/null
+++ b/test/cases/json_test_suite/y_structure_whitespace_array.json
@@ -0,0 +1 @@
+ [] 
\ No newline at end of file
diff --git a/test/jiffy_22_json_test_suite_tests.erl b/test/jiffy_22_json_test_suite_tests.erl
new file mode 100644
index 0000000..8a71ab1
--- /dev/null
+++ b/test/jiffy_22_json_test_suite_tests.erl
@@ -0,0 +1,101 @@
+% This file is part of Jiffy released under the MIT license.
+% See the LICENSE file for more information.
+%
+% Tests from https://github.com/nst/JSONTestSuite
+%
+% y_ : valid JSON, must be accepted
+% n_ : invalid JSON, must be rejected
+% i_ : implementation-defined, can do whatever
+
+-module(jiffy_22_json_test_suite_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+-include("jiffy_util.hrl").
+
+json_test_suite_test_() ->
+    Files = read_files(),
+    {Y, N, I} = classify(Files),
+    [
+        {"y_ (accepts)", [gen_accept(F) || F <- Y]},
+        {"n_ (rejects)", [gen_reject(F) || F <- N]},
+        {"i_ (whatevs)", [gen_whatever(F) || F <- I]}
+    ].
+
+
+gen_accept({Name, Json}) ->
+    {Name, fun() ->
+        Dec = jiffy:decode(Json),
+        % A few round-trips
+        Enc = jiffy:encode(Dec),
+        Dec1 = jiffy:decode(Enc),
+        ?assertEqual(Dec1, Dec),
+        % Can decode with maps
+        DecMap = jiffy:decode(Json, [return_maps]),
+        EncMap = jiffy:encode(DecMap),
+        Dec2 = jiffy:decode(EncMap, [return_maps]),
+        ?assertEqual(Dec2, DecMap)
+    end}.
+
+
+gen_reject({Name, Json}) ->
+    {Name, fun() ->
+        ?assertError({N, _} when is_integer(N), jiffy:decode(Json)),
+        ?assertError({N, _} when is_integer(N), jiffy:decode(Json, [return_maps]))
+    end}.
+
+
+% These are implementation. If we accept it let's see if we can
+% round trip them at least
+%
+gen_whatever({Name, Json}) ->
+    {Name, fun() ->
+        try jiffy:decode(Json) of
+            Dec ->
+                Enc = jiffy:encode(Dec),
+                Dec1 = jiffy:decode(Enc),
+                ?assertEqual(Dec1, Dec)
+        catch
+            error:_ -> ok
+        end
+    end}.
+
+
+% Jiffy is linient here and we deviate from strict RFC 8259:
+%  - accept truncated exponents e.g. 0.3e+ is 0.3 for us so
+%    add an exception for it
+%
+known_deviations() ->
+    [
+        "n_number_0.3e+",
+        "n_number_0_capital_E+",
+        "n_number_0e+",
+        "n_number_1.0e+",
+        "n_number_1.0e-"
+    ].
+
+
+classify(Files) ->
+    classify(Files, [], [], []).
+
+classify([], Y, N, I) ->
+    {lists:reverse(Y), lists:reverse(N), lists:reverse(I)};
+classify([{Name, _Json} = F | Rest], Y, N, I) ->
+    case {Name, lists:member(Name, known_deviations())} of
+        {_, true}    -> classify(Rest, Y, N, [F | I]);
+        {"y_" ++ _, _} -> classify(Rest, [F | Y], N, I);
+        {"n_" ++ _, _} -> classify(Rest, Y, [F | N], I);
+        {"i_" ++ _, _} -> classify(Rest, Y, N, [F | I]);
+        _              -> classify(Rest, Y, N, I)
+    end.
+
+
+read_files() ->
+    Pattern = cases_path("json_test_suite/*.json"),
+    FileNames = lists:sort(filelib:wildcard(Pattern)),
+    [make_entry(F) || F <- FileNames].
+
+
+make_entry(FileName) ->
+    {ok, Json} = file:read_file(FileName),
+    Name = filename:basename(FileName, ".json"),
+    {Name, Json}.


(couchdb-jiffy) 35/49: Test with the Big List of Naughty Strings (BLNS)

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 56117fab3aa5d57989787ee643791061efad28f1
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Sat Apr 11 00:12:50 2026 -0400

    Test with the Big List of Naughty Strings (BLNS)
    
    From https://github.com/minimaxir/big-list-of-naughty-strings
---
 test/cases/blns.txt                     | 742 ++++++++++++++++++++++++++++++++
 test/jiffy_21_naughty_strings_tests.erl |  25 ++
 2 files changed, 767 insertions(+)

diff --git a/test/cases/blns.txt b/test/cases/blns.txt
new file mode 100644
index 0000000..7c27bbd
--- /dev/null
+++ b/test/cases/blns.txt
@@ -0,0 +1,742 @@
+#	Reserved Strings
+#
+#	Strings which may be used elsewhere in code
+
+undefined
+undef
+null
+NULL
+(null)
+nil
+NIL
+true
+false
+True
+False
+TRUE
+FALSE
+None
+hasOwnProperty
+then
+constructor
+\
+\\
+
+#	Numeric Strings
+#
+#	Strings which can be interpreted as numeric
+
+0
+1
+1.00
+$1.00
+1/2
+1E2
+1E02
+1E+02
+-1
+-1.00
+-$1.00
+-1/2
+-1E2
+-1E02
+-1E+02
+1/0
+0/0
+-2147483648/-1
+-9223372036854775808/-1
+-0
+-0.0
++0
++0.0
+0.00
+0..0
+.
+0.0.0
+0,00
+0,,0
+,
+0,0,0
+0.0/0
+1.0/0.0
+0.0/0.0
+1,0/0,0
+0,0/0,0
+--1
+-
+-.
+-,
+999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999
+NaN
+Infinity
+-Infinity
+INF
+1#INF
+-1#IND
+1#QNAN
+1#SNAN
+1#IND
+0x0
+0xffffffff
+0xffffffffffffffff
+0xabad1dea
+123456789012345678901234567890123456789
+1,000.00
+1 000.00
+1'000.00
+1,000,000.00
+1 000 000.00
+1'000'000.00
+1.000,00
+1 000,00
+1'000,00
+1.000.000,00
+1 000 000,00
+1'000'000,00
+01000
+08
+09
+2.2250738585072011e-308
+
+#	Special Characters
+#
+# ASCII punctuation.  All of these characters may need to be escaped in some
+# contexts.  Divided into three groups based on (US-layout) keyboard position.
+
+,./;'[]\-=
+<>?:"{}|_+
+!@#$%^&*()`~
+
+# Non-whitespace C0 controls: U+0001 through U+0008, U+000E through U+001F,
+# and U+007F (DEL)
+# Often forbidden to appear in various text-based file formats (e.g. XML),
+# or reused for internal delimiters on the theory that they should never
+# appear in input.
+# The next line may appear to be blank or mojibake in some viewers.
+
+
+# Non-whitespace C1 controls: U+0080 through U+0084 and U+0086 through U+009F.
+# Commonly misinterpreted as additional graphic characters.
+# The next line may appear to be blank, mojibake, or dingbats in some viewers.
+€‚ƒ„†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ
+
+# Whitespace: all of the characters with category Zs, Zl, or Zp (in Unicode
+# version 8.0.0), plus U+0009 (HT), U+000B (VT), U+000C (FF), U+0085 (NEL),
+# and U+200B (ZERO WIDTH SPACE), which are in the C categories but are often
+# treated as whitespace in some contexts.
+# This file unfortunately cannot express strings containing
+# U+0000, U+000A, or U+000D (NUL, LF, CR).
+# The next line may appear to be blank or mojibake in some viewers.
+# The next line may be flagged for "trailing whitespace" in some viewers.
+	 …             ​

   
+
+# Unicode additional control characters: all of the characters with
+# general category Cf (in Unicode 8.0.0).
+# The next line may appear to be blank or mojibake in some viewers.
+­؀؁؂؃؄؅؜۝܏᠎​‌‍‎‏‪‫‬‭‮⁠⁡⁢⁣⁤⁦⁧⁨⁩𑂽𛲠𛲡𛲢𛲣𝅳𝅴𝅵𝅶𝅷𝅸𝅹𝅺󠀁󠀠󠀡󠀢󠀣󠀤󠀥󠀦󠀧󠀨󠀩󠀪󠀫󠀬󠀭󠀮󠀯󠀰󠀱󠀲󠀳󠀴󠀵󠀶󠀷󠀸󠀹󠀺󠀻󠀼󠀽󠀾󠀿󠁀󠁁󠁂󠁃󠁄󠁅󠁆󠁇󠁈󠁉󠁊󠁋󠁌󠁍󠁎󠁏󠁐󠁑󠁒󠁓󠁔󠁕󠁖󠁗󠁘󠁙󠁚󠁛󠁜󠁝󠁞󠁟󠁠󠁡󠁢󠁣󠁤󠁥󠁦󠁧󠁨󠁩󠁪󠁫󠁬󠁭󠁮󠁯󠁰󠁱󠁲󠁳󠁴󠁵󠁶󠁷󠁸󠁹󠁺󠁻󠁼󠁽󠁾󠁿
+
+# "Byte order marks", U+FEFF and U+FFFE, each on its own line.
+# The next two lines may appear to be blank or mojibake in some viewers.
+
+￾
+
+#	Unicode Symbols
+#
+#	Strings which contain common unicode symbols (e.g. smart quotes)
+
+Ω≈ç√∫˜µ≤≥÷
+åß∂ƒ©˙∆˚¬…æ
+œ∑´®†¥¨ˆøπ“‘
+¡™£¢∞§¶•ªº–≠
+¸˛Ç◊ı˜Â¯˘¿
+ÅÍÎÏ˝ÓÔÒÚÆ☃
+Œ„´‰ˇÁ¨ˆØ∏”’
+`⁄€‹›fifl‡°·‚—±
+⅛⅜⅝⅞
+ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя
+٠١٢٣٤٥٦٧٨٩
+
+#	Unicode Subscript/Superscript/Accents
+#
+#	Strings which contain unicode subscripts/superscripts; can cause rendering issues
+
+⁰⁴⁵
+₀₁₂
+⁰⁴⁵₀₁₂
+ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็
+
+#	Quotation Marks
+#
+#	Strings which contain misplaced quotation marks; can cause encoding errors
+
+'
+"
+''
+""
+'"'
+"''''"'"
+"'"'"''''"
+<foo val=“bar” />
+<foo val=“bar” />
+<foo val=”bar“ />
+<foo val=`bar' />
+
+#	Two-Byte Characters
+#
+#	Strings which contain two-byte characters: can cause rendering issues or character-length issues
+
+田中さんにあげて下さい
+パーティーへ行かないか
+和製漢語
+部落格
+사회과학원 어학연구소
+찦차를 타고 온 펲시맨과 쑛다리 똠방각하
+社會科學院語學研究所
+울란바토르
+𠜎𠜱𠝹𠱓𠱸𠲖𠳏
+
+#	Strings which contain two-byte letters: can cause issues with naïve UTF-16 capitalizers which think that 16 bits == 1 character
+
+𐐜 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐙𐐊𐐡𐐝𐐓/𐐝𐐇𐐗𐐊𐐤𐐔 𐐒𐐋𐐗 𐐒𐐌 𐐜 𐐡𐐀𐐖𐐇𐐤𐐓𐐝 𐐱𐑂 𐑄 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐏𐐆𐐅𐐤𐐆𐐚𐐊𐐡𐐝𐐆𐐓𐐆
+
+#	Special Unicode Characters Union
+#
+#	A super string recommended by VMware Inc. Globalization Team: can effectively cause rendering issues or character-length issues to validate product globalization readiness.
+#
+#	表          CJK_UNIFIED_IDEOGRAPHS (U+8868)
+#	ポ          KATAKANA LETTER PO (U+30DD)
+#	あ          HIRAGANA LETTER A (U+3042)
+#	A           LATIN CAPITAL LETTER A (U+0041)
+#	鷗          CJK_UNIFIED_IDEOGRAPHS (U+9DD7)
+#	Œ           LATIN SMALL LIGATURE OE (U+0153) 
+#	é           LATIN SMALL LETTER E WITH ACUTE (U+00E9)
+#	B           FULLWIDTH LATIN CAPITAL LETTER B (U+FF22)
+#	逍          CJK_UNIFIED_IDEOGRAPHS (U+900D)
+#	Ü           LATIN SMALL LETTER U WITH DIAERESIS (U+00FC)
+#	ß           LATIN SMALL LETTER SHARP S (U+00DF)
+#	ª           FEMININE ORDINAL INDICATOR (U+00AA)
+#	ą           LATIN SMALL LETTER A WITH OGONEK (U+0105)
+#	ñ           LATIN SMALL LETTER N WITH TILDE (U+00F1)
+#	丂          CJK_UNIFIED_IDEOGRAPHS (U+4E02)
+#	㐀          CJK Ideograph Extension A, First (U+3400)
+#	𠀀          CJK Ideograph Extension B, First (U+20000)
+
+表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀
+
+#	Changing length when lowercased
+#
+#	Characters which increase in length (2 to 3 bytes) when lowercased
+#	Credit: https://twitter.com/jifa/status/625776454479970304
+
+Ⱥ
+Ⱦ
+
+#	Japanese Emoticons
+#
+#	Strings which consists of Japanese-style emoticons which are popular on the web
+
+ヽ༼ຈل͜ຈ༽ノ ヽ༼ຈل͜ຈ༽ノ
+(。◕ ∀ ◕。)
+`ィ(´∀`∩
+__ロ(,_,*)
+・( ̄∀ ̄)・:*:
+゚・✿ヾ╲(。◕‿◕。)╱✿・゚
+,。・:*:・゜’( ☻ ω ☻ )。・:*:・゜’
+(╯°□°)╯︵ ┻━┻)
+(ノಥ益ಥ)ノ ┻━┻
+┬─┬ノ( º _ ºノ)
+( ͡° ͜ʖ ͡°)
+¯\_(ツ)_/¯
+
+#	Emoji
+#
+#	Strings which contain Emoji; should be the same behavior as two-byte characters, but not always
+
+😍
+👩🏽
+👨‍🦰 👨🏿‍🦰 👨‍🦱 👨🏿‍🦱 🦹🏿‍♂️
+👾 🙇 💁 🙅 🙆 🙋 🙎 🙍
+🐵 🙈 🙉 🙊
+❤️ 💔 💌 💕 💞 💓 💗 💖 💘 💝 💟 💜 💛 💚 💙
+✋🏿 💪🏿 👐🏿 🙌🏿 👏🏿 🙏🏿
+👨‍👩‍👦 👨‍👩‍👧‍👦 👨‍👨‍👦 👩‍👩‍👧 👨‍👦 👨‍👧‍👦 👩‍👦 👩‍👧‍👦
+🚾 🆒 🆓 🆕 🆖 🆗 🆙 🏧
+0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟
+
+#       Regional Indicator Symbols
+#
+#       Regional Indicator Symbols can be displayed differently across
+#       fonts, and have a number of special behaviors
+
+🇺🇸🇷🇺🇸 🇦🇫🇦🇲🇸
+🇺🇸🇷🇺🇸🇦🇫🇦🇲
+🇺🇸🇷🇺🇸🇦
+
+#	Unicode Numbers
+#
+#	Strings which contain unicode numbers; if the code is localized, it should see the input as numeric
+
+123
+١٢٣
+
+#	Right-To-Left Strings
+#
+#	Strings which contain text that should be rendered RTL if possible (e.g. Arabic, Hebrew)
+
+ثم نفس سقطت وبالتحديد،, جزيرتي باستخدام أن دنو. إذ هنا؟ الستار وتنصيب كان. أهّل ايطاليا، بريطانيا-فرنسا قد أخذ. سليمان، إتفاقية بين ما, يذكر الحدود أي بعد, معاملة بولندا، الإطلاق عل إيو.
+בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ
+הָיְתָהtestالصفحات التّحول
+﷽
+ﷺ
+مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ،
+الكل في المجمو عة (5)
+
+#	Ogham Text
+#
+#	The only unicode alphabet to use a space which isn't empty but should still act like a space.
+
+᚛ᚄᚓᚐᚋᚒᚄ ᚑᚄᚂᚑᚏᚅ᚜
+᚛                 ᚜
+
+#	Trick Unicode
+#
+#	Strings which contain unicode with unusual properties (e.g. Right-to-left override) (c.f. http://www.unicode.org/charts/PDF/U2000.pdf)
+
+‪‪test‪
+‫test‫
+
test

+test⁠test‫
+⁦test⁧
+
+#	Zalgo Text
+#
+#	Strings which contain "corrupted" text. The corruption will not appear in non-HTML text, however. (via http://www.eeemo.net)
+
+Ṱ̺̺̕o͞ ̷i̲̬͇̪͙n̝̗͕v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ ̖t̝͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤v̻͍e̺̭̳̪̰-m̢iͅn̖̺̞̲̯̰d̵̼̟͙̩̼̘̳ ̞̥̱̳̭r̛̗̘e͙p͠r̼̞̻̭̗e̺̠̣͟s̘͇̳͍̝͉e͉̥̯̞̲͚̬͜ǹ̬͎͎̟̖͇̤t͍̬̤͓̼̭͘ͅi̪̱n͠g̴͉ ͏͉ͅc̬̟h͡a̫̻̯͘o̫̟̖͍̙̝͉s̗̦̲.̨̹͈̣
+̡͓̞ͅI̗̘̦͝n͇͇͙v̮̫ok̲̫̙͈i̖͙̭̹̠̞n̡̻̮̣̺g̲͈͙̭͙̬͎ ̰t͔̦h̞̲e̢̤ ͍̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠n͖͍̗͓̳̮g͍ ̨o͚̪͡f̘̣̬ ̖̘͖̟͙̮c҉͔̫͖͓͇͖ͅh̵̤̣͚͔á̗̼͕ͅo̼̣̥s̱͈̺̖̦̻͢.̛̖̞̠̫̰
+̗̺͖̹̯͓Ṯ̤͍̥͇͈h̲́e͏͓̼̗̙̼̣͔ ͇̜̱̠͓͍ͅN͕͠e̗̱z̘̝̜̺͙p̤̺̹͍̯͚e̠̻̠͜r̨̤͍̺̖͔̖̖d̠̟̭̬̝͟i̦͖̩͓͔̤a̠̗̬͉̙n͚͜ ̻̞̰͚ͅh̵͉i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇̝̦n̗͙ḍ̟ ̯̲͕͞ǫ̟̯̰̲͙̻̝f ̪̰̰̗̖̭̘͘c̦͍̲̞͍̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.̝̝ ҉Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳͍̩g̡̟̼̱͚̞̬ͅo̗͜.̟
+̦H̬̤̗̤͝e͜ ̜̥̝̻͍̟́w̕h̖̯͓o̝͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪͍į͈͕̭͙̯̜t̶̼̮s̘͙͖̕ ̠̫̠B̻͍͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕n͟d̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢h͏͓̮̻e̬̝̟ͅ ̤̹̝W͙̞̝͔͇͝ͅa͏͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.͕
+Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮
+
+#	Unicode Upsidedown
+#
+#	Strings which contain unicode with an "upsidedown" effect (via http://www.upsidedowntext.com)
+
+˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs 'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ 'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥
+00˙Ɩ$-
+
+#	Unicode font
+#
+#	Strings which contain bold/italic/etc. versions of normal characters
+
+The quick brown fox jumps over the lazy dog
+𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠
+𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌
+𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈
+𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰
+𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘
+𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐
+⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢
+
+#	Script Injection
+#
+#	Strings which attempt to invoke a benign script injection; shows vulnerability to XSS
+
+<script>alert(0)</script>
+&lt;script&gt;alert(&#39;1&#39;);&lt;/script&gt;
+<img src=x onerror=alert(2) />
+<svg><script>123<1>alert(3)</script>
+"><script>alert(4)</script>
+'><script>alert(5)</script>
+><script>alert(6)</script>
+</script><script>alert(7)</script>
+< / script >< script >alert(8)< / script >
+ onfocus=JaVaSCript:alert(9) autofocus
+" onfocus=JaVaSCript:alert(10) autofocus
+' onfocus=JaVaSCript:alert(11) autofocus
+<script>alert(12)</script>
+<sc<script>ript>alert(13)</sc</script>ript>
+--><script>alert(14)</script>
+";alert(15);t="
+';alert(16);t='
+JavaSCript:alert(17)
+;alert(18);
+src=JaVaSCript:prompt(19)
+"><script>alert(20);</script x="
+'><script>alert(21);</script x='
+><script>alert(22);</script x=
+" autofocus onkeyup="javascript:alert(23)
+' autofocus onkeyup='javascript:alert(24)
+<script\x20type="text/javascript">javascript:alert(25);</script>
+<script\x3Etype="text/javascript">javascript:alert(26);</script>
+<script\x0Dtype="text/javascript">javascript:alert(27);</script>
+<script\x09type="text/javascript">javascript:alert(28);</script>
+<script\x0Ctype="text/javascript">javascript:alert(29);</script>
+<script\x2Ftype="text/javascript">javascript:alert(30);</script>
+<script\x0Atype="text/javascript">javascript:alert(31);</script>
+'`"><\x3Cscript>javascript:alert(32)</script>
+'`"><\x00script>javascript:alert(33)</script>
+ABC<div style="x\x3Aexpression(javascript:alert(34)">DEF
+ABC<div style="x:expression\x5C(javascript:alert(35)">DEF
+ABC<div style="x:expression\x00(javascript:alert(36)">DEF
+ABC<div style="x:exp\x00ression(javascript:alert(37)">DEF
+ABC<div style="x:exp\x5Cression(javascript:alert(38)">DEF
+ABC<div style="x:\x0Aexpression(javascript:alert(39)">DEF
+ABC<div style="x:\x09expression(javascript:alert(40)">DEF
+ABC<div style="x:\xE3\x80\x80expression(javascript:alert(41)">DEF
+ABC<div style="x:\xE2\x80\x84expression(javascript:alert(42)">DEF
+ABC<div style="x:\xC2\xA0expression(javascript:alert(43)">DEF
+ABC<div style="x:\xE2\x80\x80expression(javascript:alert(44)">DEF
+ABC<div style="x:\xE2\x80\x8Aexpression(javascript:alert(45)">DEF
+ABC<div style="x:\x0Dexpression(javascript:alert(46)">DEF
+ABC<div style="x:\x0Cexpression(javascript:alert(47)">DEF
+ABC<div style="x:\xE2\x80\x87expression(javascript:alert(48)">DEF
+ABC<div style="x:\xEF\xBB\xBFexpression(javascript:alert(49)">DEF
+ABC<div style="x:\x20expression(javascript:alert(50)">DEF
+ABC<div style="x:\xE2\x80\x88expression(javascript:alert(51)">DEF
+ABC<div style="x:\x00expression(javascript:alert(52)">DEF
+ABC<div style="x:\xE2\x80\x8Bexpression(javascript:alert(53)">DEF
+ABC<div style="x:\xE2\x80\x86expression(javascript:alert(54)">DEF
+ABC<div style="x:\xE2\x80\x85expression(javascript:alert(55)">DEF
+ABC<div style="x:\xE2\x80\x82expression(javascript:alert(56)">DEF
+ABC<div style="x:\x0Bexpression(javascript:alert(57)">DEF
+ABC<div style="x:\xE2\x80\x81expression(javascript:alert(58)">DEF
+ABC<div style="x:\xE2\x80\x83expression(javascript:alert(59)">DEF
+ABC<div style="x:\xE2\x80\x89expression(javascript:alert(60)">DEF
+<a href="\x0Bjavascript:javascript:alert(61)" id="fuzzelement1">test</a>
+<a href="\x0Fjavascript:javascript:alert(62)" id="fuzzelement1">test</a>
+<a href="\xC2\xA0javascript:javascript:alert(63)" id="fuzzelement1">test</a>
+<a href="\x05javascript:javascript:alert(64)" id="fuzzelement1">test</a>
+<a href="\xE1\xA0\x8Ejavascript:javascript:alert(65)" id="fuzzelement1">test</a>
+<a href="\x18javascript:javascript:alert(66)" id="fuzzelement1">test</a>
+<a href="\x11javascript:javascript:alert(67)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x88javascript:javascript:alert(68)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x89javascript:javascript:alert(69)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x80javascript:javascript:alert(70)" id="fuzzelement1">test</a>
+<a href="\x17javascript:javascript:alert(71)" id="fuzzelement1">test</a>
+<a href="\x03javascript:javascript:alert(72)" id="fuzzelement1">test</a>
+<a href="\x0Ejavascript:javascript:alert(73)" id="fuzzelement1">test</a>
+<a href="\x1Ajavascript:javascript:alert(74)" id="fuzzelement1">test</a>
+<a href="\x00javascript:javascript:alert(75)" id="fuzzelement1">test</a>
+<a href="\x10javascript:javascript:alert(76)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x82javascript:javascript:alert(77)" id="fuzzelement1">test</a>
+<a href="\x20javascript:javascript:alert(78)" id="fuzzelement1">test</a>
+<a href="\x13javascript:javascript:alert(79)" id="fuzzelement1">test</a>
+<a href="\x09javascript:javascript:alert(80)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x8Ajavascript:javascript:alert(81)" id="fuzzelement1">test</a>
+<a href="\x14javascript:javascript:alert(82)" id="fuzzelement1">test</a>
+<a href="\x19javascript:javascript:alert(83)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\xAFjavascript:javascript:alert(84)" id="fuzzelement1">test</a>
+<a href="\x1Fjavascript:javascript:alert(85)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x81javascript:javascript:alert(86)" id="fuzzelement1">test</a>
+<a href="\x1Djavascript:javascript:alert(87)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x87javascript:javascript:alert(88)" id="fuzzelement1">test</a>
+<a href="\x07javascript:javascript:alert(89)" id="fuzzelement1">test</a>
+<a href="\xE1\x9A\x80javascript:javascript:alert(90)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x83javascript:javascript:alert(91)" id="fuzzelement1">test</a>
+<a href="\x04javascript:javascript:alert(92)" id="fuzzelement1">test</a>
+<a href="\x01javascript:javascript:alert(93)" id="fuzzelement1">test</a>
+<a href="\x08javascript:javascript:alert(94)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x84javascript:javascript:alert(95)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x86javascript:javascript:alert(96)" id="fuzzelement1">test</a>
+<a href="\xE3\x80\x80javascript:javascript:alert(97)" id="fuzzelement1">test</a>
+<a href="\x12javascript:javascript:alert(98)" id="fuzzelement1">test</a>
+<a href="\x0Djavascript:javascript:alert(99)" id="fuzzelement1">test</a>
+<a href="\x0Ajavascript:javascript:alert(100)" id="fuzzelement1">test</a>
+<a href="\x0Cjavascript:javascript:alert(101)" id="fuzzelement1">test</a>
+<a href="\x15javascript:javascript:alert(102)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\xA8javascript:javascript:alert(103)" id="fuzzelement1">test</a>
+<a href="\x16javascript:javascript:alert(104)" id="fuzzelement1">test</a>
+<a href="\x02javascript:javascript:alert(105)" id="fuzzelement1">test</a>
+<a href="\x1Bjavascript:javascript:alert(106)" id="fuzzelement1">test</a>
+<a href="\x06javascript:javascript:alert(107)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\xA9javascript:javascript:alert(108)" id="fuzzelement1">test</a>
+<a href="\xE2\x80\x85javascript:javascript:alert(109)" id="fuzzelement1">test</a>
+<a href="\x1Ejavascript:javascript:alert(110)" id="fuzzelement1">test</a>
+<a href="\xE2\x81\x9Fjavascript:javascript:alert(111)" id="fuzzelement1">test</a>
+<a href="\x1Cjavascript:javascript:alert(112)" id="fuzzelement1">test</a>
+<a href="javascript\x00:javascript:alert(113)" id="fuzzelement1">test</a>
+<a href="javascript\x3A:javascript:alert(114)" id="fuzzelement1">test</a>
+<a href="javascript\x09:javascript:alert(115)" id="fuzzelement1">test</a>
+<a href="javascript\x0D:javascript:alert(116)" id="fuzzelement1">test</a>
+<a href="javascript\x0A:javascript:alert(117)" id="fuzzelement1">test</a>
+`"'><img src=xxx:x \x0Aonerror=javascript:alert(118)>
+`"'><img src=xxx:x \x22onerror=javascript:alert(119)>
+`"'><img src=xxx:x \x0Bonerror=javascript:alert(120)>
+`"'><img src=xxx:x \x0Donerror=javascript:alert(121)>
+`"'><img src=xxx:x \x2Fonerror=javascript:alert(122)>
+`"'><img src=xxx:x \x09onerror=javascript:alert(123)>
+`"'><img src=xxx:x \x0Conerror=javascript:alert(124)>
+`"'><img src=xxx:x \x00onerror=javascript:alert(125)>
+`"'><img src=xxx:x \x27onerror=javascript:alert(126)>
+`"'><img src=xxx:x \x20onerror=javascript:alert(127)>
+"`'><script>\x3Bjavascript:alert(128)</script>
+"`'><script>\x0Djavascript:alert(129)</script>
+"`'><script>\xEF\xBB\xBFjavascript:alert(130)</script>
+"`'><script>\xE2\x80\x81javascript:alert(131)</script>
+"`'><script>\xE2\x80\x84javascript:alert(132)</script>
+"`'><script>\xE3\x80\x80javascript:alert(133)</script>
+"`'><script>\x09javascript:alert(134)</script>
+"`'><script>\xE2\x80\x89javascript:alert(135)</script>
+"`'><script>\xE2\x80\x85javascript:alert(136)</script>
+"`'><script>\xE2\x80\x88javascript:alert(137)</script>
+"`'><script>\x00javascript:alert(138)</script>
+"`'><script>\xE2\x80\xA8javascript:alert(139)</script>
+"`'><script>\xE2\x80\x8Ajavascript:alert(140)</script>
+"`'><script>\xE1\x9A\x80javascript:alert(141)</script>
+"`'><script>\x0Cjavascript:alert(142)</script>
+"`'><script>\x2Bjavascript:alert(143)</script>
+"`'><script>\xF0\x90\x96\x9Ajavascript:alert(144)</script>
+"`'><script>-javascript:alert(145)</script>
+"`'><script>\x0Ajavascript:alert(146)</script>
+"`'><script>\xE2\x80\xAFjavascript:alert(147)</script>
+"`'><script>\x7Ejavascript:alert(148)</script>
+"`'><script>\xE2\x80\x87javascript:alert(149)</script>
+"`'><script>\xE2\x81\x9Fjavascript:alert(150)</script>
+"`'><script>\xE2\x80\xA9javascript:alert(151)</script>
+"`'><script>\xC2\x85javascript:alert(152)</script>
+"`'><script>\xEF\xBF\xAEjavascript:alert(153)</script>
+"`'><script>\xE2\x80\x83javascript:alert(154)</script>
+"`'><script>\xE2\x80\x8Bjavascript:alert(155)</script>
+"`'><script>\xEF\xBF\xBEjavascript:alert(156)</script>
+"`'><script>\xE2\x80\x80javascript:alert(157)</script>
+"`'><script>\x21javascript:alert(158)</script>
+"`'><script>\xE2\x80\x82javascript:alert(159)</script>
+"`'><script>\xE2\x80\x86javascript:alert(160)</script>
+"`'><script>\xE1\xA0\x8Ejavascript:alert(161)</script>
+"`'><script>\x0Bjavascript:alert(162)</script>
+"`'><script>\x20javascript:alert(163)</script>
+"`'><script>\xC2\xA0javascript:alert(164)</script>
+<img \x00src=x onerror="alert(165)">
+<img \x47src=x onerror="javascript:alert(166)">
+<img \x11src=x onerror="javascript:alert(167)">
+<img \x12src=x onerror="javascript:alert(168)">
+<img\x47src=x onerror="javascript:alert(169)">
+<img\x10src=x onerror="javascript:alert(170)">
+<img\x13src=x onerror="javascript:alert(171)">
+<img\x32src=x onerror="javascript:alert(172)">
+<img\x47src=x onerror="javascript:alert(173)">
+<img\x11src=x onerror="javascript:alert(174)">
+<img \x47src=x onerror="javascript:alert(175)">
+<img \x34src=x onerror="javascript:alert(176)">
+<img \x39src=x onerror="javascript:alert(177)">
+<img \x00src=x onerror="javascript:alert(178)">
+<img src\x09=x onerror="javascript:alert(179)">
+<img src\x10=x onerror="javascript:alert(180)">
+<img src\x13=x onerror="javascript:alert(181)">
+<img src\x32=x onerror="javascript:alert(182)">
+<img src\x12=x onerror="javascript:alert(183)">
+<img src\x11=x onerror="javascript:alert(184)">
+<img src\x00=x onerror="javascript:alert(185)">
+<img src\x47=x onerror="javascript:alert(186)">
+<img src=x\x09onerror="javascript:alert(187)">
+<img src=x\x10onerror="javascript:alert(188)">
+<img src=x\x11onerror="javascript:alert(189)">
+<img src=x\x12onerror="javascript:alert(190)">
+<img src=x\x13onerror="javascript:alert(191)">
+<img[a][b][c]src[d]=x[e]onerror=[f]"alert(192)">
+<img src=x onerror=\x09"javascript:alert(193)">
+<img src=x onerror=\x10"javascript:alert(194)">
+<img src=x onerror=\x11"javascript:alert(195)">
+<img src=x onerror=\x12"javascript:alert(196)">
+<img src=x onerror=\x32"javascript:alert(197)">
+<img src=x onerror=\x00"javascript:alert(198)">
+<a href=java&#1&#2&#3&#4&#5&#6&#7&#8&#11&#12script:javascript:alert(199)>XXX</a>
+<img src="x` `<script>javascript:alert(200)</script>"` `>
+<img src onerror /" '"= alt=javascript:alert(201)//">
+<title onpropertychange=javascript:alert(202)></title><title title=>
+<a href=http://foo.bar/#x=`y></a><img alt="`><img src=x:x onerror=javascript:alert(203)></a>">
+<!--[if]><script>javascript:alert(204)</script -->
+<!--[if<img src=x onerror=javascript:alert(205)//]> -->
+<script src="/\%(jscript)s"></script>
+<script src="\\%(jscript)s"></script>
+<IMG """><SCRIPT>alert("206")</SCRIPT>">
+<IMG SRC=javascript:alert(String.fromCharCode(50,48,55))>
+<IMG SRC=# onmouseover="alert('208')">
+<IMG SRC= onmouseover="alert('209')">
+<IMG onmouseover="alert('210')">
+<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#50;&#49;&#49;&#39;&#41;>
+<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000050&#0000049&#0000050&#0000039&#0000041>
+<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x32&#x31&#x33&#x27&#x29>
+<IMG SRC="jav   ascript:alert('214');">
+<IMG SRC="jav&#x09;ascript:alert('215');">
+<IMG SRC="jav&#x0A;ascript:alert('216');">
+<IMG SRC="jav&#x0D;ascript:alert('217');">
+perl -e 'print "<IMG SRC=java\0script:alert(\"218\")>";' > out
+<IMG SRC=" &#14;  javascript:alert('219');">
+<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>
+<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("220")>
+<SCRIPT/SRC="http://ha.ckers.org/xss.js"></SCRIPT>
+<<SCRIPT>alert("221");//<</SCRIPT>
+<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >
+<SCRIPT SRC=//ha.ckers.org/.j>
+<IMG SRC="javascript:alert('222')"
+<iframe src=http://ha.ckers.org/scriptlet.html <
+\";alert('223');//
+<u oncopy=alert()> Copy me</u>
+<i onwheel=alert(224)> Scroll over me </i>
+<plaintext>
+http://a/%%30%30
+</textarea><script>alert(225)</script>
+
+#	SQL Injection
+#
+#	Strings which can cause a SQL injection if inputs are not sanitized
+
+1;DROP TABLE users
+1'; DROP TABLE users-- 1
+' OR 1=1 -- 1
+' OR '1'='1
+'; EXEC sp_MSForEachTable 'DROP TABLE ?'; --
+ 
+%
+_
+
+#	Server Code Injection
+#
+#	Strings which can cause user to run code on server as a privileged user (c.f. https://news.ycombinator.com/item?id=7665153)
+
+-
+--
+--version
+--help
+$USER
+/dev/null; touch /tmp/blns.fail ; echo
+`touch /tmp/blns.fail`
+$(touch /tmp/blns.fail)
+@{[system "touch /tmp/blns.fail"]}
+
+#	Command Injection (Ruby)
+#
+#	Strings which can call system commands within Ruby/Rails applications
+
+eval("puts 'hello world'")
+System("ls -al /")
+`ls -al /`
+Kernel.exec("ls -al /")
+Kernel.exit(1)
+%x('ls -al /')
+
+#      XXE Injection (XML)
+#
+#	String which can reveal system files when parsed by a badly configured XML parser
+
+<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>
+
+#	Unwanted Interpolation
+#
+#	Strings which can be accidentally expanded into different strings if evaluated in the wrong context, e.g. used as a printf format string or via Perl or shell eval. Might expose sensitive data from the program doing the interpolation, or might just represent the wrong string.
+
+$HOME
+$ENV{'HOME'}
+%d
+%s%s%s%s%s
+{0}
+%*.*s
+%@
+%n
+File:///
+
+#	File Inclusion
+#
+#	Strings which can cause user to pull in files that should not be a part of a web server
+
+../../../../../../../../../../../etc/passwd%00
+../../../../../../../../../../../etc/hosts
+
+#	Known CVEs and Vulnerabilities
+#
+#	Strings that test for known vulnerabilities
+
+() { 0; }; touch /tmp/blns.shellshock1.fail;
+() { _; } >_[$($())] { touch /tmp/blns.shellshock2.fail; }
+<<< %s(un='%s') = %u
++++ATH0
+
+#	MSDOS/Windows Special Filenames
+#
+#	Strings which are reserved characters in MSDOS/Windows
+
+CON
+PRN
+AUX
+CLOCK$
+NUL
+A:
+ZZ:
+COM1
+LPT1
+LPT2
+LPT3
+COM2
+COM3
+COM4
+
+#   IRC specific strings
+#
+#   Strings that may occur on IRC clients that make security products freak out
+
+DCC SEND STARTKEYLOGGER 0 0 0
+
+#	Scunthorpe Problem
+#
+#	Innocuous strings which may be blocked by profanity filters (https://en.wikipedia.org/wiki/Scunthorpe_problem)
+
+Scunthorpe General Hospital
+Penistone Community Church
+Lightwater Country Park
+Jimmy Clitheroe
+Horniman Museum
+shitake mushrooms
+RomansInSussex.co.uk
+http://www.cum.qc.ca/
+Craig Cockburn, Software Specialist
+Linda Callahan
+Dr. Herman I. Libshitz
+magna cum laude
+Super Bowl XXX
+medieval erection of parapets
+evaluate
+mocha
+expression
+Arsenal canal
+classic
+Tyson Gay
+Dick Van Dyke
+basement
+
+#	Human injection
+#
+#	Strings which may cause human to reinterpret worldview
+
+If you're reading this, you've been in a coma for almost 20 years now. We're trying a new technique. We don't know where this message will end up in your dream, but we hope it works. Please wake up, we miss you.
+
+#	Terminal escape codes
+#
+#	Strings which punish the fools who use cat/type on this file
+
+Roses are red, violets are blue. Hope you enjoy terminal hue
+But now...for my greatest trick...
+The quick brown fox... [Beeeep]
+
+#	iOS Vulnerabilities
+#
+#	Strings which crashed iMessage in various versions of iOS
+
+Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗
+🏳0🌈️
+జ్ఞ‌ా
+
+# Persian special characters
+#
+# This is a four characters string which includes Persian special characters (گچپژ)
+
+گچپژ
+
+# jinja2 injection
+#
+# first one is supposed to raise "MemoryError" exception
+# second, obviously, prints contents of /etc/passwd
+
+{% print 'x' * 64 * 1024**3 %}
+{{ "".__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read() }}
diff --git a/test/jiffy_21_naughty_strings_tests.erl b/test/jiffy_21_naughty_strings_tests.erl
new file mode 100644
index 0000000..39035ee
--- /dev/null
+++ b/test/jiffy_21_naughty_strings_tests.erl
@@ -0,0 +1,25 @@
+% This file is part of Jiffy released under the MIT license.
+% See the LICENSE file for more information.
+
+% Test the Big List of Naughty Strings (BLNS)
+% https://github.com/minimaxir/big-list-of-naughty-strings
+
+-module(jiffy_21_naughty_strings_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+-include("jiffy_util.hrl").
+
+naughty_strings_test() ->
+    lists:foreach(fun t_string/1, load_strings()).
+
+t_string(String) ->
+    ?assertEqual(String, jiffy:decode(jiffy:encode(String))),
+    ForceEnc = jiffy:encode(String, [force_utf8]),
+    ?assertEqual(String, jiffy:decode(ForceEnc)).
+
+load_strings() ->
+    FilePath = cases_path("blns.txt"),
+    {ok, Binary} = file:read_file(FilePath),
+    Lines = binary:split(Binary, <<"\n">>, [global]),
+    % Skip blank lines and comments
+    [Line || Line <- Lines, byte_size(Line) > 0, binary:first(Line) =/= $#].


(couchdb-jiffy) 15/49: Gate the restrict macro with a C version check

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit b699032dcd9447ac435f393cc2adf94025052d3a
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Wed Apr 1 00:12:54 2026 -0400

    Gate the restrict macro with a C version check
    
    For those who are using pre-C99 C compiler, it won't have the `restrict`
    keyword, so define it as an empty macro. If it's undefined the compiler just
    won't do the optimization but it should still work fine.
    
    While monkeying around with macros do some other cleanups:
    
     1) Noticed that some macros were not used at all, some were redefined per
     module when a single definition in jiffy.h would be better
    
     2) `termstack` functions are only used in encode.c, so there is no need to
     compile a separate module for it. Those are just a handful of function so move
     them to `encode.c` file. This way if folks don't have link-time optimization
     set up, they'll still get pretty much the same kind of optimization from the
     inline hints and all the code being in the same module.
---
 c_src/decoder.c   |  6 +---
 c_src/encoder.c   | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 c_src/jiffy.h     | 11 +++++++
 c_src/termstack.c | 87 ----------------------------------------------------
 c_src/termstack.h | 34 --------------------
 5 files changed, 99 insertions(+), 131 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 93efd90..0b440d9 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -13,10 +13,6 @@
 #define STACK_SIZE_INC 64
 #define NUM_BUF_LEN 32
 
-#if WINDOWS || WIN32
-#define snprintf  _snprintf
-#endif
-
 enum {
     st_value=0,
     st_object,
@@ -273,7 +269,7 @@ dec_string(Decoder* d, ERL_NIF_TERM* value)
             }
         } else if(d->p[d->i] < 0x80) {
             // Scan ahead plain ASCII as an optimization
-            const unsigned char* restrict p = d->p;
+            const unsigned char* JIFFY_RESTRICT p = d->p;
             size_t idx = d->i + 1;
             const size_t len = d->len;
             while(idx < len
diff --git a/c_src/encoder.c b/c_src/encoder.c
index 29ff860..0cf1b08 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -6,12 +6,13 @@
 #include <string.h>
 
 #include "jiffy.h"
-#include "termstack.h"
 
 #define BIN_INC_SIZE 2048
 
 #define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
 
+#define SMALL_TERMSTACK_SIZE 16
+
 #define MAYBE_PRETTY(e)             \
 do {                                \
     if(e->pretty) {                 \
@@ -20,10 +21,13 @@ do {                                \
     }                               \
 } while(0)
 
-#if WINDOWS || WIN32
-#define inline __inline
-#define snprintf  _snprintf
-#endif
+typedef struct {
+    ERL_NIF_TERM* elements;
+    size_t size;
+    size_t top;
+
+    ERL_NIF_TERM __default_elements[SMALL_TERMSTACK_SIZE];
+} TermStack;
 
 typedef struct {
     ErlNifEnv*      env;
@@ -66,6 +70,84 @@ static char* shifts[NUM_SHIFTS] = {
 };
 
 
+static inline ERL_NIF_TERM
+termstack_save(ErlNifEnv* env, TermStack* stack)
+{
+    return enif_make_tuple_from_array(env, stack->elements, stack->top);
+}
+
+static inline int
+termstack_restore(ErlNifEnv* env, ERL_NIF_TERM from, TermStack* stack)
+{
+    const ERL_NIF_TERM* elements;
+    int arity;
+
+    if(enif_get_tuple(env, from, &arity, &elements)) {
+        stack->top = arity;
+
+        if(arity <= SMALL_TERMSTACK_SIZE) {
+            stack->elements = &stack->__default_elements[0];
+            stack->size = SMALL_TERMSTACK_SIZE;
+        } else {
+            stack->size = arity * 2;
+            stack->elements = enif_alloc(stack->size * sizeof(ERL_NIF_TERM));
+
+            if(!stack->elements) {
+                return 0;
+            }
+        }
+
+        memcpy(stack->elements, elements, arity * sizeof(ERL_NIF_TERM));
+        return 1;
+    }
+
+    return 0;
+}
+
+static inline void
+termstack_destroy(TermStack* stack)
+{
+    if(stack->elements != &stack->__default_elements[0]) {
+        enif_free(stack->elements);
+    }
+}
+
+static inline void
+termstack_push(TermStack* stack, ERL_NIF_TERM term)
+{
+
+    if(stack->top == stack->size) {
+        size_t new_size = stack->size * 2;
+        size_t num_bytes = new_size * sizeof(ERL_NIF_TERM);
+
+        if (stack->elements == &stack->__default_elements[0]) {
+            ERL_NIF_TERM* elems = enif_alloc(num_bytes);
+            memcpy(elems, stack->elements, num_bytes);
+            stack->elements = elems;
+        } else {
+            stack->elements = enif_realloc(stack->elements, num_bytes);
+        }
+
+        stack->size = new_size;
+    }
+
+    assert(stack->top < stack->size);
+    stack->elements[stack->top++] = term;
+}
+
+static inline ERL_NIF_TERM
+termstack_pop(TermStack* stack)
+{
+    assert(stack->top > 0 && stack->top <= stack->size);
+    return stack->elements[--stack->top];
+}
+
+static inline int
+termstack_is_empty(TermStack* stack)
+{
+    return stack->top == 0;
+}
+
 static Encoder*
 enc_new(ErlNifEnv* env)
 {
diff --git a/c_src/jiffy.h b/c_src/jiffy.h
index af755da..a0bcc28 100644
--- a/c_src/jiffy.h
+++ b/c_src/jiffy.h
@@ -9,6 +9,17 @@
 #define DEFAULT_BYTES_PER_REDUCTION 20
 #define DEFAULT_ERLANG_REDUCTION_COUNT 2000
 
+// Check for C99
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+  #define JIFFY_RESTRICT restrict
+#else
+  #define JIFFY_RESTRICT
+#endif
+
+#if WINDOWS || WIN32
+  #define inline __inline
+#endif
+
 typedef struct {
     ERL_NIF_TERM    atom_ok;
     ERL_NIF_TERM    atom_error;
diff --git a/c_src/termstack.c b/c_src/termstack.c
deleted file mode 100644
index a558b0a..0000000
--- a/c_src/termstack.c
+++ /dev/null
@@ -1,87 +0,0 @@
-// This file is part of Jiffy released under the MIT license.
-// See the LICENSE file for more information.
-
-#include <assert.h>
-#include <stdio.h>
-#include <string.h>
-
-#include "jiffy.h"
-#include "termstack.h"
-
-ERL_NIF_TERM
-termstack_save(ErlNifEnv* env, TermStack* stack)
-{
-    return enif_make_tuple_from_array(env, stack->elements, stack->top);
-}
-
-int
-termstack_restore(ErlNifEnv* env, ERL_NIF_TERM from, TermStack* stack)
-{
-    const ERL_NIF_TERM* elements;
-    int arity;
-
-    if(enif_get_tuple(env, from, &arity, &elements)) {
-        stack->top = arity;
-
-        if(arity <= SMALL_TERMSTACK_SIZE) {
-            stack->elements = &stack->__default_elements[0];
-            stack->size = SMALL_TERMSTACK_SIZE;
-        } else {
-            stack->size = arity * 2;
-            stack->elements = enif_alloc(stack->size * sizeof(ERL_NIF_TERM));
-
-            if(!stack->elements) {
-                return 0;
-            }
-        }
-
-        memcpy(stack->elements, elements, arity * sizeof(ERL_NIF_TERM));
-        return 1;
-    }
-
-    return 0;
-}
-
-void
-termstack_destroy(TermStack* stack)
-{
-    if(stack->elements != &stack->__default_elements[0]) {
-        enif_free(stack->elements);
-    }
-}
-
-inline void
-termstack_push(TermStack* stack, ERL_NIF_TERM term)
-{
-
-    if(stack->top == stack->size) {
-        size_t new_size = stack->size * 2;
-        size_t num_bytes = new_size * sizeof(ERL_NIF_TERM);
-
-        if (stack->elements == &stack->__default_elements[0]) {
-            ERL_NIF_TERM* elems = enif_alloc(num_bytes);
-            memcpy(elems, stack->elements, num_bytes);
-            stack->elements = elems;
-        } else {
-            stack->elements = enif_realloc(stack->elements, num_bytes);
-        }
-
-        stack->size = new_size;
-    }
-
-    assert(stack->top < stack->size);
-    stack->elements[stack->top++] = term;
-}
-
-inline ERL_NIF_TERM
-termstack_pop(TermStack* stack)
-{
-    assert(stack->top > 0 && stack->top <= stack->size);
-    return stack->elements[--stack->top];
-}
-
-inline int
-termstack_is_empty(TermStack* stack)
-{
-    return stack->top == 0;
-}
diff --git a/c_src/termstack.h b/c_src/termstack.h
deleted file mode 100644
index d1f8ee4..0000000
--- a/c_src/termstack.h
+++ /dev/null
@@ -1,34 +0,0 @@
-// This file is part of Jiffy released under the MIT license.
-// See the LICENSE file for more information.
-
-#ifndef TERMSTACK_H
-#define TERMSTACK_H
-
-#include "erl_nif.h"
-
-#ifdef _WIN32
-#define JIFFY_EXPORT __declspec(dllexport)
-#else
-#define JIFFY_EXPORT
-#endif
-
-#define SMALL_TERMSTACK_SIZE 16
-
-typedef struct {
-    ERL_NIF_TERM* elements;
-    size_t size;
-    size_t top;
-
-    ERL_NIF_TERM __default_elements[SMALL_TERMSTACK_SIZE];
-} TermStack;
-
-
-ERL_NIF_TERM termstack_save(ErlNifEnv* env, TermStack* stack);
-int termstack_restore(ErlNifEnv* env, ERL_NIF_TERM from, TermStack* stack);
-void termstack_destroy(TermStack* stack);
-
-JIFFY_EXPORT void termstack_push(TermStack* stack, ERL_NIF_TERM term);
-JIFFY_EXPORT ERL_NIF_TERM termstack_pop(TermStack* stack);
-JIFFY_EXPORT int termstack_is_empty(TermStack* stack);
-
-#endif


(couchdb-jiffy) 03/49: Add specific gcc and clang versions to CI

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 229c05bf4b0b1e96886bd1f0d0f9e90e7687ef2a
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Sun Mar 22 01:13:08 2026 -0400

    Add specific gcc and clang versions to CI
    
    While at it add macos tahoe as well
---
 .github/workflows/ci.yml | 38 ++++++++++++++++++++++++++++++++++----
 1 file changed, 34 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2f811b1..7501cea 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -25,11 +25,41 @@ jobs:
       image: erlang:${{matrix.otp_version}}
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
     - name: ${{matrix.rebar}} make check
       run: |
         make REBAR=${{matrix.rebar}} check
 
+  gcc-14:
+    name: gcc-14
+    runs-on: ubuntu-latest
+    container:
+      image: erlang:28
+    steps:
+      - uses: actions/checkout@v6
+      - name: install gcc-14
+        run: |
+          apt update
+          apt install -y --no-install-recommends --no-install-suggests gcc-14 g++-14
+      - name: make check
+        run: |
+          CC=gcc-14 CXX=g++-14 CFLAGS="-Wextra -Wno-unused-parameter" make check
+
+  clang-19:
+    name: clang-19
+    runs-on: ubuntu-latest
+    container:
+      image: erlang:28
+    steps:
+      - uses: actions/checkout@v6
+      - name: install clang 20
+        run: |
+          apt update
+          apt install -y --no-install-recommends --no-install-suggests clang-19
+      - name: make check
+        run: |
+          CC=clang-19 CXX=clang++-19 CFLAGS="-Wextra -Wno-unused-parameter" make check
+
   macos:
     name: macos
     runs-on: ${{matrix.os}}
@@ -37,10 +67,10 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        os: [macos-14, macos-15, macos-latest]
+        os: [macos-14, macos-15, macos-26, macos-26-intel, macos-latest]
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
     - name: install erlang
       run: |
         brew install erlang
@@ -65,7 +95,7 @@ jobs:
       run: |
           git config --global core.autocrlf false
           git config --global core.eol lf
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@v6
     - name: install rebar3
       run: |
         choco install rebar3


(couchdb-jiffy) 32/49: Add some NIF lifecyle coverage

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit a529615e4cd40e3ec857ce2e7c2f334ef777a0eb
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Fri Apr 10 14:31:11 2026 -0400

    Add some NIF lifecyle coverage
    
    Not 100% if these ever worked right. Resources do have `ERL_NIF_RT_TAKEOVER`
    specified and we end up re-creating the private context on upgrade so at least
    let's try to test them with a few purge and reload checks.
    
    Another alternative is to make these `NULL`s and just say that upgrade is not
    supported.
---
 test/jiffy_20_lifecycle_tests.erl | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/test/jiffy_20_lifecycle_tests.erl b/test/jiffy_20_lifecycle_tests.erl
new file mode 100644
index 0000000..f29af85
--- /dev/null
+++ b/test/jiffy_20_lifecycle_tests.erl
@@ -0,0 +1,29 @@
+% This file is part of Jiffy released under the MIT license.
+% See the LICENSE file for more information.
+
+-module(jiffy_20_lifecycle_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+
+%% This is more of covarage hacking but we're trying exercise some of the
+%% lifecycle callback in jiffy.c
+
+upgrade_test() ->
+    % baseline
+    ?assertEqual(1, jiffy:decode(<<"1">>)),
+    ?assertEqual(<<"1">>, iolist_to_binary(jiffy:encode(1))),
+
+    % soft purge and reload
+    code:purge(jiffy),
+    {module, jiffy} = code:load_file(jiffy),
+
+    % verify
+    ?assertEqual(1, jiffy:decode(<<"1">>)),
+    ?assertEqual(<<"1">>, iolist_to_binary(jiffy:encode(1))),
+
+    % another purge should now evict the old module
+    code:purge(jiffy),
+
+    % verify
+    ?assertEqual(1, jiffy:decode(<<"1">>)),
+    ?assertEqual(<<"1">>, iolist_to_binary(jiffy:encode(1))).


(couchdb-jiffy) 23/49: Add bench folder to gitignore

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 13686ba4e39d35d317ebc4fd0ad44a08d339c119
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Thu Apr 2 17:08:41 2026 -0400

    Add bench folder to gitignore
---
 .gitignore | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitignore b/.gitignore
index 590e2b1..8ddf166 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ hexer.config
 rebar.lock
 TEST-*.xml
 /eqc/
+/bench/
 coverage.info
 coverage-jiffy.info
 coverage-html/


(couchdb-jiffy) 21/49: Bump yielding reduction count to 4000

Posted by va...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

nickva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb-jiffy.git

commit 0e5c71c878f3774f98801c9201e6ab311ca88e41
Author: Nick Vatamaniuc <va...@gmail.com>
AuthorDate: Thu Apr 2 14:59:00 2026 -0400

    Bump yielding reduction count to 4000
    
    Also avoid using a division and a separate function call for bumping
    reductions. Instead make it static inline and use a multiplication instead.
    
    [1] It's the new value in OTP since 19.2.
     OTP commit:
     https://github.com/erlang/otp/commit/6bcdd45abd97134fddfb5b0307b1d256337b0c67
     Doc mention:
     https://www.erlang.org/doc/apps/erts/erlang.html#bump_reductions/1
---
 c_src/decoder.c | 4 +++-
 c_src/encoder.c | 4 +++-
 c_src/jiffy.h   | 9 +++++++--
 c_src/util.c    | 6 ------
 4 files changed, 13 insertions(+), 10 deletions(-)

diff --git a/c_src/decoder.c b/c_src/decoder.c
index 5021630..46239db 100644
--- a/c_src/decoder.c
+++ b/c_src/decoder.c
@@ -756,10 +756,12 @@ decode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
 
     start = d->i;
 
+    const size_t yt = yield_threshold(d->bytes_per_red);
+
     while(d->i < bin.size) {
         bytes_processed = d->i - start;
 
-        if(should_yield(bytes_processed, d->bytes_per_red)) {
+        if(bytes_processed >= yt) {
             assert(enif_is_list(env, objs));
             assert(enif_is_list(env, curr));
 
diff --git a/c_src/encoder.c b/c_src/encoder.c
index 96f2c24..f331290 100644
--- a/c_src/encoder.c
+++ b/c_src/encoder.c
@@ -862,10 +862,12 @@ encode_iter(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
 
     start = e->iosize + e->i;
 
+    const size_t yt = yield_threshold(e->bytes_per_red);
+
     while(!termstack_is_empty(&stack)) {
         bytes_processed = (e->iosize + e->i) - start;
 
-        if(should_yield(bytes_processed, e->bytes_per_red)) {
+        if(bytes_processed >= yt) {
 
             assert(enif_is_list(env, e->iolist));
 
diff --git a/c_src/jiffy.h b/c_src/jiffy.h
index a0bcc28..19f8365 100644
--- a/c_src/jiffy.h
+++ b/c_src/jiffy.h
@@ -7,7 +7,10 @@
 #include "erl_nif.h"
 
 #define DEFAULT_BYTES_PER_REDUCTION 20
-#define DEFAULT_ERLANG_REDUCTION_COUNT 2000
+
+// This used to be 2000 and in 19.2 was bumped to 4000
+// #define CONTEXT_REDS in erts/emulator/beam/erl_vm.h
+#define DEFAULT_ERLANG_REDUCTION_COUNT 4000
 
 // Check for C99
 #if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
@@ -57,7 +60,9 @@ ERL_NIF_TERM make_atom(ErlNifEnv* env, const char* name);
 int get_bytes_per_iter(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpi);
 int get_bytes_per_red(ErlNifEnv* env, ERL_NIF_TERM val, size_t* bpr);
 int get_null_term(ErlNifEnv* env, ERL_NIF_TERM val, ERL_NIF_TERM *null_term);
-int should_yield(size_t used, size_t bytes_per_red);
+static inline size_t yield_threshold(size_t bytes_per_red) {
+    return bytes_per_red * DEFAULT_ERLANG_REDUCTION_COUNT;
+}
 void bump_used_reds(ErlNifEnv* env, size_t used, size_t bytes_per_red);
 
 ERL_NIF_TERM decode_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
diff --git a/c_src/util.c b/c_src/util.c
index 5df4fde..b1a0242 100644
--- a/c_src/util.c
+++ b/c_src/util.c
@@ -101,12 +101,6 @@ get_null_term(ErlNifEnv* env, ERL_NIF_TERM val, ERL_NIF_TERM *null_term)
     return 1;
 }
 
-int
-should_yield(size_t used, size_t bytes_per_red)
-{
-    return (used / bytes_per_red) >= DEFAULT_ERLANG_REDUCTION_COUNT;
-}
-
 void
 bump_used_reds(ErlNifEnv* env, size_t used, size_t bytes_per_red)
 {