Skip to content

Commit 7dfdcb6

Browse files
committed
Add Ryū algorithm for shortest float-to-string conversion
Vendor an adaptation of Ulf Adams' Ryū algorithm (PLDI 2018) from https://github.com/ulfjack/ryu commit 4c0618b, used under the Apache License 2.0. Ryū computes the shortest decimal representation of a binary floating-point number using only fixed-size integer arithmetic and a ~10 KB lookup table of bounded powers of five, with a published correctness proof. This avoids the bignum allocations that David Gay's dtoa requires for the same problem. The exported _Py_ryu_dtoa() writes 1 to 17 ASCII digits into a caller-provided buffer and returns the decimal-point position and sign, matching the interface of _Py_dg_dtoa() in mode 0. The d2s C path in upstream Ryū has been effectively frozen since 2021 and is also vendored unchanged in Microsoft's STL and LLVM's libc++, so no active sync mechanism is provided; the file header records the upstream commit and lists local modifications, following the pattern established for Python/dtoa.c.
1 parent e02ac1d commit 7dfdcb6

10 files changed

Lines changed: 1166 additions & 0 deletions

File tree

Doc/license.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,29 @@ copyright and licensing notice::
655655
***************************************************************/
656656

657657

658+
Ryū
659+
---
660+
661+
The files :file:`Python/ryu.c` and :file:`Python/ryu_tables.h`, which supply
662+
fast shortest float-to-string conversion, are derived from the Ryū reference
663+
implementation by Ulf Adams at https://github.com/ulfjack/ryu, used under the
664+
Apache License 2.0::
665+
666+
Copyright 2018 Ulf Adams
667+
668+
Licensed under the Apache License, Version 2.0 (the "License");
669+
you may not use this file except in compliance with the License.
670+
You may obtain a copy of the License at
671+
672+
http://www.apache.org/licenses/LICENSE-2.0
673+
674+
Unless required by applicable law or agreed to in writing, software
675+
distributed under the License is distributed on an "AS IS" BASIS,
676+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
677+
See the License for the specific language governing permissions and
678+
limitations under the License.
679+
680+
658681
OpenSSL
659682
-------
660683

Include/internal/pycore_ryu.h

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#ifndef Py_INTERNAL_RYU_H
2+
#define Py_INTERNAL_RYU_H
3+
#ifdef __cplusplus
4+
extern "C" {
5+
#endif
6+
7+
#ifndef Py_BUILD_CORE
8+
# error "this header requires Py_BUILD_CORE define"
9+
#endif
10+
11+
#include "pycore_pymath.h" // _PY_SHORT_FLOAT_REPR
12+
13+
#if _PY_SHORT_FLOAT_REPR == 1
14+
15+
/* Space for the longest possible result of _Py_ryu_dtoa: 17 digits + nul,
16+
or "Infinity" + nul. */
17+
#define _Py_RYU_DTOA_BUFSIZE 18
18+
19+
/* Compute the shortest decimal representation of 'd' that round-trips
20+
when parsed back as a double.
21+
22+
Writes 1 to 17 ASCII digits into 'buf' (no sign, no decimal point,
23+
no exponent), null-terminated. For NaN and infinities writes "NaN"
24+
or "Infinity" instead. 'buf' must have room for at least
25+
_Py_RYU_DTOA_BUFSIZE bytes.
26+
27+
On return, *decpt is set to the position of the decimal point relative
28+
to the start of the digit string (9999 for NaN/Inf), and *sign is set
29+
to 0 for positive values and 1 for negative values.
30+
31+
This produces the same (digits, decpt, sign) triple as
32+
_Py_dg_dtoa(d, 0, 0, decpt, sign, ...), but uses only fixed-size
33+
integer arithmetic and writes into a caller-provided buffer.
34+
35+
Returns the number of bytes written, not counting the trailing nul. */
36+
// Export for '_testinternalcapi' shared extension.
37+
PyAPI_FUNC(int) _Py_ryu_dtoa(double d, char *buf, int *decpt, int *sign);
38+
39+
40+
/* Space for the longest possible Python repr() of a float plus a
41+
trailing nul:
42+
43+
sign 1 + digit 1 + '.' 1 + digits 16 + 'e' 1 + sign 1 + exp 3
44+
= 24 chars + nul = 25.
45+
46+
Rounded up to the next 4-byte boundary. */
47+
#define _Py_DOUBLE_REPR_BUFSIZE 28
48+
49+
/* Write the Python repr of 'val' (as PyOS_double_to_string with format
50+
code 'r' and the given Py_DTSF_* flags) into the caller-provided
51+
buffer 'buf' of size 'bufsize', which must be at least
52+
_Py_DOUBLE_REPR_BUFSIZE. The result is null-terminated.
53+
54+
Returns the number of bytes written, not counting the trailing nul. */
55+
// Export for '_json' shared extension.
56+
PyAPI_FUNC(Py_ssize_t) _Py_double_repr_buffered(double val, char *buf,
57+
Py_ssize_t bufsize, int flags);
58+
59+
#endif // _PY_SHORT_FLOAT_REPR == 1
60+
61+
#ifdef __cplusplus
62+
}
63+
#endif
64+
#endif /* !Py_INTERNAL_RYU_H */

