Skip to content

Commit f0b4405

Browse files
committed
just a dev note for the sleef_strtoq finding
1 parent 85074c1 commit f0b4405

2 files changed

Lines changed: 38 additions & 0 deletions

File tree

src/csrc/utilities.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,41 @@ cstring_to_quad_internal(const char *str, const char *start, QuadBackendType bac
139139
temp[len] = '\0';
140140

141141
// Call Sleef_strtoq with the bounded string
142+
//
143+
// DEVELOPER NOTE - Sleef_strtoq decimal precision limit.
144+
// The decimal branch of Sleef_strtoq is NOT correctly-rounded. It
145+
// accumulates digits (n = n*10 + d) into SLEEF's triple-double "tdx"
146+
// format (a 64-bit exponent + vdouble3 mantissa, ~159 mantissa bits),
147+
// scales by exp10i() and rounds via cast_vq_tdx(). Only the hex (0x...)
148+
// branch is exact bit-manipulation.
149+
//
150+
// Three different digit counts are involved - do NOT conflate them:
151+
// * ~34 = a quad's actual precision (113 * log10(2))
152+
// * 36 = digits that GUARANTEE any quad round-trips; this is
153+
// SLEEF_QUAD_DECIMAL_DIG and the most Dragon4 (str/repr) emits
154+
// * ~48 = the tdx mantissa's raw capacity (159 bits / log2(10))
155+
// * ~45 = the EFFECTIVE window this parser resolves correctly - a few
156+
// digits below 48 because the tdx add/mul/div/exp10i are not
157+
// perfectly rounded. Property of the parser, not of quads.
158+
//
159+
// ~45 is NOT a hard/coded limit: it is a soft, build-dependent threshold,
160+
// and it is measured (empirical), not read from a constant. Sweeping
161+
// genuine quads across the exponent range, the smallest k at which
162+
// nudging a half-ULP midpoint by 10^-k gets silently dropped (rounds the
163+
// wrong way) was 45 (some values tolerate 46-48). Inputs longer than the
164+
// window are still read - the extra digits just stop reliably affecting
165+
// the result. So if the digit that decides rounding sits at position
166+
// >= ~46 - e.g. a value just above a midpoint - the result can be 1 ULP
167+
// off. Concrete repro
168+
// (SLEEF 3.9.0, x86): the ~46-digit-deep value near
169+
// "1.99999...9997111...e0" parses to ...ffffe, not the correct ...fffff.
170+
//
171+
// Practical impact on quad<->string round-trips: none here. Every quad
172+
// is captured by <= 36 digits, ~9 below the ~45 window, so Dragon4
173+
// output re-parses exactly. BUT that ~45 is FMA-/codegen-dependent (the
174+
// tdx arithmetic can lose a guard bit on some toolchains, e.g. macOS)
175+
// and can shrink toward 36 - so do not rely on string parsing for
176+
// exactness. Binary paths (raw bytes) are always exact.
142177
char *sleef_endptr;
143178
out_value->sleef_value = Sleef_strtoq(temp, &sleef_endptr);
144179
free(temp);

src/include/scalar.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ QuadPrecision_raw_new(QuadBackendType backend);
2424
QuadPrecisionObject *
2525
QuadPrecision_from_object(PyObject *value, QuadBackendType backend);
2626

27+
PyObject *
28+
QuadPrecision_from_raw_bytes(PyObject *module, PyObject *args);
29+
2730
int
2831
init_quadprecision_scalar(void);
2932

0 commit comments

Comments
 (0)