Makefile.pre.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@ PYTHON_OBJS= \
508508
Python/pystrtod.o \
509509
Python/pystrhex.o \
510510
Python/dtoa.o \
511+
Python/ryu.o \
511512
Python/fileutils.o \
512513
Python/suggestions.o \
513514
Python/perf_trampoline.o \
@@ -1422,6 +1423,7 @@ PYTHON_HEADERS= \
14221423
$(srcdir)/Include/internal/pycore_runtime_init.h \
14231424
$(srcdir)/Include/internal/pycore_runtime_init_generated.h \
14241425
$(srcdir)/Include/internal/pycore_runtime_structs.h \
1426+
$(srcdir)/Include/internal/pycore_ryu.h \
14251427
$(srcdir)/Include/internal/pycore_semaphore.h \
14261428
$(srcdir)/Include/internal/pycore_setobject.h \
14271429
$(srcdir)/Include/internal/pycore_signal.h \

PCbuild/_freeze_module.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@
268268
<ClCompile Include="..\Python\pytime.c" />
269269
<ClCompile Include="..\Python\qsbr.c" />
270270
<ClCompile Include="..\Python\remote_debugging.c" />
271+
<ClCompile Include="..\Python\ryu.c" />
271272
<ClCompile Include="..\Python\specialize.c" />
272273
<ClCompile Include="..\Python\structmember.c" />
273274
<ClCompile Include="..\Python\suggestions.c" />

PCbuild/_freeze_module.vcxproj.filters

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,9 @@
412412
<ClCompile Include="..\Python\remote_debugging.c">
413413
<Filter>Source Files</Filter>
414414
</ClCompile>
415+
<ClCompile Include="..\Python\ryu.c">
416+
<Filter>Source Files</Filter>
417+
</ClCompile>
415418
<ClCompile Include="..\Python\specialize.c">
416419
<Filter>Source Files</Filter>
417420
</ClCompile>

PCbuild/pythoncore.vcxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@
308308
<ClInclude Include="..\Include\internal\pycore_runtime_init.h" />
309309
<ClInclude Include="..\Include\internal\pycore_runtime_init_generated.h" />
310310
<ClInclude Include="..\Include\internal\pycore_runtime_structs.h" />
311+
<ClInclude Include="..\Include\internal\pycore_ryu.h" />
311312
<ClInclude Include="..\Include\internal\pycore_semaphore.h" />
312313
<ClInclude Include="..\Include\internal\pycore_setobject.h" />
313314
<ClInclude Include="..\Include\internal\pycore_signal.h" />
@@ -671,6 +672,7 @@
671672
<ClCompile Include="..\Python\remote_debugging.c" />
672673
<ClCompile Include="..\Python\qsbr.c" />
673674
<ClCompile Include="..\Python\dtoa.c" />
675+
<ClCompile Include="..\Python\ryu.c" />
674676
<ClCompile Include="..\Python\Python-ast.c" />
675677
<ClCompile Include="..\Python\Python-tokenize.c" />
676678
<ClCompile Include="..\Python\pythonrun.c" />

PCbuild/pythoncore.vcxproj.filters

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,9 @@
828828
<ClInclude Include="..\Include\internal\pycore_runtime_structs.h">
829829
<Filter>Include\internal</Filter>
830830
</ClInclude>
831+
<ClInclude Include="..\Include\internal\pycore_ryu.h">
832+
<Filter>Include\internal</Filter>
833+
</ClInclude>
831834
<ClInclude Include="..\Include\internal\pycore_semaphore.h">
832835
<Filter>Include\internal</Filter>
833836
</ClInclude>
@@ -1556,6 +1559,9 @@
15561559
<ClCompile Include="..\Python\dtoa.c">
15571560
<Filter>Python</Filter>
15581561
</ClCompile>
1562+
<ClCompile Include="..\Python\ryu.c">
1563+
<Filter>Python</Filter>
1564+
</ClCompile>
15591565
<ClCompile Include="..\Python\Python-ast.c">
15601566
<Filter>Python</Filter>
15611567
</ClCompile>

0 commit comments

Comments
 (0)