Răsfoiți Sursa

+ re-implementation of real->string and string->real conversion routines
based on the Grisu1 algorithm. This corrects several precision issues
with the previous code used to perform such conversions (patch by
Max Nazhalov, mantis #25241)
o adaptation of several tests to deal with the better precision of these
routines compared to the previous version
Please don't remove the real2str.inc file yet, it's still used by the
JVM target for now

git-svn-id: trunk@25888 -

Jonas Maebe 11 ani în urmă
părinte
comite
21eeec9981

+ 3 - 0
.gitattributes

@@ -7904,6 +7904,9 @@ rtl/inc/extres.inc svneol=native#text/plain
 rtl/inc/fexpand.inc svneol=native#text/plain
 rtl/inc/file.inc svneol=native#text/plain
 rtl/inc/filerec.inc svneol=native#text/plain
+rtl/inc/flt_conv.inc svneol=native#text/plain
+rtl/inc/flt_core.inc svneol=native#text/plain
+rtl/inc/flt_pack.inc svneol=native#text/plain
 rtl/inc/fpextres.pp svneol=native#text/plain
 rtl/inc/fpintres.pp svneol=native#text/plain
 rtl/inc/gencurr.inc svneol=native#text/plain

+ 419 - 0
rtl/inc/flt_conv.inc

@@ -0,0 +1,419 @@
+{
+    Copyright (C) 2013 by Max Nazhalov
+
+    This file, in conjunction with FLT_CORE.INC, implements 2-way conversion
+    among the binary floating-point value and its ASCII representation.
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version with the following modification:
+
+    As a special exception, the copyright holders of this library give you
+    permission to link this library with independent modules to produce an
+    executable, regardless of the license terms of these independent modules,
+    and to copy and distribute the resulting executable under terms of your
+    choice, provided that you also meet, for each linked independent module,
+    the terms and conditions of the license of that module. An independent
+    module is a module which is not derived from or based on this library.
+    If you modify this library, you may extend this exception to your version
+    of the library, but you are not obligated to do so. If you do not wish to
+    do so, delete this exception statement from your version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+    See the GNU Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; if not, write to the Free Software Foundation,
+    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ ****************************************************************************
+}
+{
+ Algorithm for converting floating-point values to decimal representation
+ implemented here has the formal name "Grisu" (basically, "Grisu1").
+ It was developed by Florian Loitsch and was presented on the PLDI forum
+ in the mid-2010. Detailed algorithm description and rationale can be found
+ in his article, referenced below. [1]
+
+ This implementation is purely based on [1], extrapolating the original
+ algorithm on to floating point types other than the "double": namely
+ "single", "extended" and "float128".
+
+ For self-sufficiency, inverse conversion is also implemented.
+ It uses strightforward approach, with the help of high-precision integer
+ routines and tables used by Grisu. The main goal was to confirm the "internal
+ identity", i.e. conversion path " float -> ASCII -> float " should recover
+ the original floating-point representation so long as there are enough
+ significant digits provided.
+
+ Generally, the algorithm used has two major drawbacks:
+  1. Although "Grisu1" is seems precise in the sense of internal identity, as
+     defined above, it is often produces suboptimal output, i.e., in
+     exponential representation, output could end up with "...00001",
+     "...99998" et al., despite of the fact that in those cases rounding-off
+     the last 1 (or, less often, 2) digits would not break internal identity.
+     Refer to [1] for further explanations.
+  2. Although "Grisu1" is REALLY fast on, e.g. i386, there can be significant
+     performance impact on platforms which have long floating point, but
+     rather "short" integer ALU (e.g., i8086 with i8087). Despite of the fact
+     that an attempt was made to use only "uint32" when converting the
+     "single" back and forth, all other floating-point types unavoidably
+     require and heavily use "uint64" arithmetics.
+
+ ---
+
+ Implementation was intentionally split into 3 include files to simplify
+ maintenance and to avoid complex multiple self-includings.
+
+ This file is the root one. It, depending on the selected mode, defines
+ proper conditionals, and then includes FLT_CORE.INC, which implements actual
+ conversions. Basically, there are 2 possible compilation modes:
+  1. If condition "fpc_softfpu_implementation" is defined before including
+     this file, it is assumed to be a part of SoftFPU. In this case, file
+     FLT_CORE.INC is included once per every supported floating point type.
+     Naming conflicts resolved with macros.
+     Supported types are selected with the following pre-defined conditionals:
+         SOFTFLOAT_ASCII_FLOAT32
+         SOFTFLOAT_ASCII_FLOAT64
+         SOFTFLOAT_ASCII_FLOATX80
+         SOFTFLOAT_ASCII_FLOAT128
+     This mode is basically intended to allow testing and debugging various
+     conversion scenarios without having appropriate floating point hardware,
+     using only sandbox provided by the SoftFPU. An implementation example of
+     this mode is shown in the accompanying file SFPU_FORMAT.PP.
+  2. If condition "fpc_softfpu_implementation" is not defined, it is assumed
+     to be a part of the system unit, or something else. In this case, file
+     FLT_CORE.INC is included only once, and the actual floating point type
+     must be defined as "ValReal" before including this file. Also, kind of
+     the "ValReal" must be further specified with the following defines (in
+     order of priority):
+         SUPPORT_FLOAT128
+         SUPPORT_EXTENDED
+         SUPPORT_DOUBLE
+         SUPPORT_SINGLE
+     Overall behavior and definitions mimic the REAL2STR.INC file from the
+     FPC source tree.
+
+ BEWARE: there are two minor differences between the code generated for mode 1
+ and mode 2, which can potentially lead to different results:
+   <i> floating-point value packing and unpacking routines are implemented
+       differently among these modes (see FLT_PACK.INC).
+  <ii> also one internal subroutine involved into conversion to ASCII, namely
+       "k_comp", has separate implementations for mode 1 and mode 2.
+ Certainly, they should behave the same, and they have never been caught yet
+ during testing, but who knows..
+
+ ---
+
+ References:
+
+  [1] Florian Loitsch. Printing Floating-Point Numbers Quickly and Accurately
+      with Integers. PLDI'10, June 5-10, 2010, Toronto, Ontario, Canada.
+      http://florian.loitsch.com/publications/dtoa-pldi2010.pdf?attredirects=0
+
+  [2] IEEE 754-2008, Standard for Floating-Point Arithmetic. IEEE, New York,
+      Aug. 29 2008.
+
+ ****************************************************************************
+}
+{$push}
+{$Q-,R-,B-}
+
+{$ifdef DEBUG}
+    {$define grisu1_debug}
+    {$C+}
+{$else}
+    {$undef grisu1_debug}
+{$endif}
+
+(*-------------------------------------------------------
+ | Compatibility settings
+ *-------------------------------------------------------*)
+    
+    // FPC defaults to "real indefinite" QNaN value, which is negative.
+    // Undefine to respect the sign provided during ASCII->float conversion.
+    {$define GRISU1_A2F_QNAN_REAL_INDEFINITE}
+
+    // Controls printing of NaN-sign.
+    // Undefine to print NaN sign during float->ASCII conversion.
+    {$define GRISU1_F2A_NAN_SIGNLESS} // IEEE does not interpret the sign of a NaN, so leave it defined.
+
+    // Controls rounding of generated digits when formatting with narrowed
+    // width (either fixed or exponential notation).
+    // Traditionally, FPC and BP7/Delphi use "roundTiesToAway" mode.
+    // Undefine to use "roundTiesToEven" approach.
+    {$define GRISU1_F2A_HALF_ROUNDUP}
+
+    // This one is a hack against Grusu sub-optimality.
+    // It may be used only strictly together with GRISU1_F2A_HALF_ROUNDUP.
+    // It does not violate most general rules due to the fact that it is
+    // applicable only when formatting with narrowed width, where the fine
+    // view is more desirable, and the precision is already lost, so it can
+    // be used in general-purpose applications.
+    // Refer to its implementation.
+    {$define GRISU1_F2A_AGRESSIVE_ROUNDUP} // Defining this fixes several tests.
+
+    // Controls rounding of over-required digits during ASCII->float.
+    // Undefine to use "roundTiesToEven" approach. [Should have no much sense, though]
+    {$undef GRISU1_A2F_HALF_ROUNDUP}
+
+    // Controls which result is returned in case of error during ASCII->float.
+    // FPC initializes result to "0.0".
+    // Undefine to return (s)NAN.
+    {$define GRISU1_A2F_ERROR_RET0} // Leave it defined, otherwise several irrelevant tests will break.
+
+    // Undefine to enable SNaN support.
+    // Note: IEEE [754-2008, page 31] requires (1) to recognize "SNaN" during
+    // ASCII->float, and (2) to generate the "invalid FP operation" exception
+    // either when SNaN is printed as "NaN", or "SNaN" is evaluated to QNaN,
+    // so it would be preferable to undefine these settings,
+    // but the FPC RTL is not ready for this right now..
+    {$define GRISU1_F2A_NO_SNAN}
+    {$define GRISU1_A2F_NO_SNAN}
+
+    // Controls how many digits is printed for "single".
+    // IEEE claims 9 digits is always enough, and it is confirmed by
+    // comprehensive testing.
+    // Define to print 10 digits.
+    // Note: there is no much sense to print 10 digits, except that it fixes
+    // the test "./tests/test/units/sysutils/tfloattostr.pp".
+    // Alternate way would be to print 9 digits, and change the test to
+    // compare against "6e-9" instead of "6e-10".
+    {$define GRISU1_F2A_SINGLE_10DIGITS}
+
+(*-------------------------------------------------------
+ | These conditional defines are heavily used internally,
+ | so make sure they are not defined around
+ *-------------------------------------------------------*)
+
+{$ifdef VALREAL_32}
+    {$fatal VALREAL_32 should not be defined here!}
+{$endif}
+{$ifdef VALREAL_64}
+    {$fatal VALREAL_64 should not be defined here!}
+{$endif}
+{$ifdef VALREAL_80}
+    {$fatal VALREAL_80 should not be defined here!}
+{$endif}
+{$ifdef VALREAL_128}
+    {$fatal VALREAL_128 should not be defined here!}
+{$endif}
+{$ifdef VALREAL_PACK}
+    {$fatal VALREAL_PACK should not be defined here!}
+{$endif}
+
+(*-------------------------------------------------------
+ | Floating point types formatting profile
+ *-------------------------------------------------------*)
+
+type
+    TReal_Type = (
+        RT_S32REAL,  // single
+        RT_S64REAL,  // double
+        RT_S80REAL,  // extended [80-bit]
+        RT_SC80REAL, // extended ["C-extended"; functionally the same as RT_S80REAL, but may be different in alignment and padding]
+        RT_C64BIT,   // comp [legacy; just an int64 passed via float]
+        RT_CURRENCY, // currency [seems never passed to str_real since it has its own dedicated converters after r5866]
+        RT_S128REAL  // float128
+    );
+
+const
+    float_format: array [ TReal_Type ] of record
+        nDig_mantissa, nDig_exp10: integer;
+    end = (
+{
+    Number of mantissa digits is dictated by [2] "IEEE 754-2008", page 32.
+    N = 1 + ceiling( p * log10(2) ), where p is the number of significant
+    bits, i.e. 24/53/64/113 accordingly (including the implicit one if any)
+}
+        // RT_S32REAL
+        ( nDig_mantissa: {$ifdef GRISU1_F2A_SINGLE_10DIGITS} 10 {$else} 9 {$endif}; 
+          nDig_exp10: 2;
+        ),
+        // RT_S64REAL
+        ( nDig_mantissa: 17;
+          nDig_exp10: 3;
+        ),
+        // RT_S80REAL
+        ( nDig_mantissa: 21;
+          nDig_exp10: 4;
+        ),
+        // RT_SC80REAL
+        ( nDig_mantissa: 21;
+          nDig_exp10: 4;
+        ),
+        // RT_C64BIT
+        ( nDig_mantissa: 19; // int64 has 19 digits
+          nDig_exp10: 4; // Delphi prints exponent with 4 digits
+        ),
+        // RT_CURRENCY [seems not used after r5866]
+        ( nDig_mantissa: 19;
+          nDig_exp10: 2;
+        ),
+        // RT_S128REAL
+        ( nDig_mantissa: 36;
+          nDig_exp10: 4;
+        )
+    );
+
+    C_STR_INF  : string[3] = 'Inf';
+    C_STR_QNAN : string[3] = 'Nan';
+{$if not ( defined(GRISU1_F2A_NO_SNAN) and defined(GRISU1_A2F_NO_SNAN) )}
+    C_STR_SNAN : string[4] = 'SNan';
+{$endif GRISU1_*_NO_SNAN}
+
+{$ifdef fpc_softfpu_implementation}
+(****************************************************************************
+ * 
+ * SoftPFU unit: Multiple instances for all supported floating point types
+ *
+ ****************************************************************************)
+{$inline on}
+{$macro on}
+{$define grisu1_inline}
+
+    {$define ValSInt:=integer}
+
+{$ifdef SOFTFLOAT_ASCII_FLOAT32}
+    {$define VALREAL_32}
+    {$undef  VALREAL_64}
+    {$undef  VALREAL_80}
+    {$undef  VALREAL_128}
+
+ // Name remapping
+    {$define val_real:=ascii_to_float32}
+    {$define ValReal:=float32rec}
+    {$define TDIY_FP:=TDIY_FP32}
+    {$define TDIY_FP_Power_of_10:=TDIY_FP32_Power_of_10}
+    
+ // Implementation
+    {$info === float32<->ASCII ===}
+    {$i flt_core.inc}
+
+{$endif SOFTFLOAT_ASCII_FLOAT32}
+
+{$ifdef SOFTFLOAT_ASCII_FLOAT64}
+    {$undef  VALREAL_32}
+    {$define VALREAL_64}
+    {$undef  VALREAL_80}
+    {$undef  VALREAL_128}
+
+ // Name remapping
+    {$define val_real:=ascii_to_float64}
+    {$define ValReal:=float64}
+    {$define TDIY_FP:=TDIY_FP64}
+    {$define TDIY_FP_Power_of_10:=TDIY_FP64_Power_of_10}
+
+ // Implementation
+    {$info === float64<->ASCII ===}
+    {$i flt_core.inc}
+
+{$endif SOFTFLOAT_ASCII_FLOAT64}
+
+{$ifdef SOFTFLOAT_ASCII_FLOATX80}
+    {$undef  VALREAL_32}
+    {$undef  VALREAL_64}
+    {$define VALREAL_80}
+    {$undef  VALREAL_128}
+
+ // Name remapping
+    {$define val_real:=ascii_to_floatx80}
+    {$define ValReal:=floatx80}
+    {$define TDIY_FP:=TDIY_FP96}
+    {$define TDIY_FP_Power_of_10:=TDIY_FP96_Power_of_10}
+
+ // Implementation
+    {$info === floatx80<->ASCII ===}
+    {$i flt_core.inc}
+
+{$endif SOFTFLOAT_ASCII_FLOATX80}
+
+{$ifdef SOFTFLOAT_ASCII_FLOAT128}
+    {$undef  VALREAL_32}
+    {$undef  VALREAL_64}
+    {$undef  VALREAL_80}
+    {$define VALREAL_128}
+
+ // Name remapping
+    {$define val_real:=ascii_to_float128}
+    {$define ValReal:=float128}
+    {$define TDIY_FP:=TDIY_FP128}
+    {$define TDIY_FP_Power_of_10:=TDIY_FP128_Power_of_10}
+
+ // Implementation
+    {$info === float128<->ASCII ===}
+    {$i flt_core.inc}
+
+{$endif SOFTFLOAT_ASCII_FLOAT128}
+
+ // Clean-up
+    {$undef VALREAL_32}
+    {$undef VALREAL_64}
+    {$undef VALREAL_80}
+    {$undef VALREAL_128}
+    {$undef VALREAL_PACK}
+    {$undef val_real}
+    {$undef ValReal}
+    {$undef ValSInt}
+    {$undef TDIY_FP}
+    {$undef TDIY_FP_Power_of_10}
+    {$undef grisu1_inline}
+    {$info ========================}
+
+{$else not fpc_softfpu_implementation}
+(****************************************************************************
+ * 
+ * System unit: only one native floating point type
+ *
+ ****************************************************************************)
+{$ifdef SYSTEMINLINE}
+    {$define grisu1_inline}
+{$else}
+    {$undef grisu1_inline}
+{$endif}
+
+{$if defined(SUPPORT_FLOAT128)}
+    {$undef  VALREAL_32}
+    {$undef  VALREAL_64}
+    {$undef  VALREAL_80}
+    {$define VALREAL_128}
+
+{$elseif defined(SUPPORT_EXTENDED)}
+    {$undef  VALREAL_32}
+    {$undef  VALREAL_64}
+    {$define VALREAL_80}
+    {$undef  VALREAL_128}
+
+{$elseif defined(SUPPORT_DOUBLE)}
+    {$undef  VALREAL_32}
+    {$define VALREAL_64}
+    {$undef  VALREAL_80}
+    {$undef  VALREAL_128}
+
+{$elseif defined(SUPPORT_SINGLE)}
+    {$define VALREAL_32}
+    {$undef  VALREAL_64}
+    {$undef  VALREAL_80}
+    {$undef  VALREAL_128}
+
+{$else}
+    {$error Unrecognized ValReal type}
+{$endif SUPPORT_*}
+
+ // Implementation
+    {$i flt_core.inc}
+
+ // Clean-up
+    { undef VALREAL_32}
+    { undef VALREAL_64}
+    { undef VALREAL_80}
+    { undef VALREAL_128}
+    {$undef VALREAL_PACK}
+    {$undef grisu1_inline}
+
+{$endif fpc_softfpu_implementation}
+
+{$pop}

+ 2717 - 0
rtl/inc/flt_core.inc

@@ -0,0 +1,2717 @@
+{
+    Copyright (C) 2013 by Max Nazhalov
+
+    This file contains generalized floating point<->ASCII conversion routines.
+    It is included by the FLT_CONV.INC after setting-up correct conditional
+    definitions, therefore it sholud not be used directly.
+    Refer to FLT_CONV.INC for further explanation.
+
+    This library is free software; you can redistribute it and/or modify it
+    under the terms of the GNU Library General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or (at your
+    option) any later version with the following modification:
+
+    As a special exception, the copyright holders of this library give you
+    permission to link this library with independent modules to produce an
+    executable, regardless of the license terms of these independent modules,
+    and to copy and distribute the resulting executable under terms of your
+    choice, provided that you also meet, for each linked independent module,
+    the terms and conditions of the license of that module. An independent
+    module is a module which is not derived from or based on this library.
+    If you modify this library, you may extend this exception to your version
+    of the library, but you are not obligated to do so. If you do not wish to
+    do so, delete this exception statement from your version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+    See the GNU Library General Public License for more details.
+
+    You should have received a copy of the GNU Library General Public License
+    along with this library; if not, write to the Free Software Foundation,
+    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+ ****************************************************************************
+}
+
+type
+    // "Do-It-Yourself Floating Point" structure
+    TDIY_FP = record
+      {$ifdef VALREAL_128}
+        fh : qword;
+      {$endif}
+      {$ifdef VALREAL_32}
+        f : dword;
+      {$else}
+        f : qword;
+      {$endif}
+      {$ifdef VALREAL_80}
+        fh : dword;
+      {$endif}
+        e : integer;
+    end;
+
+    TDIY_FP_Power_of_10 = record
+        c : TDIY_FP;
+        e10 : integer;
+    end;
+
+{$if defined(VALREAL_80) or defined(VALREAL_128)}
+
+(****************************************************************************
+ * diy_util_add
+ *
+ * Helper routine: summ of multiword unsigned integers
+ *
+ * Used by:
+ *   [80,128] diy_fp_cached_power10
+ *   [80,128] diy_fp_multiply
+ *   [80,128] float<->ASCII
+ *
+ ****************************************************************************)
+{$ifdef VALREAL_80}
+procedure diy_util_add( var xh: dword; var xl: qword; const yh: dword; const yl: qword ); {$ifdef grisu1_inline}inline;{$endif}
+{$else VALREAL_128}
+procedure diy_util_add( var xh, xl: qword; const yh, yl: qword ); {$ifdef grisu1_inline}inline;{$endif}
+{$endif VALREAL_*}
+var
+    temp: qword;
+begin
+    temp := xl + yl;
+    xh := xh + yh + ord( temp < xl );
+    xl := temp;
+end;
+
+(****************************************************************************
+ * diy_util_shl
+ *
+ * Helper routine: left shift of multiword unsigned integer
+ *
+ * Used by:
+ *   [80,128] float<->ASCII
+ *
+ ****************************************************************************)
+{$ifdef VALREAL_80}
+procedure diy_util_shl( var h: dword; var l: qword; const count: integer );
+{$else VALREAL_128}
+procedure diy_util_shl( var h, l: qword; const count: integer );
+{$endif VALREAL_*}
+begin
+    if ( count = 0 ) then
+        exit;
+{$ifdef grisu1_debug}
+    assert( count > 0 );
+  {$ifdef VALREAL_80}
+    assert( count < 96 );
+  {$else VALREAL_128}
+    assert( count < 128 );
+  {$endif VALREAL_*}
+{$endif grisu1_debug}
+    if ( count = 1 ) then
+    begin
+        diy_util_add( h, l, h, l );
+        exit;
+    end;
+    if ( count >= 64 ) then
+    begin
+        if ( count > 64 ) then
+            h := ( l shl ( count - 64 ) )
+        else
+            h := l;
+        l := 0;
+        exit;
+    end;
+  {$ifdef VALREAL_80}
+    if ( count = 32 ) then
+        h := hi( l )
+    else
+  {$endif VALREAL_80}
+    if ( count < 32 ) then
+        h := ( h shl count ) + ( hi( l ) shr ( 32 - count ) )
+    else
+  {$ifdef VALREAL_80}
+        h := ( l shr ( 64 - count ) );
+  {$else VALREAL_128}
+        h := ( h shl count ) + ( l shr ( 64 - count ) );
+  {$endif VALREAL_*}
+    l := ( l shl count );
+end;
+
+(****************************************************************************
+ * diy_util_shr
+ *
+ * Helper routine: right shift of multiword unsigned integer
+ *
+ * Used by:
+ *   [80,128] float<->ASCII
+ *
+ ****************************************************************************)
+{$ifdef VALREAL_80}
+procedure diy_util_shr( var h: dword; var l: qword; const count: integer );
+{$else VALREAL_128}
+procedure diy_util_shr( var h, l: qword; const count: integer );
+{$endif VALREAL_*}
+begin
+    if ( count = 0 ) then
+        exit;
+  {$ifdef grisu1_debug}
+    assert( count > 0 );
+  {$endif grisu1_debug}
+    if ( count = 1 ) then
+    begin
+        l := l shr 1;
+        if ( lo(h) and 1 <> 0 ) then
+            l := l or qword($8000000000000000);
+        h := h shr 1;
+        exit;
+    end;
+    if ( count < 64 ) then
+    begin
+        l := ( qword( h ) shl ( ( - count ) and 63 ) ) or ( l shr count );
+      {$ifdef VALREAL_80}
+        if ( count >= 32 ) then
+            h := 0
+        else
+      {$endif VALREAL_80}
+            h := h shr count;
+        exit;
+    end;
+  {$ifdef VALREAL_80}
+    if ( count < 96 ) then
+  {$else VALREAL_128}
+    if ( count < 128 ) then
+  {$endif VALREAL_*}
+        l := h shr ( count and 63 )
+    else
+        l := 0;
+    h := 0;
+end;
+
+{$endif VALREAL_80 | VALREAL_128}
+
+(****************************************************************************
+ * diy_fp_multiply
+ *
+ * "Do-It-Yourself Floating Point" multiplication routine
+ *
+ * Simplified implementation:
+ *  > restricted input:
+ *     - both operands should be normalized
+ *  > relaxed output:
+ *     - rounding is simple [half is rounded-up]
+ *     - normalization is optional and performed at the very end if requested
+ *       [at most 1 shift required since both multipliers are normalized]
+ *
+ * Used by:
+ *   [all] float<->ASCII
+ *   [>32] diy_fp_cached_power10
+ *
+ ****************************************************************************)
+function diy_fp_multiply( const x, y: TDIY_FP; normalize: boolean ): TDIY_FP;
+const
+    C_1_SHL_31 = dword($80000000);
+{$ifdef VALREAL_32}
+//***************** 32-bit *********************
+var
+    a, b, c, d, ac, bc, ad, bd, t1: dword;
+begin
+    a := ( x.f shr 16 );
+    b := ( x.f and $0000FFFF );
+    c := ( y.f shr 16 );
+    d := ( y.f and $0000FFFF );
+    ac := a * c;
+    bc := b * c;
+    ad := a * d;
+    bd := b * d;
+    t1 := ( bc and $0000FFFF )
+        + ( bd shr 16 )
+        + ( ad and $0000FFFF )
+        + ( 1 shl 15 ); // round
+    diy_fp_multiply.f := ac
+        + ( ad shr 16 )
+        + ( bc shr 16 )
+        + ( t1 shr 16 );
+    diy_fp_multiply.e := x.e + y.e + 32;
+    if normalize then with diy_fp_multiply do
+    begin
+        if ( f and C_1_SHL_31 = 0 ) then
+        begin
+            inc( f, f );
+            dec( e );
+        end;
+      {$ifdef grisu1_debug}
+        assert( f and C_1_SHL_31 <> 0 );
+      {$endif grisu1_debug}
+    end;
+end;
+{$else not VALREAL_32}
+(*-------------------------------------------------------
+ | u32_mul_u32_to_u64 [local]
+ |
+ | Local routine of the "diy_fp_multiply"; common to float64..float128:
+ |     uint32 * uint32 -> uint64
+ |
+ *-------------------------------------------------------*)
+function u32_mul_u32_to_u64( const a, b: dword ): qword; {$ifdef grisu1_inline}inline;{$endif}
+begin
+    // it seems this pattern is very tightly optimized by FPC at least for i386
+    u32_mul_u32_to_u64 := qword( a ) * b;
+end;
+{$endif VALREAL_*}
+{$ifdef VALREAL_64}
+//***************** 64-bit *********************
+var
+    a, b, c, d: dword;
+    ac, bc, ad, bd, t1: qword;
+begin
+    a := hi( x.f ); b := x.f;
+    c := hi( y.f ); d := y.f;
+    ac := u32_mul_u32_to_u64( a, c );
+    bc := u32_mul_u32_to_u64( b, c );
+    ad := u32_mul_u32_to_u64( a, d );
+    bd := u32_mul_u32_to_u64( b, d );
+    t1 := qword( C_1_SHL_31 ) // round
+        + hi( bd ) + lo( bc ) + lo( ad );
+    diy_fp_multiply.f := ac + hi( ad ) + hi( bc ) + hi( t1 );
+    diy_fp_multiply.e := x.e + y.e + 64;
+    if normalize then with diy_fp_multiply do
+    begin
+        if ( hi( f ) and C_1_SHL_31 = 0 ) then
+        begin
+            inc( f, f );
+            dec( e );
+        end;
+      {$ifdef grisu1_debug}
+        assert( hi( f ) and C_1_SHL_31 <> 0 );
+      {$endif grisu1_debug}
+    end;
+end;
+{$endif VALREAL_64}
+{$ifdef VALREAL_80}
+//***************** 96-bit *********************
+var
+    a, b, c, u, v, w: dword;
+    au, av, aw, bu, bv, bw, cu, cv, cw, t1, t2: qword;
+begin
+    a := x.fh; b := hi( x.f ); c := x.f;
+    u := y.fh; v := hi( y.f ); w := y.f;
+    au := u32_mul_u32_to_u64( a, u );
+    bu := u32_mul_u32_to_u64( b, u );
+    cu := u32_mul_u32_to_u64( c, u );
+    av := u32_mul_u32_to_u64( a, v );
+    bv := u32_mul_u32_to_u64( b, v );
+    cv := u32_mul_u32_to_u64( c, v );
+    aw := u32_mul_u32_to_u64( a, w );
+    bw := u32_mul_u32_to_u64( b, w );
+    cw := u32_mul_u32_to_u64( c, w );
+    t1 := ( cw shr 32 ) + lo( bw ) + lo( cv );
+    t1 := qword( C_1_SHL_31 ) // round
+        + hi( t1 ) + hi( bw ) + hi( cv ) + lo( aw ) + lo( bv ) + lo( cu );
+    t1 := ( t1 shr 32 ) + hi( aw ) + hi( bv ) + hi( cu ) + lo( av ) + lo( bu );
+    t2 := au + hi( av ) + hi( bu ) + hi( t1 );
+    diy_fp_multiply.f := ( t2 shl 32 ) + lo( t1 );
+    diy_fp_multiply.fh := hi( t2 );
+    diy_fp_multiply.e := x.e + y.e + 96;
+    if normalize then with diy_fp_multiply do
+    begin
+        if ( fh and C_1_SHL_31 = 0 ) then
+        begin
+            diy_util_add( fh, f, fh, f );
+            dec( e );
+        end;
+      {$ifdef grisu1_debug}
+        assert( fh and C_1_SHL_31 <> 0 );
+      {$endif grisu1_debug}
+    end;
+end;
+{$endif VALREAL_80}
+{$ifdef VALREAL_128}
+//***************** 128-bit ********************
+var
+    a, b, c, d, u, v, w, z: dword;
+    au, av, aw, az, bu, bv, bw, bz, cu, cv, cw, cz, du, dv, dw, dz, t1, t2: qword;
+begin
+    a := hi( x.fh ); b := x.fh; c := hi( x.f ); d := x.f;
+    u := hi( y.fh ); v := y.fh; w := hi( y.f ); z := y.f;
+    au := u32_mul_u32_to_u64( a, u );
+    bu := u32_mul_u32_to_u64( b, u );
+    cu := u32_mul_u32_to_u64( c, u );
+    du := u32_mul_u32_to_u64( d, u );
+    av := u32_mul_u32_to_u64( a, v );
+    bv := u32_mul_u32_to_u64( b, v );
+    cv := u32_mul_u32_to_u64( c, v );
+    dv := u32_mul_u32_to_u64( d, v );
+    aw := u32_mul_u32_to_u64( a, w );
+    bw := u32_mul_u32_to_u64( b, w );
+    cw := u32_mul_u32_to_u64( c, w );
+    dw := u32_mul_u32_to_u64( d, w );
+    az := u32_mul_u32_to_u64( a, z );
+    bz := u32_mul_u32_to_u64( b, z );
+    cz := u32_mul_u32_to_u64( c, z );
+    dz := u32_mul_u32_to_u64( d, z );
+    t1 := ( dz shr 32 ) + lo( cz ) + lo( dw );
+    t1 := ( t1 shr 32 ) + hi( cz ) + hi( dw ) + lo( bz ) + lo( cw ) + lo( dv );
+    t1 := qword( C_1_SHL_31 ) // round
+        + hi( t1 ) + hi( bz ) + hi( cw ) + hi( dv ) + lo( az ) + lo( bw ) + lo( cv ) + lo( du );
+    t2 := ( t1 shr 32 ) + hi( az ) + hi( bw ) + hi( cv ) + hi( du ) + lo( aw ) + lo( bv ) + lo( cu );
+    t1 := ( t2 shr 32 ) + hi( aw ) + hi( bv ) + hi( cu ) + lo( av ) + lo( bu );
+    diy_fp_multiply.f := ( t1 shl 32 ) + lo( t2 );
+    diy_fp_multiply.fh := au + hi( av ) + hi( bu ) + hi( t1 );
+    diy_fp_multiply.e := x.e + y.e + 128;
+    if normalize then with diy_fp_multiply do
+    begin
+        if ( hi( fh ) and C_1_SHL_31 = 0 ) then
+        begin
+            diy_util_add( fh, f, fh, f );
+            dec( e );
+        end;
+      {$ifdef grisu1_debug}
+        assert( hi( fh ) and C_1_SHL_31 <> 0 );
+      {$endif grisu1_debug}
+    end;
+end;
+{$endif VALREAL_128}
+
+(****************************************************************************
+ * diy_fp_cached_power10
+ *
+ * The main purpose of this routine is to return normalized correctly rounded
+ * DIY-floating-point approximation of the power of 10, which has to be used
+ * by the Grisu1 as a scaling factor, intended to shift a binary exponent of
+ * the original number into selected [ alpha .. gamma ] range.
+ *
+ * This routine is also usable as a helper during ASCII -> float conversion,
+ * so the range of cached powers is slightly extended beyond the requirements
+ * of the Grisu1.
+ *
+ * Used by:
+ *   [all] float<->ASCII
+ *
+ ****************************************************************************)
+procedure diy_fp_cached_power10( exp10: integer; out factor: TDIY_FP_Power_of_10 );
+{$ifdef VALREAL_32}
+const
+    // alpha =-29; gamma = 0; step = 1E+8
+    cache: array [ 0 .. 13 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( f: dword($FB158593); e: -218 ); e10: -56 ),
+        ( c: ( f: dword($BB127C54); e: -191 ); e10: -48 ),
+        ( c: ( f: dword($8B61313C); e: -164 ); e10: -40 ),
+        ( c: ( f: dword($CFB11EAD); e: -138 ); e10: -32 ),
+        ( c: ( f: dword($9ABE14CD); e: -111 ); e10: -24 ),
+        ( c: ( f: dword($E69594BF); e:  -85 ); e10: -16 ),
+        ( c: ( f: dword($ABCC7712); e:  -58 ); e10:  -8 ),
+        ( c: ( f: dword($80000000); e:  -31 ); e10:   0 ),
+        ( c: ( f: dword($BEBC2000); e:   -5 ); e10:   8 ),
+        ( c: ( f: dword($8E1BC9BF); e:   22 ); e10:  16 ),
+        ( c: ( f: dword($D3C21BCF); e:   48 ); e10:  24 ),
+        ( c: ( f: dword($9DC5ADA8); e:   75 ); e10:  32 ),
+        ( c: ( f: dword($EB194F8E); e:  101 ); e10:  40 ),
+        ( c: ( f: dword($AF298D05); e:  128 ); e10:  48 )
+    );
+var
+    i, min10: integer;
+begin
+    // find index
+    min10 := cache[ low( cache ) ].e10;
+    if ( exp10 <= min10 ) then
+        i := 0
+    else
+    begin
+        i := ( exp10 - min10 ) div 8;
+        if ( i >= high(cache) ) then
+            i := high(cache)
+        else
+            if ( cache[ i ].e10 <> exp10 ) then
+                inc( i ); // round-up
+    end;
+    // generate result
+    factor := cache[ i ];
+end;
+{$endif VALREAL_32}
+//**************************************
+{$ifdef VALREAL_64}
+const
+    // alpha =-61; gamma = 0
+    // full cache: 1E-450 .. 1E+432, step = 1E+18
+    // sparse = 1/10
+    C_PWR10_DELTA = 18;
+    C_PWR10_COUNT = 50;
+    base: array [ 0 .. 9 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( f: qword($825ECC24C8737830); e: -362 ); e10:  -90 ),
+        ( c: ( f: qword($E2280B6C20DD5232); e: -303 ); e10:  -72 ),
+        ( c: ( f: qword($C428D05AA4751E4D); e: -243 ); e10:  -54 ),
+        ( c: ( f: qword($AA242499697392D3); e: -183 ); e10:  -36 ),
+        ( c: ( f: qword($9392EE8E921D5D07); e: -123 ); e10:  -18 ),
+        ( c: ( f: qword($8000000000000000); e:  -63 ); e10:    0 ),
+        ( c: ( f: qword($DE0B6B3A76400000); e:   -4 ); e10:   18 ),
+        ( c: ( f: qword($C097CE7BC90715B3); e:   56 ); e10:   36 ),
+        ( c: ( f: qword($A70C3C40A64E6C52); e:  116 ); e10:   54 ),
+        ( c: ( f: qword($90E40FBEEA1D3A4B); e:  176 ); e10:   72 )
+    );
+    factor_plus: array [ 0 .. 1 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( f: qword($F6C69A72A3989F5C); e:   534 ); e10:  180 ),
+        ( c: ( f: qword($EDE24AE798EC8284); e:  1132 ); e10:  360 ) 
+    );
+    factor_minus: array [ 0 .. 1 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( f: qword($84C8D4DFD2C63F3B); e:  -661 ); e10: -180 ),
+        ( c: ( f: qword($89BF722840327F82); e: -1259 ); e10: -360 ) 
+    );
+    corrector: array [ 0 .. C_PWR10_COUNT - 1 ] of shortint = (
+    // extra mantissa correction [ulp; signed]
+        0,  0,  0,  0,  1,  0,  0,  0,  1, -1,
+        0,  1,  1,  1, -1,  0,  0,  1,  0, -1,
+        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+       -1,  0,  0, -1,  0,  0,  0,  0,  0, -1,
+        0,  0,  0,  0,  1,  0,  0,  0, -1,  0
+    );
+{$endif VALREAL_64}
+//**************************************
+{$ifdef VALREAL_80}
+const
+    // alpha =-93; gamma =+30
+    // full cache: 1E-5032 .. 1E+4995, step = 1E+37
+    // sparse = 1/16
+    C_PWR10_DELTA = 37;
+    C_PWR10_COUNT = 272;
+    base: array [ 0 .. 15 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( f: qword($07286FAA1AF5AF66); fh: dword($D1476E2C); e:  -1079 ); e10:  -296 ),
+        ( c: ( f: qword($99107C22CB550FB4); fh: dword($C4CE17B3); e:   -956 ); e10:  -259 ),
+        ( c: ( f: qword($99F6858428E2557B); fh: dword($B9131798); e:   -833 ); e10:  -222 ),
+        ( c: ( f: qword($4738705E9624AB51); fh: dword($AE0B158B); e:   -710 ); e10:  -185 ),
+        ( c: ( f: qword($0D5FDAF5C13E60D1); fh: dword($A3AB6658); e:   -587 ); e10:  -148 ),
+        ( c: ( f: qword($163FA42E504BCED2); fh: dword($99EA0196); e:   -464 ); e10:  -111 ),
+        ( c: ( f: qword($483BB9B9B1C6F22B); fh: dword($90BD77F3); e:   -341 ); e10:   -74 ),
+        ( c: ( f: qword($545C75757E50D641); fh: dword($881CEA14); e:   -218 ); e10:   -37 ),
+        ( c: ( f: qword($0000000000000000); fh: dword($80000000); e:    -95 ); e10:     0 ),
+        ( c: ( f: qword($BB48DB201E86D400); fh: dword($F0BDC21A); e:     27 ); e10:    37 ),
+        ( c: ( f: qword($4DCDAB14C696963C); fh: dword($E264589A); e:    150 ); e10:    74 ),
+        ( c: ( f: qword($C1D1EA966C9E18AC); fh: dword($D4E5E2CD); e:    273 ); e10:   111 ),
+        ( c: ( f: qword($C8965D3D6F928295); fh: dword($C83553C5); e:    396 ); e10:   148 ),
+        ( c: ( f: qword($96706114873D5D9F); fh: dword($BC4665B5); e:    519 ); e10:   185 ),
+        ( c: ( f: qword($56105DAD7425A83F); fh: dword($B10D8E14); e:    642 ); e10:   222 ),
+        ( c: ( f: qword($B84603568A892ABB); fh: dword($A67FF273); e:    765 ); e10:   259 )
+    );
+    factor_plus: array [ 0 .. 7 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( f: qword($3576D3D149738BA0); fh: dword($BF87DECC); e:   1871 ); e10:   592 ),
+        ( c: ( f: qword($750E83050A40DE03); fh: dword($8F4C0691); e:   3838 ); e10:  1184 ),
+        ( c: ( f: qword($727E5D9756BC4BF8); fh: dword($D66B8D68); e:   5804 ); e10:  1776 ),
+        ( c: ( f: qword($CE9DB63FD51AF6A3); fh: dword($A06C0BD4); e:   7771 ); e10:  2368 ),
+        ( c: ( f: qword($5A7ADBC5B8787D89); fh: dword($F00B82D7); e:   9737 ); e10:  2960 ),
+        ( c: ( f: qword($22D732D7AE7EDAA7); fh: dword($B397FD9A); e:  11704 ); e10:  3552 ),
+        ( c: ( f: qword($CCD2839E0367500B); fh: dword($865DB7A9); e:  13671 ); e10:  4144 ),
+        ( c: ( f: qword($FCBEE713F3BE171A); fh: dword($C90E78C7); e:  15637 ); e10:  4736 )
+    );
+    factor_minus: array [ 0 .. 7 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( f: qword($2F85DC7AE66FEACF); fh: dword($AB15B5D2); e:  -2062 ); e10:  -592 ),
+        ( c: ( f: qword($4237088F4C7284FA); fh: dword($E4AC057C); e:  -4029 ); e10: -1184 ),
+        ( c: ( f: qword($D2DCB34CEC42875C); fh: dword($98D24C2F); e:  -5995 ); e10: -1776 ),
+        ( c: ( f: qword($B50918191D8106CD); fh: dword($CC42DD5C); e:  -7962 ); e10: -2368 ),
+        ( c: ( f: qword($10CF24303CA163B8); fh: dword($8881FC6C); e:  -9928 ); e10: -2960 ),
+        ( c: ( f: qword($BF10EA474FE1E9B1); fh: dword($B674CE73); e: -11895 ); e10: -3552 ),
+        ( c: ( f: qword($478E074A0E85FC7F); fh: dword($F3DEFE25); e: -13862 ); e10: -4144 ),
+        ( c: ( f: qword($A3BD093CC62364C2); fh: dword($A2FAA242); e: -15828 ); e10: -4736 )
+    );
+    corrector: array [ 0 .. C_PWR10_COUNT - 1 ] of shortint = (
+    // extra mantissa correction [ulp; signed]
+        0,  0,  0,  1,  0,  0,  1,  1,  0,  0,  1,  1, -1,  1,  0,  0,
+        0,  1,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,
+        0,  0,  0,  0,  0,  0,  1,  2,  2,  0,  1,  1,  0,  0, -2,  0,
+        2,  0,  1,  1,  1,  1,  1,  2,  0,  0,  2,  1,  0,  1,  0,  0,
+        0,  0,  1, -1,  0,  0,  1,  1,  0,  0,  1,  0, -1,  0, -1,  0,
+        0,  0,  1,  0,  0,  0,  1,  0,  0,  0,  1,  1, -1,  0, -1,  1,
+        0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0, -1,  0,
+       -1,  0,  0, -1,  0, -1,  1,  1,  0, -1,  0,  0, -1, -1, -1,  0,
+        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+       -1,  0,  0,  0,  0, -1,  0, -1,  0,  0,  0,  0,  0,  0,  0,  0,
+        2,  2,  1,  1,  0,  0,  0,  2,  0,  0,  1,  1,  0,  0,  1,  1,
+        0,  0,  1,  0,  0,  0,  1,  2,  0,  0,  1,  0,  0,  0, -1,  0,
+        0,  0,  2,  0,  0,  0,  1,  1,  0,  0,  0,  1, -1,  1,  0,  1,
+        0,  0,  0, -1,  0,  0,  0,  1,  0,  0,  1,  0,  0,  0,  0,  0,
+        0,  0,  1,  1, -1,  0,  0,  2,  0,  0,  1,  1,  0,  1,  1,  1,
+       -1, -1,  1, -2,  0,  0,  0, -1,  1, -1,  1, -1, -1, -1,  0,  0,
+        1,  1,  0,  0,  0,  0,  1,  1,  0,  0,  1,  0,  0,  0,  0,  0
+    );
+{$endif VALREAL_80}
+//**************************************
+{$ifdef VALREAL_128}
+const
+    // alpha =-125; gamma = -2
+    // full cache: 1E-5032 .. 1E+4995, step = 1E+37
+    // sparse = 1/16
+    C_PWR10_DELTA = 37;
+    C_PWR10_COUNT = 272;
+    base: array [ 0 .. 15 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( fh: qword($D1476E2C07286FAA); f: qword($1AF5AF660DB4AEE2); e:  -1111 ); e10:  -296 ),
+        ( c: ( fh: qword($C4CE17B399107C22); f: qword($CB550FB4384D21D4); e:   -988 ); e10:  -259 ),
+        ( c: ( fh: qword($B913179899F68584); f: qword($28E2557B59846E3F); e:   -865 ); e10:  -222 ),
+        ( c: ( fh: qword($AE0B158B4738705E); f: qword($9624AB50B148D446); e:   -742 ); e10:  -185 ),
+        ( c: ( fh: qword($A3AB66580D5FDAF5); f: qword($C13E60D0D2E0EBBA); e:   -619 ); e10:  -148 ),
+        ( c: ( fh: qword($99EA0196163FA42E); f: qword($504BCED1BF8E4E46); e:   -496 ); e10:  -111 ),
+        ( c: ( fh: qword($90BD77F3483BB9B9); f: qword($B1C6F22B5E6F48C3); e:   -373 ); e10:   -74 ),
+        ( c: ( fh: qword($881CEA14545C7575); f: qword($7E50D64177DA2E55); e:   -250 ); e10:   -37 ),
+        ( c: ( fh: qword($8000000000000000); f: qword($0000000000000000); e:   -127 ); e10:     0 ),
+        ( c: ( fh: qword($F0BDC21ABB48DB20); f: qword($1E86D40000000000); e:     -5 ); e10:    37 ),
+        ( c: ( fh: qword($E264589A4DCDAB14); f: qword($C696963C7EED2DD2); e:    118 ); e10:    74 ),
+        ( c: ( fh: qword($D4E5E2CDC1D1EA96); f: qword($6C9E18AC7007C91A); e:    241 ); e10:   111 ),
+        ( c: ( fh: qword($C83553C5C8965D3D); f: qword($6F92829494E5ACC7); e:    364 ); e10:   148 ),
+        ( c: ( fh: qword($BC4665B596706114); f: qword($873D5D9F0DDE1FEF); e:    487 ); e10:   185 ),
+        ( c: ( fh: qword($B10D8E1456105DAD); f: qword($7425A83E872C5F47); e:    610 ); e10:   222 ),
+        ( c: ( fh: qword($A67FF273B8460356); f: qword($8A892ABAF368F137); e:    733 ); e10:   259 )
+    );
+    factor_plus: array [ 0 .. 7 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( fh: qword($BF87DECC3576D3D1); f: qword($49738B9F99B4642D); e:   1839 ); e10:   592 ),
+        ( c: ( fh: qword($8F4C0691750E8305); f: qword($0A40DE037C9AD730); e:   3806 ); e10:  1184 ),
+        ( c: ( fh: qword($D66B8D68727E5D97); f: qword($56BC4BF837B34968); e:   5772 ); e10:  1776 ),
+        ( c: ( fh: qword($A06C0BD4CE9DB63F); f: qword($D51AF6A3244A6983); e:   7739 ); e10:  2368 ),
+        ( c: ( fh: qword($F00B82D75A7ADBC5); f: qword($B8787D891AB45D5B); e:   9705 ); e10:  2960 ),
+        ( c: ( fh: qword($B397FD9A22D732D7); f: qword($AE7EDAA76FBBD923); e:  11672 ); e10:  3552 ),
+        ( c: ( fh: qword($865DB7A9CCD2839E); f: qword($0367500A8E9A1790); e:  13639 ); e10:  4144 ),
+        ( c: ( fh: qword($C90E78C7FCBEE713); f: qword($F3BE171A27BF81DB); e:  15605 ); e10:  4736 )
+    );
+    factor_minus: array [ 0 .. 7 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( fh: qword($AB15B5D22F85DC7A); f: qword($E66FEACEB7836F69); e:  -2094 ); e10:  -592 ),
+        ( c: ( fh: qword($E4AC057C4237088F); f: qword($4C7284F9EDDA793D); e:  -4061 ); e10: -1184 ),
+        ( c: ( fh: qword($98D24C2FD2DCB34C); f: qword($EC42875C0B22B986); e:  -6027 ); e10: -1776 ),
+        ( c: ( fh: qword($CC42DD5CB5091819); f: qword($1D8106CCF8EE85B4); e:  -7994 ); e10: -2368 ),
+        ( c: ( fh: qword($8881FC6C10CF2430); f: qword($3CA163B873AA88A6); e:  -9960 ); e10: -2960 ),
+        ( c: ( fh: qword($B674CE73BF10EA47); f: qword($4FE1E9B0FCDF7B3D); e: -11927 ); e10: -3552 ),
+        ( c: ( fh: qword($F3DEFE25478E074A); f: qword($0E85FC7F4EDBD3CB); e: -13894 ); e10: -4144 ),
+        ( c: ( fh: qword($A2FAA242A3BD093C); f: qword($C62364C260A887E2); e: -15860 ); e10: -4736 )
+    );
+    corrector: array [ 0 .. C_PWR10_COUNT - 1 ] of shortint = (
+      // extra mantissa correction [ulp; signed]
+        1,  0,  1,  0,  1,  1,  0,  0,  0,  0,  0,  1,  0,  0,  2,  0,
+       -1, -1,  0, -1,  0, -1,  0,  0, -1,  0,  0,  0,  0,  0,  0,  0,
+        1,  0,  0,  0,  1, -1,  0, -1, -1,  1,  0,  1,  0,  0,  1,  1,
+        0, -2,  0,  0,  0, -1,  0,  0,  0,  0, -2,  0,  0,  0,  0,  0,
+        0, -1,  1,  0,  1,  0,  0, -1,  0,  1,  0,  0,  1,  0,  1,  1,
+        1, -1,  0,  0,  1, -1,  0,  0,  0,  1,  0,  1,  1, -1,  1,  1,
+        0,  0,  1,  0,  0,  0, -1,  0, -1,  0,  0,  0,  0,  0,  0,  1,
+        0,  0,  2,  1,  0, -1, -1, -1, -1,  0, -1,  1,  0, -1,  0,  0,
+        0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,
+        0,  0,  0, -1,  1, -1, -1,  0, -1,  0, -1,  0,  0,  0,  0,  0,
+        1, -1,  2,  1,  2,  0, -1,  1,  0,  0,  0,  1,  2,  0,  1,  1,
+        0,  0,  1,  0,  1,  0,  0,  0,  0,  1,  0,  1,  1,  0,  1,  1,
+        0,  0,  0,  1,  0, -1, -1,  0, -1,  0,  0,  0,  0,  0,  0,  1,
+       -1, -1,  0,  0,  0,  0,  0, -1, -1,  0,  0,  0,  1,  0,  0,  0,
+        0,  0,  1,  0, -1,  0,  0,  0, -1,  0, -1,  0,  1,  0,  0, -1,
+        0, -1,  1, -1,  1, -1,  0, -1,  0,  1, -1,  0,  1,  1,  1,  1,
+        0, -1,  1, -1,  0, -2,  0, -1, -1,  0, -1,  0,  0, -1,  0,  0
+    );
+{$endif VALREAL_128}
+//**************************************
+{$ifndef VALREAL_32} // common for float64..float128
+var
+    i, xmul, inod, min10: integer;
+    A: TDIY_FP_Power_of_10;
+  {$ifdef VALREAL_80}
+    ch: dword;
+  {$endif}
+  {$ifdef VALREAL_128}
+    ch: qword;
+  {$endif}
+    cx: shortint;
+begin
+    // find non-sparse index
+    min10 := base [ low( base ) ].e10 + factor_minus[ high( factor_minus ) ].e10;
+    if ( exp10 <= min10 ) then
+        i := 0
+    else
+    begin
+        i := ( exp10 - min10 ) div C_PWR10_DELTA;
+        if ( i * C_PWR10_DELTA + min10 <> exp10 ) then
+            inc( i ); // round-up
+        if ( i > C_PWR10_COUNT - 1 ) then
+            i := C_PWR10_COUNT - 1;
+    end;
+    // generate result
+    inod := i mod length( base );
+    xmul := ( i div length( base ) ) - length( factor_minus );
+    if ( xmul = 0 ) then
+    begin
+        // base
+        factor := base[ inod ];
+        exit;
+    end;
+    // surrogate
+    A := base[ inod ];
+    if ( xmul > 0 ) then
+    begin
+        dec( xmul );
+        factor.e10 := A.e10 + factor_plus[ xmul ].e10;
+        if ( A.e10 <> 0 ) then
+            factor.c := diy_fp_multiply( A.c, factor_plus[ xmul ].c, TRUE )
+        else
+        begin
+            // exact
+            factor.c := factor_plus[ xmul ].c;
+            exit;
+        end;
+    end
+    else
+    begin
+        xmul := - ( xmul + 1 );
+        factor.e10 := A.e10 + factor_minus[ xmul ].e10;
+        if ( A.e10 <> 0 ) then
+            factor.c := diy_fp_multiply( A.c, factor_minus[ xmul ].c, TRUE )
+        else
+        begin
+            // exact
+            factor.c := factor_minus[ xmul ].c;
+            exit;
+        end;
+    end;
+    // adjust mantissa
+    cx := corrector[ i ];
+    if ( cx <> 0 ) then
+  {$ifdef VALREAL_64}
+        inc( factor.c.f, int64( cx ) );
+  {$else VALREAL_80 | VALREAL_128}
+    begin
+        ch := 0;
+        if ( cx < 0 ) then
+            dec( ch );
+        diy_util_add( factor.c.fh, factor.c.f, ch, int64( cx ) );
+    end;
+  {$endif VALREAL_*}
+    //
+end;
+{$endif VALREAL_64..VALREAL_128}
+
+(*==========================================================================*
+ *                                                                          *
+ *                              Float -> ASCII                              *
+ *                                                                          *
+ *==========================================================================*)
+
+procedure str_real( min_width, frac_digits: integer; const v: ValReal; real_type: TReal_Type; out str: shortstring );
+
+{$undef VALREAL_PACK}
+{$i flt_pack.inc}
+
+const
+{$ifdef VALREAL_32}
+    C_FRAC2_BITS  = 23;
+    C_EXP2_BIAS   = 127;
+    C_DIY_FP_Q    = 32;
+    C_GRISU_ALPHA =-29;
+    C_GRISU_GAMMA = 0;
+    RT_NATIVE = RT_S32REAL;
+{$endif VALREAL_32}
+{$ifdef VALREAL_64}
+    C_FRAC2_BITS  = 52;
+    C_EXP2_BIAS   = 1023;
+    C_DIY_FP_Q    = 64;
+    C_GRISU_ALPHA =-61;
+    C_GRISU_GAMMA = 0;
+    RT_NATIVE = RT_S64REAL;
+{$endif VALREAL_64}
+{$ifdef VALREAL_80}
+    C_FRAC2_BITS  = 63;
+    C_EXP2_BIAS   = 16383;
+    C_DIY_FP_Q    = 96;
+    C_GRISU_ALPHA =-93;
+    C_GRISU_GAMMA = 30;
+    RT_NATIVE = RT_S80REAL;
+{$endif VALREAL_80}
+{$ifdef VALREAL_128}
+    C_FRAC2_BITS  = 112;
+    C_EXP2_BIAS   = 16383;
+    C_DIY_FP_Q    = 128;
+    C_GRISU_ALPHA =-125;
+    C_GRISU_GAMMA =-2;
+    RT_NATIVE = RT_S128REAL;
+{$endif VALREAL_128}
+
+(****************************************************************************)
+    // handy const
+    C_EXP2_SPECIAL = C_EXP2_BIAS * 2 + 1;
+{$ifdef VALREAL_32}
+    C_MANT2_INTEGER = dword(1) shl C_FRAC2_BITS;
+{$endif VALREAL_32}
+{$if defined(VALREAL_64) or defined(VALREAL_80)}
+    C_MANT2_INTEGER = qword(1) shl C_FRAC2_BITS;
+{$endif VALREAL_64 | VALREAL_80}
+{$ifdef VALREAL_128}
+    C_MANT2_INTEGER_H = qword(1) shl ( C_FRAC2_BITS - 64 );
+{$endif VALREAL_128}
+
+    C_MAX_WIDTH = 255; // shortstring
+
+{$ifdef fpc_softfpu_implementation}
+    C_NO_MIN_WIDTH = -1; // just for convenience
+{$else}
+    C_NO_MIN_WIDTH = -32767; // this value is the one used internally by FPC
+{$endif}
+
+type
+    TAsciiDigits = array [ 0 .. 39 ] of byte;
+
+(*-------------------------------------------------------
+ | gen_digits_32 [local]
+ | gen_digits_64 [local] (used only for float64..float128 -> ASCII)
+ |
+ | These routines perform conversion of 32-bit/64-bit unsigned integer
+ | to the sequence of byte-sized decimal digits.
+ |
+ *-------------------------------------------------------*)
+function gen_digits_32( out buf: TAsciiDigits; pos: integer; x: dword; pad_9zero: boolean = false ): integer;
+const
+    digits: array [ 0 .. 9 ] of dword = (
+              0,
+             10,
+            100,
+           1000,
+          10000,
+         100000,
+        1000000,
+       10000000,
+      100000000,
+     1000000000
+    );
+var
+    n: integer;
+    m, z: dword;
+begin
+    // Calculate amount of digits
+    if ( x = 0 ) then
+        // emit nothing if padding is not required
+        n := 0
+    else
+    begin
+        n :=( ( BSRdword( x ) + 1 ) * 1233 ) shr 12;
+        if ( x >= digits[ n ] ) then
+            inc( n );
+    end;
+    if pad_9zero and ( n < 9 ) then
+        n := 9;
+    gen_digits_32 := n;
+    // Emit digits
+    m := x;
+    while ( n > 0 ) do
+    begin
+        dec( n );
+        if ( m <> 0 ) then
+        begin
+            z := m div 10;
+            buf[ pos + n ] := m - z * 10;
+            m := z;
+        end
+        else
+            buf[ pos + n ] := 0;
+    end;
+end;
+
+{$ifndef VALREAL_32}
+function gen_digits_64( out buf: TAsciiDigits; pos: integer; const x: qword; pad_19zero: boolean = false ): integer;
+var
+    n_digits: integer;
+    temp: qword;
+    splitl, splitm, splith: dword;
+begin
+    // Split X into 3 unsigned 32-bit integers; lower two should be less than 10 digits long
+    if ( x < 1000000000 ) then
+    begin
+        splith := 0;
+        splitm := 0;
+        splitl := x;
+    end
+    else
+    begin
+        temp := x div 1000000000;
+        splitl := x - temp * 1000000000;
+        if ( temp < 1000000000 ) then
+        begin
+            splith := 0;
+            splitm := temp;
+        end
+        else
+        begin
+            splith := temp div 1000000000;
+            splitm := lo( temp ) - splith * 1000000000;
+        end;
+    end;
+    // Generate digits
+    n_digits := gen_digits_32( buf, pos, splith, false );
+    if pad_19zero and ( n_digits = 0 ) then
+    begin
+        // at most 18 digits expected from splitm and splitl, so add one more
+        buf[ pos ] := 0;
+        n_digits := 1;
+    end;
+    inc( n_digits, gen_digits_32( buf, pos + n_digits, splitm, n_digits <> 0 ) );
+    inc( n_digits, gen_digits_32( buf, pos + n_digits, splitl, n_digits <> 0 ) );
+    gen_digits_64 := n_digits;
+end;
+{$endif VALREAL_64..VALREAL_128}
+
+(*-------------------------------------------------------
+ | round_digits [local]
+ |
+ | Performs digit sequence rounding, returns decimal point correction.
+ |
+ *-------------------------------------------------------*)
+function round_digits( var buf: TAsciiDigits; var n_current: integer; n_max: integer; half_round_to_even: boolean = true ): integer;
+var
+    n: integer;
+    dig_round, dig_sticky: byte;
+  {$ifdef GRISU1_F2A_AGRESSIVE_ROUNDUP}
+    i: integer;
+  {$endif}
+begin
+    round_digits := 0;
+    n := n_current;
+{$ifdef grisu1_debug}
+    assert( n_max >= 0 );
+    assert( n_max < n );
+{$endif grisu1_debug}
+    n_current := n_max;
+    // Get round digit
+    dig_round := buf[n_max];
+
+{$ifdef GRISU1_F2A_AGRESSIVE_ROUNDUP}
+    // Detect if rounding-up the second last digit turns the "dig_round"
+    // into "5"; also make sure we have at least 1 digit between "dig_round"
+    // and the second last.
+    if not half_round_to_even then
+        if ( dig_round = 4 ) and ( n_max < n - 3 ) then
+            if ( buf[ n - 2 ] >= 8 ) then // somewhat arbitrary..
+            begin
+                // check for only "9" are in between
+                i := n - 2;
+                repeat
+                    dec( i );
+                until ( i = n_max ) or ( buf[i] <> 9 );
+                if ( i = n_max ) then
+                    // force round-up
+                    dig_round := 9; // any value ">=5"
+            end;
+{$endif}
+
+    if ( dig_round < 5 ) then
+        exit;
+
+    // Handle "round half to even" case
+    if ( dig_round = 5 ) and half_round_to_even and ( ( n_max = 0 ) or ( buf[ n_max - 1 ] and 1 = 0 ) ) then
+    begin
+        // even and a half: check if exactly the half
+        dig_sticky := 0;
+        while ( n > n_max + 1 ) and ( dig_sticky = 0 ) do
+        begin
+            dec( n );
+            dig_sticky := buf[n];
+        end;
+        if ( dig_sticky = 0 ) then
+            exit; // exactly a half -> no rounding is required
+    end;
+
+    // Round-up
+    while ( n_max > 0 ) do
+    begin
+        dec( n_max );
+        inc( buf[n_max] );
+        if ( buf[n_max] < 10 ) then
+        begin
+            // no more overflow: stop now
+            n_current := n_max + 1;
+            exit;
+        end;
+        // continue rounding
+    end;
+
+    // Overflow out of the 1st digit, all n_max digits became 0
+    buf[0] := 1;
+    n_current := 1;
+    round_digits := 1;
+end;
+
+(*-------------------------------------------------------
+ | try_return_fixed [local]
+ |
+ | This routine tries to format the number in the fixed-point representation.
+ | If the resulting string is estimated to be too long to fit into shortstring,
+ | routine returns FALSE giving the caller a chance to return the exponential
+ | representation.
+ | Otherwise, it returns TRUE.
+ |
+ | Not implemented [and why to do it at all?]:
+ |     Here also a good place to limit the fixed point formatting by exponent
+ |     range, falling back to exponential notation (just return FALSE).
+ |
+ *-------------------------------------------------------*)
+function try_return_fixed( out str: shortstring; minus: boolean; const digits: TAsciiDigits; n_digits_have, fixed_dot_pos, min_width, frac_digits: integer ): boolean;
+var
+    rounded: boolean;
+    temp_round: TAsciiDigits;
+    i, j, len, cut_digits_at: integer;
+    n_spaces, n_spaces_max, n_before_dot, n_before_dot_pad0, n_after_dot_pad0, n_after_dot, n_tail_pad0: integer;
+begin
+    try_return_fixed := false;
+{$ifdef grisu1_debug}
+    assert( n_digits_have >= 0 );
+    assert( min_width <= C_MAX_WIDTH );
+    assert( frac_digits >= 0 );
+    assert( frac_digits <= C_MAX_WIDTH - 3 );
+{$endif grisu1_debug}
+    // Round digits if necessary
+    rounded := false;
+    cut_digits_at := fixed_dot_pos + frac_digits;
+    if ( cut_digits_at < 0 ) then
+        // zero
+        n_digits_have := 0
+    else
+    if ( cut_digits_at < n_digits_have ) then
+    begin
+        // round digits
+        if ( n_digits_have > 0 ) then
+            move( digits, temp_round, n_digits_have * sizeof( digits[0] ) );
+        inc( fixed_dot_pos, round_digits( temp_round, n_digits_have, cut_digits_at {$ifdef GRISU1_F2A_HALF_ROUNDUP}, false {$endif} ) );
+        rounded := true;
+    end;
+    // Before dot: digits, pad0
+    if ( fixed_dot_pos <= 0 ) or ( n_digits_have = 0 ) then
+    begin
+        n_before_dot := 0;
+        n_before_dot_pad0 := 1;
+    end
+    else
+    if ( fixed_dot_pos > n_digits_have ) then
+    begin
+        n_before_dot := n_digits_have;
+        n_before_dot_pad0 := fixed_dot_pos - n_digits_have;
+    end
+    else
+    begin
+        n_before_dot := fixed_dot_pos;
+        n_before_dot_pad0 := 0;
+    end;
+    // After dot: pad0, digits, pad0
+    if ( fixed_dot_pos < 0 ) then
+        n_after_dot_pad0 := - fixed_dot_pos
+    else
+        n_after_dot_pad0 := 0;
+    if ( n_after_dot_pad0 > frac_digits ) then
+        n_after_dot_pad0 := frac_digits;
+    n_after_dot := n_digits_have - n_before_dot;
+    n_tail_pad0 := frac_digits - n_after_dot - n_after_dot_pad0;
+{$ifdef grisu1_debug}
+    assert( n_tail_pad0 >= 0 );
+{$endif grisu1_debug}
+    // Estimate string length
+    len := ord( minus ) + n_before_dot + n_before_dot_pad0;
+    if ( frac_digits > 0 ) then
+        inc( len, n_after_dot_pad0 + n_after_dot + n_tail_pad0 + 1 );
+    n_spaces_max := C_MAX_WIDTH - len;
+    if ( n_spaces_max < 0 ) then
+        exit;
+    // Calculate space-padding length
+    n_spaces := min_width - len;
+    if ( n_spaces > n_spaces_max ) then
+        n_spaces := n_spaces_max;
+    if ( n_spaces > 0 ) then
+        inc( len, n_spaces );
+    // Allocate storage
+    SetLength( str, len );
+    i := 1;
+    // Leading spaces
+    if ( n_spaces > 0 ) then
+    begin
+        fillchar( str[i], n_spaces, ' ' );
+        inc( i, n_spaces );
+    end;
+    // Sign
+    if minus then
+    begin
+        str[i] := '-';
+        inc( i );
+    end;
+    // Integer significant digits
+    j := 0;
+    if rounded then
+        while ( n_before_dot > 0 ) do
+        begin
+            str[i] := char( temp_round[j] + ord('0') );
+            inc( i );
+            inc( j );
+            dec( n_before_dot );
+        end
+    else
+        while ( n_before_dot > 0 ) do
+        begin
+            str[i] := char( digits[j] + ord('0') );
+            inc( i );
+            inc( j );
+            dec( n_before_dot );
+        end;
+    // Integer 0-padding
+    if ( n_before_dot_pad0 > 0 ) then
+    begin
+        fillchar( str[i], n_before_dot_pad0, '0' );
+        inc( i, n_before_dot_pad0 );
+    end;
+    //
+    if ( frac_digits <> 0 ) then
+    begin
+        // Dot
+        str[i] := '.';
+        inc( i );
+        // Pre-fraction 0-padding
+        if ( n_after_dot_pad0 > 0 ) then
+        begin
+            fillchar( str[i], n_after_dot_pad0, '0' );
+            inc( i, n_after_dot_pad0 );
+        end;
+        // Fraction significant digits
+        if rounded then
+            while ( n_after_dot > 0 ) do
+            begin
+                str[i] := char( temp_round[j] + ord('0') );
+                inc( i );
+                inc( j );
+                dec( n_after_dot );
+            end
+        else
+            while ( n_after_dot > 0 ) do
+            begin
+                str[i] := char( digits[j] + ord('0') );
+                inc( i );
+                inc( j );
+                dec( n_after_dot );
+            end;
+        // Tail 0-padding
+        if ( n_tail_pad0 > 0 ) then
+        begin
+            fillchar( str[i], n_tail_pad0, '0' );
+{$ifdef grisu1_debug}
+            inc( i, n_tail_pad0 );
+{$endif grisu1_debug}
+        end;
+    end;
+    //
+{$ifdef grisu1_debug}
+    assert( i = len + 1 );
+{$endif grisu1_debug}
+    try_return_fixed := true
+end;
+
+(*-------------------------------------------------------
+ | return_exponential [local]
+ |
+ | Formats the number in the exponential representation.
+ |
+ *-------------------------------------------------------*)
+procedure return_exponential( out str: shortstring; minus: boolean; const digits: TAsciiDigits; n_digits_have, n_digits_req, d_exp, n_digits_exp, min_width: integer );
+var
+    e_minus: boolean;
+    i, j, len, n_exp, n_spaces, n_spaces_max: integer;
+    buf_exp: TAsciiDigits;
+begin
+{$ifdef grisu1_debug}
+    assert( n_digits_have >= 0 );
+    assert( n_digits_have <= n_digits_req );
+    assert( min_width <= C_MAX_WIDTH );
+{$endif grisu1_debug}
+    // Prepare exponent
+    e_minus := ( d_exp < 0 );
+    if e_minus then
+        d_exp := - d_exp;
+    n_exp := gen_digits_32( buf_exp, 0, d_exp, false );
+    if ( n_exp <= n_digits_exp ) then
+        len := n_digits_exp
+    else
+        len := n_exp;
+    // Estimate string length
+    inc( len, 1{sign} + n_digits_req + 1{E} + 1{E-sign} );
+    if ( n_digits_req > 1 ) then
+        inc( len ); // dot
+    // Calculate space-padding length
+    n_spaces_max := C_MAX_WIDTH - len;
+    n_spaces := min_width - len;
+    if ( n_spaces > n_spaces_max ) then
+        n_spaces := n_spaces_max;
+    if ( n_spaces > 0 ) then
+        inc( len, n_spaces );
+    // Allocate storage
+    SetLength( str, len );
+    i := 1;
+    // Leading spaces
+    if ( n_spaces > 0 ) then
+    begin
+        fillchar( str[i], n_spaces, ' ' );
+        inc( i, n_spaces );
+    end;
+    // Sign
+    if minus then
+        str[i] := '-'
+    else
+        str[i] := ' ';
+    inc( i );
+    // Integer part
+    if ( n_digits_have > 0 ) then
+        str[i] := char( digits[0] + ord('0') )
+    else
+        str[i] := '0';
+    inc( i );
+    // Dot
+    if ( n_digits_req > 1 ) then
+    begin
+        str[i] := '.';
+        inc( i );
+    end;
+    // Fraction significant digits
+    j := 1;
+    while ( j < n_digits_have ) and ( j < n_digits_req ) do
+    begin
+        str[i] := char( digits[j] + ord('0') );
+        inc( i );
+        inc( j );
+    end;
+    // Fraction 0-padding
+    j := n_digits_req - j;
+    if ( j > 0 ) then
+    begin
+        fillchar( str[i], j, '0' );
+        inc( i, j );
+    end;
+    // Exponent designator
+    str[i] := 'E';
+    inc( i );
+    // Exponent sign
+    if e_minus then
+        str[i] := '-'
+    else
+        str[i] := '+';
+    inc( i );
+    // Exponent 0-padding
+    j := n_digits_exp - n_exp;
+    if ( j > 0 ) then
+    begin
+        fillchar( str[i], j, '0' );
+        inc( i, j );
+    end;
+    // Exponent digits
+    for j := 0 to n_exp - 1 do
+    begin
+        str[i] := char( buf_exp[j] + ord('0') );
+        inc( i );
+    end;
+{$ifdef grisu1_debug}
+    assert( i = len + 1 );
+{$endif grisu1_debug}
+end;
+
+(*-------------------------------------------------------
+ | return_special [local]
+ |
+ | This routine formats one of special results.
+ |
+ *-------------------------------------------------------*)
+procedure return_special(  out str: shortstring; sign: integer; const spec: shortstring; min_width: integer );
+var
+    i, slen, len, n_spaces, n_spaces_max: integer;
+begin
+    slen := length(spec);
+    if ( sign = 0 ) then
+        len := slen
+    else
+        len := slen + 1;
+    n_spaces_max := C_MAX_WIDTH - len;
+    // Calculate space-padding length
+    n_spaces := min_width - len;
+    if ( n_spaces > n_spaces_max ) then
+        n_spaces := n_spaces_max;
+    if ( n_spaces > 0 ) then
+        inc( len, n_spaces );
+    // Allocate storage
+    SetLength( str, len );
+    i := 1;
+    // Leading spaces
+    if ( n_spaces > 0 ) then
+    begin
+        fillchar( str[i], n_spaces, ' ' );
+        inc( i, n_spaces );
+    end;
+    // Sign
+    if ( sign <> 0 ) then
+    begin
+        if ( sign > 0 ) then
+            str[i] := '+'
+        else
+            str[i] := '-';
+        inc( i );
+    end;
+    // Special
+    move( spec[1], str[i], slen );
+end;
+
+{$if defined(VALREAL_80) or defined(VALREAL_128)}
+(*-------------------------------------------------------
+ | u128_div_u64_to_u64 [local]
+ |
+ | Divides unsigned 128-bit integer by unsigned 64-bit integer.
+ | Returns 64-bit quotient and reminder.
+ |
+ | This routine is used here only for splitting specially prepared unsigned
+ | 128-bit integer into two 64-bit ones before converting it to ASCII.
+ |
+ *-------------------------------------------------------*)
+function u128_div_u64_to_u64( const xh, xl: qword; const y: qword; out quotient, reminder: qword ): boolean;
+var
+    b,                // Number base
+    v,                // Norm. divisor
+    un1, un0,         // Norm. dividend LSD's
+    vn1, vn0,         // Norm. divisor digits
+    q1, q0,           // Quotient digits
+    un64, un21, un10, // Dividend digit pairs
+    rhat: qword;      // A remainder
+    s: integer;       // Shift amount for norm
+begin    
+    // Overflow check
+    if ( xh >= y ) then
+    begin
+        u128_div_u64_to_u64 := false;
+        exit;
+    end;
+    // Count leading zeros
+    s := 63 - BSRqword( y ); // 0 <= s <= 63
+    // Normalize divisor
+    v := y shl s;
+    // Break divisor up into two 32-bit digits
+    vn1 := hi(v);
+    vn0 := lo(v);
+    // Shift dividend left
+    un64 := xh shl s;
+    if ( s > 0 ) then
+        un64 := un64 or ( xl shr ( 64 - s ) );
+    un10 := xl shl s;
+    // Break right half of dividend into two digits
+    un1 := hi(un10);
+    un0 := lo(un10);
+    // Compute the first quotient digit, q1
+    q1 := un64 div vn1;
+    rhat := un64 - q1 * vn1;
+    b := qword(1) shl 32; // Number base
+    while ( q1 >= b ) or ( q1 * vn0 > b * rhat + un1 ) do
+    begin
+        dec( q1 );
+        inc( rhat, vn1 );
+        if rhat >= b then
+            break;
+    end;
+    // Multiply and subtract
+    un21 := un64 * b + un1 - q1 * v;
+    // Compute the second quotient digit, q0
+    q0 := un21 div vn1;
+    rhat := un21 - q0 * vn1;
+    while ( q0 >= b ) or ( q0 * vn0 > b * rhat + un0 ) do
+    begin
+        dec( q0 );
+        inc( rhat, vn1 );
+        if ( rhat >= b ) then
+            break;
+    end;
+    // Result
+    reminder := ( un21 * b + un0 - q0 * v ) shr s;
+    quotient := q1 * b + q0;
+    u128_div_u64_to_u64 := true;
+end;
+{$endif VALREAL_80 | VALREAL_128}
+
+(*-------------------------------------------------------
+ | count_leading_zero [local]
+ |
+ | Counts number of 0-bits at most significant bit position.
+ |
+ *-------------------------------------------------------*)
+{$ifdef VALREAL_32}
+function count_leading_zero( const X: dword ): integer; {$ifdef grisu1_inline}inline;{$endif}
+begin
+    count_leading_zero := 31 - BSRdword( X );
+end;
+{$else not VALREAL_32}
+function count_leading_zero( const X: qword ): integer; {$ifdef grisu1_inline}inline;{$endif}
+begin
+    count_leading_zero := 63 - BSRqword( X );
+end;
+{$endif VALREAL_*}
+
+{$if defined(VALREAL_80) or defined(VALREAL_128)}
+(*-------------------------------------------------------
+ | make_frac_mask [local]
+ |
+ | Makes DIY_FP fractional part mask:
+ |     result := ( 1 shl one.e ) - 1
+ |
+ *-------------------------------------------------------*)
+{$ifdef VALREAL_80}
+procedure make_frac_mask( out h: dword; out l: qword; one_e: integer ); {$ifdef grisu1_inline}inline;{$endif}
+{$else VALREAL_128}
+procedure make_frac_mask( out h, l: qword; one_e: integer ); {$ifdef grisu1_inline}inline;{$endif}
+{$endif VALREAL_*}
+begin
+  {$ifdef grisu1_debug}
+    assert( one_e <= 0 );
+    assert( one_e > - ( sizeof( l ) + sizeof( h ) ) * 8 );
+  {$endif grisu1_debug}
+    if ( one_e <= - 64 ) then
+    begin
+        l := qword( -1 );
+        h := ( {$ifdef VALREAL_128} qword {$endif} ( 1 ) shl ( - one_e - 64 ) ) - 1;
+    end
+    else
+    begin
+        l := ( qword( 1 ) shl ( - one_e ) ) - 1;
+        h := 0;
+    end;
+end;
+{$endif VALREAL_80 | VALREAL_128}
+
+(*-------------------------------------------------------
+ | k_comp [local]
+ |
+ | Calculates the exp10 of a factor required to bring the binary exponent
+ | of the original number into selected [ alpha .. gamma ] range:
+ |     result := ceiling[ ( alpha - e ) * log10(2) ]
+ |
+ *-------------------------------------------------------*)
+function k_comp( e, alpha{, gamma}: integer ): integer;
+{$ifdef fpc_softfpu_implementation}
+///////////////
+//
+// Assuming no HardFloat available.
+// Note: using softfpu here significantly slows down overall
+// conversion performance, so we use integers.
+//
+const
+    D_LOG10_2: TDIY_FP = // log10(2) = 0.301029995663981195213738894724493027
+      {$ifdef VALREAL_32}
+        ( f: dword($9A209A85); e: -33 );
+      {$endif}
+      {$ifdef VALREAL_64}
+        ( f: qword($9A209A84FBCFF799); e: -65 );
+      {$endif}
+      {$ifdef VALREAL_80}
+        ( f: qword($FBCFF7988F8959AC); fh: dword($9A209A84); e: -97 );
+      {$endif}
+      {$ifdef VALREAL_128}
+        ( fh: qword($9A209A84FBCFF798); f: qword($8F8959AC0B7C9178); e: -129 );
+      {$endif}
+var
+    x, n: integer;
+    y, z: TDIY_FP;
+  {$ifdef VALREAL_32}
+    mask_one: dword;
+  {$else not VALREAL_32}
+    mask_one: qword;
+  {$endif}
+  {$ifdef VALREAL_80}
+    mask_oneh: dword;
+  {$endif}
+  {$ifdef VALREAL_128}
+    mask_oneh: qword;
+  {$endif}
+    plus, round_up: boolean;
+begin
+    x := alpha - e;
+    if ( x = 0 ) then
+    begin
+        k_comp := 0;
+        exit;
+    end;
+    plus := ( x > 0 );
+    if plus then
+        y.f := x
+    else
+        y.f := - x;
+    round_up := plus;
+    n := C_DIY_FP_Q - 1 - BSRdword( y.f );
+  {$if defined(VALREAL_32) or defined(VALREAL_64)}
+    y.f := y.f shl n;
+  {$else VALREAL_80 | VALREAL_128}
+    y.fh := 0;
+    diy_util_shl( y.fh, y.f, n );
+  {$endif VALREAL_*}
+    y.e := - n;
+    z := diy_fp_multiply( y, D_LOG10_2, false );
+    if ( z.e <= - C_DIY_FP_Q ) then
+    begin
+        round_up := plus and ( 0 <>
+  {$if defined(VALREAL_32) or defined(VALREAL_64)}
+          z.f
+  {$else VALREAL_80 | VALREAL_128}
+          z.f or z.fh
+  {$endif}
+        );
+        n := 0;
+    end
+    else
+    begin
+        if plus then
+        begin
+          {$if defined(VALREAL_32) or defined(VALREAL_64)}
+            mask_one := ( {$ifdef VALREAL_64} qword {$endif} ( 1 ) shl ( - z.e ) ) - 1;
+            round_up := ( z.f and mask_one <> 0 );
+          {$else VALREAL_80 | VALREAL_128}
+            make_frac_mask( mask_oneh, mask_one, z.e );
+            round_up := ( z.f and mask_one <> 0 ) or ( z.fh and mask_oneh <> 0 );
+          {$endif VALREAL_*}
+        end;
+      {$if defined(VALREAL_32) or defined(VALREAL_64)}
+        n := z.f shr ( - z.e );
+      {$else VALREAL_80 | VALREAL_128}
+        diy_util_shr( z.fh, z.f, - z.e );
+        n := z.f;
+      {$endif VALREAL_*}
+    end;
+    if not plus then
+        n := - n;
+    if round_up then
+        k_comp := n + 1
+    else
+        k_comp := n;
+end;
+{$else not fpc_softfpu_implementation}
+///////////////
+//
+// HardFloat implementation
+//
+{$if defined(SUPPORT_SINGLE) and defined(VALREAL_32)}
+// If available, use single math for VALREAL_32
+var
+    dexp: single;
+const
+    D_LOG10_2: single =
+{$elseif defined(SUPPORT_DOUBLE) and not defined(VALREAL_32)}
+// If available, use double math for all types >VALREAL_32
+var
+    dexp: double;
+const
+    D_LOG10_2: double =
+{$else}
+// Use native math
+var
+    dexp: ValReal;
+const
+    D_LOG10_2: ValReal =
+{$endif}
+    0.301029995663981195213738894724493027; // log10(2)
+var
+    x, n: integer;
+begin
+    x := alpha - e;
+    dexp := x * D_LOG10_2;
+    // ceil( dexp )
+    n := trunc( dexp );
+    if ( x > 0 ) then
+        if ( dexp <> n ) then
+            inc( n ); // round-up
+    k_comp := n;
+end;
+{$endif fpc_softfpu_implementation}
+
+(****************************************************************************)
+var
+    w, D: TDIY_FP;
+    c_mk: TDIY_FP_Power_of_10;
+    n, mk, dot_pos, n_digits_exp, n_digits_need, n_digits_have: integer;
+    n_digits_req, n_digits_sci: integer;
+    minus: boolean;
+  {$ifndef VALREAL_32}
+    fl, one_maskl: qword;
+  {$endif not VALREAL_32}
+  {$ifdef VALREAL_80}
+    templ: qword;
+    fh, one_maskh, temph: dword;
+  {$endif VALREAL_80}
+  {$ifdef VALREAL_128}
+    templ: qword;
+    fh, one_maskh, temph: qword;
+  {$endif VALREAL_128}
+    one_e: integer;
+    one_mask, f: dword;
+    buf: TAsciiDigits;
+
+begin
+
+    // Limit parameters
+    if ( frac_digits > 216 ) then
+        frac_digits := 216; // Delphi compatible
+    if ( min_width <= C_NO_MIN_WIDTH ) then
+        min_width := -1 // no minimal width
+    else
+        if ( min_width < 0 ) then
+            min_width := 0 // minimal width is as short as possible
+        else
+            if ( min_width > C_MAX_WIDTH ) then
+                min_width := C_MAX_WIDTH;
+
+    // Format profile: select "n_digits_need" and "n_digits_exp"
+    n_digits_req := float_format[real_type].nDig_mantissa;
+    n_digits_exp := float_format[real_type].nDig_exp10;
+    // number of digits to be calculated by Grisu
+    n_digits_need := float_format[RT_NATIVE].nDig_mantissa;
+    if ( n_digits_req < n_digits_need ) then
+        n_digits_need := n_digits_req;
+    // number of mantissa digits to be printed in exponential notation
+    if ( min_width < 0 ) then
+        n_digits_sci := n_digits_req
+    else
+    begin
+        n_digits_sci := min_width - 1{sign} - 1{dot} - 1{E} - 1{E-sign} - n_digits_exp;
+        if ( n_digits_sci < 2 ) then
+            n_digits_sci := 2; // at least 2 digits
+        if ( n_digits_sci > n_digits_req ) then
+            n_digits_sci := n_digits_req; // at most requested by real_type
+    end;
+
+    // Float -> DIY_FP
+    w := unpack_float( v, minus );
+
+    // Handle Zero
+    if ( w.e = 0 ) and ( w.f {$ifdef VALREAL_128} or w.fh {$endif} = 0 ) then
+    begin
+        buf[0] := 0; // to avoid "warning: uninitialized"
+        if ( frac_digits >= 0 ) then
+            if try_return_fixed( str, minus, buf, 0, 1, min_width, frac_digits ) then
+                exit
+          {$ifdef grisu1_debug}
+            else
+                assert( FALSE ) // should never fail with these arguments
+          {$endif grisu1_debug};
+        return_exponential( str, minus, buf, 0, n_digits_sci, 0, n_digits_exp, min_width );
+        exit;
+    end;
+
+  {$ifdef VALREAL_80}
+    // Handle non-normals
+    if ( w.e <> 0 ) and ( w.e <> C_EXP2_SPECIAL ) then
+        if ( w.f and C_MANT2_INTEGER = 0 ) then
+        begin
+            // -> QNaN
+            w.f := qword(-1);
+            w.e := C_EXP2_SPECIAL;
+        end;
+  {$endif VALREAL_80}
+
+    // Handle specials
+    if ( w.e = C_EXP2_SPECIAL ) then
+    begin
+        if ( min_width < 0 ) then
+            // backward compat..
+            min_width := float_format[real_type].nDig_mantissa + float_format[real_type].nDig_exp10 + 4;
+        n := 1 - ord(minus) * 2; // default special sign [-1|+1]
+      {$if defined(VALREAL_32) or defined(VALREAL_64)}
+        if ( w.f = 0 ) then
+      {$endif VALREAL_32 | VALREAL_64}
+      {$ifdef VALREAL_80}
+        if ( w.f = qword(C_MANT2_INTEGER) ) then
+      {$endif VALREAL_80}
+      {$ifdef VALREAL_128}
+        if ( w.fh or w.f = 0 ) then
+      {$endif VALREAL_128}
+        begin
+           // Inf
+           return_special( str, n, C_STR_INF, min_width );
+        end
+        else
+        begin
+           // NaN [also pseudo-NaN, pseudo-Inf, non-normal for floatx80]
+       {$ifdef GRISU1_F2A_NAN_SIGNLESS}
+           n := 0;
+       {$endif}
+       {$ifndef GRISU1_F2A_NO_SNAN}
+         {$ifdef VALREAL_128}
+           if ( w.fh and ( C_MANT2_INTEGER_H shr 1 ) = 0 ) then
+         {$else}
+           if ( w.f and ( C_MANT2_INTEGER shr 1 ) = 0 ) then
+         {$endif}
+               return_special( str, n, C_STR_SNAN, min_width )
+           else
+       {$endif GRISU1_F2A_NO_SNAN}
+               return_special( str, n, C_STR_QNAN, min_width );
+        end;
+        exit;
+    end;
+
+    // Handle denormals
+    if ( w.e <> 0 ) then
+    begin
+        // normal
+    {$ifdef VALREAL_128}
+        w.fh := w.fh or C_MANT2_INTEGER_H;
+    {$else not VALREAL_128}
+      {$ifndef VALREAL_80}
+        w.f := w.f or C_MANT2_INTEGER;
+      {$endif not VALREAL_80}
+    {$endif VALREAL_*}
+        n := C_DIY_FP_Q - C_FRAC2_BITS - 1;
+    end
+    else
+    begin
+        // denormal
+    {$ifdef VALREAL_128}
+        if ( w.fh = 0 ) then
+            n := count_leading_zero( w.f ) + 64
+        else
+            n := count_leading_zero( w.fh );
+    {$else not VALREAL_128}
+      {$ifdef VALREAL_80}
+        // also handle pseudo-denormals
+        n := count_leading_zero( w.f ) + 32;
+      {$else VALREAL_32 | VALREAL_64}
+        n := count_leading_zero( w.f );
+      {$endif VALREAL_*}
+    {$endif VALREAL_*}
+        inc( w.e );
+    end;
+
+    // Final normalization
+  {$if defined(VALREAL_32) or defined(VALREAL_64)}
+    w.f := w.f shl n;
+  {$else VALREAL_80 | VALREAL_128}
+    diy_util_shl( w.fh, w.f, n );
+  {$endif VALREAL_*}
+    dec( w.e, C_EXP2_BIAS + n + C_FRAC2_BITS );
+
+    //
+    // 1. Find the normalized "c_mk = f_c * 2^e_c" such that "alpha <= e_c + e_w + q <= gamma"
+    // 2. Define "V = D * 10^k": multiply the input number by "c_mk", do not normalize to land into [ alpha .. gamma ]
+    // 3. Generate digits ( n_digits_need + "round" )
+    //
+
+    if ( C_GRISU_ALPHA <= w.e ) and ( w.e <= C_GRISU_GAMMA ) then
+    begin
+        // no scaling required
+        D := w;
+        c_mk.e10 := 0;
+    end
+    else
+    begin
+        mk := k_comp( w.e, C_GRISU_ALPHA{, C_GRISU_GAMMA} );
+        diy_fp_cached_power10( mk, c_mk );
+        // Let "D = f_D * 2^e_D := w (*) c_mk"
+        if c_mk.e10 = 0 then
+            D := w
+        else
+            D := diy_fp_multiply( w, c_mk.c, FALSE );
+    end;
+
+{$ifdef grisu1_debug}
+    assert( ( C_GRISU_ALPHA <= D.e ) and ( D.e <= C_GRISU_GAMMA ) );
+{$endif grisu1_debug}
+
+    // Generate digits: integer part
+{$ifdef grisu1_debug}
+  {$ifdef VALREAL_80}
+    assert( D.e <= 32 );
+  {$else not VALREAL_80}
+    assert( D.e <= 0 );
+  {$endif VALREAL_*}
+{$endif grisu1_debug}
+
+  {$ifdef VALREAL_32}
+    n_digits_have := gen_digits_32( buf, 0, D.f shr ( - D.e ) );
+  {$endif VALREAL_32}
+  
+  {$ifdef VALREAL_64}
+    n_digits_have := gen_digits_64( buf, 0, D.f shr ( - D.e ) );
+  {$endif VALREAL_64}
+  
+  {$ifdef VALREAL_80}
+    fl := D.f;
+    fh := D.fh;
+    if ( D.e > 0 ) then
+    begin
+        templ := ( qword(fh) shl D.e ) and qword($FFFFFFFF00000000);
+        diy_util_shl( fh, fl, D.e );
+        inc( templ, fh );
+    end
+    else
+    begin
+        diy_util_shr( fh, fl, - D.e );
+        templ := fh;
+    end;
+  {$endif VALREAL_80}
+
+  {$ifdef VALREAL_128}
+    fl := D.f;
+    templ := D.fh;
+    diy_util_shr( templ, fl, - D.e );
+  {$endif VALREAL_128}
+
+  {$if defined(VALREAL_80) or defined(VALREAL_128)}
+    if ( templ = 0 ) then
+        n_digits_have := gen_digits_64( buf, 0, fl )
+    else
+    begin
+        if not u128_div_u64_to_u64( templ, fl, qword(10000000000000000000), templ, fl ) then
+{$ifdef grisu1_debug}
+            assert( FALSE ) // never overflows by design
+{$endif grisu1_debug};
+        n_digits_have := gen_digits_64( buf, 0, templ );
+        inc( n_digits_have, gen_digits_64( buf, n_digits_have, fl, n_digits_have > 0 ) );
+    end;
+  {$endif VALREAL_80 | VALREAL_128}
+
+    dot_pos := n_digits_have;
+
+    // Generate digits: fractional part
+    f := 0; // "sticky" digit
+    if ( D.e < 0 ) then
+    repeat
+        // MOD by ONE
+        one_e := D.e;
+      {$ifdef VALREAL_32}
+        one_mask := dword( 1 ) shl ( - D.e ) - 1;
+        f := D.f and one_mask;
+      {$endif VALREAL_32}
+      {$ifdef VALREAL_64}
+        one_maskl := qword( 1 ) shl ( - D.e ) - 1;
+        fl := D.f and one_maskl;
+      {$endif VALREAL_64}
+      {$if defined(VALREAL_80) or defined(VALREAL_128)}
+        make_frac_mask( one_maskh, one_maskl, D.e );
+        fl := D.f and one_maskl;
+        fh := D.fh and one_maskh;
+
+        // 128/96-bit loop
+        while ( one_e < -61 ) and ( n_digits_have < n_digits_need + 1 ) and ( fl or fh <> 0 ) do
+        begin
+            // f := f * 5;
+            templ := fl;
+            temph := fh;
+            diy_util_shl( fh, fl, 2 );
+            diy_util_add( fh, fl, temph, templ );
+            // one := one / 2
+            diy_util_shr( one_maskh, one_maskl, 1 );
+            inc( one_e );
+            // DIV by one
+            templ := fl;
+            temph := fh;
+            diy_util_shr( temph, templ, - one_e );
+            buf[ n_digits_have ] := lo(templ);
+            // MOD by one
+            fl := fl and one_maskl;
+            fh := fh and one_maskh;
+            // next
+            inc( n_digits_have );
+        end;
+        if ( n_digits_have >= n_digits_need + 1 ) then
+        begin
+            // only "sticky" digit remains
+            f := ord( fl or fh <> 0 );
+            break;
+        end;
+      {$endif VALREAL_80 | VALREAL_128}
+
+      {$ifndef VALREAL_32}
+        // 64-bit loop
+        while ( one_e < -29 ) and ( n_digits_have < n_digits_need + 1 ) and ( fl <> 0 ) do
+        begin
+            // f := f * 5;
+            inc( fl, fl shl 2 );
+            // one := one / 2
+            one_maskl := one_maskl shr 1;
+            inc( one_e );
+            // DIV by one
+            buf[ n_digits_have ] := fl shr ( - one_e );
+            // MOD by one
+            fl := fl and one_maskl;
+            // next
+            inc( n_digits_have );
+        end;
+        if ( n_digits_have >= n_digits_need + 1 ) then
+        begin
+            // only "sticky" digit remains
+            f := ord( fl <> 0 );
+            break;
+        end;
+        one_mask := lo(one_maskl);
+        f := lo(fl);
+      {$endif not VALREAL_32}
+
+        // 32-bit loop
+        while ( n_digits_have < n_digits_need + 1 ) and ( f <> 0 ) do
+        begin
+            // f := f * 5;
+            inc( f, f shl 2 );
+            // one := one / 2
+            one_mask := one_mask shr 1;
+            inc( one_e );
+            // DIV by one
+            buf[ n_digits_have ] := f shr ( - one_e );
+            // MOD by one
+            f := f and one_mask;
+            // next
+            inc( n_digits_have );
+        end;
+
+    until true;
+
+    // Append "sticky" digit if any
+    if ( f <> 0 ) and ( n_digits_have >= n_digits_need + 1 ) then
+    begin
+        // single "<>0" digit is enough
+        n_digits_have := n_digits_need + 2;
+        buf[ n_digits_need + 1 ] := 1;
+    end;
+
+    // Round to n_digits_need using "roundTiesToEven"
+    if ( n_digits_have > n_digits_need ) then
+        inc( dot_pos, round_digits( buf, n_digits_have, n_digits_need ) );
+
+    // Generate output
+    if ( frac_digits >= 0 ) then
+        if try_return_fixed( str, minus, buf, n_digits_have, dot_pos - c_mk.e10, min_width, frac_digits ) then
+            exit;
+    if ( n_digits_have > n_digits_sci ) then
+        inc( dot_pos, round_digits( buf, n_digits_have, n_digits_sci {$ifdef GRISU1_F2A_HALF_ROUNDUP}, false {$endif} ) );
+    return_exponential( str, minus, buf, n_digits_have, n_digits_sci, dot_pos - c_mk.e10 - 1, n_digits_exp, min_width );
+
+end;
+
+(****************************************************************************)
+
+{$ifndef fpc_softfpu_implementation}
+procedure str_real_iso( len, f: longint; d: ValReal; real_type: treal_type; out s: string );
+var
+    i: integer;
+begin
+    str_real( len, f, d, real_type, s );
+    for i := length( s ) downto 1 do
+        if ( s[i] ='E' ) then
+        begin
+            s[i] := 'e';
+            break; // only one "E" expected
+        end;
+end;
+{$endif not fpc_softfpu_implementation}
+
+(*==========================================================================*
+ *                                                                          *
+ *                              ASCII -> Float                              *
+ *                                                                          *
+ *==========================================================================*)
+
+function val_real( const src: shortstring; out err_pos: ValSInt ): ValReal;
+
+{$define VALREAL_PACK}
+{$i flt_pack.inc}
+
+{ NOTE: C_MAX_DIGITS_ACCEPTED should fit into unsigned integer which forms DIY_FP }
+const
+{$ifdef VALREAL_32}
+    C_MAX_DIGITS_ACCEPTED = 9;
+    C_EXP10_OVER = 100;
+    C_DIY_FP_Q   = 32;
+    C_FRAC2_BITS = 23;
+    C_EXP2_BIAS  = 127;
+{$endif VALREAL_32}
+{$ifdef VALREAL_64}
+    C_MAX_DIGITS_ACCEPTED = 19;
+    C_EXP10_OVER = 1000;
+    C_DIY_FP_Q   = 64;
+    C_FRAC2_BITS = 52;
+    C_EXP2_BIAS  = 1023;
+{$endif VALREAL_64}
+{$ifdef VALREAL_80}
+    C_MAX_DIGITS_ACCEPTED = 28;
+    C_EXP10_OVER = 10000;
+    C_DIY_FP_Q   = 96;
+    C_FRAC2_BITS = 63;
+    C_EXP2_BIAS  = 16383;
+{$endif VALREAL_80}
+{$ifdef VALREAL_128}
+    C_MAX_DIGITS_ACCEPTED = 38;
+    C_EXP10_OVER = 10000;
+    C_DIY_FP_Q   = 128;
+    C_FRAC2_BITS = 112;
+    C_EXP2_BIAS  = 16383;
+{$endif VALREAL_128}
+
+(****************************************************************************)
+    // handy const
+    C_EXP2_SPECIAL   = C_EXP2_BIAS * 2 + 1;
+    C_DIY_SHR_TO_FLT = C_DIY_FP_Q - 1 - C_FRAC2_BITS;
+    C_DIY_EXP_TO_FLT = C_DIY_FP_Q - 1 + C_EXP2_BIAS;
+    C_DIY_ROUND_BIT  = 1 shl ( C_DIY_SHR_TO_FLT - 1 );
+    C_DIY_ROUND_MASK = ( C_DIY_ROUND_BIT shl 2 ) - 1;
+{$ifdef VALREAL_128}
+    C_FLT_INT_BITh   = qword(1) shl ( C_FRAC2_BITS - 64 );
+    C_FLT_FRAC_MASKh = C_FLT_INT_BITh - 1;
+{$else not VALREAL_128}
+  {$ifdef VALREAL_32}
+    C_FLT_INT_BIT    = dword(1) shl C_FRAC2_BITS;
+    C_FLT_FRAC_MASK  = C_FLT_INT_BIT - 1;
+  {$else VALREAL_64 | VALREAL_80}
+    C_FLT_INT_BIT    = qword(1) shl C_FRAC2_BITS;
+    C_FLT_FRAC_MASK  = qword(C_FLT_INT_BIT) - 1;
+  {$endif VALREAL_*}
+{$endif VALREAL_*}
+
+    // specials
+{$ifdef VALREAL_32}
+    C_FLT_MANT_INF  = dword($00000000);
+  {$ifndef GRISU1_A2F_NO_SNAN}
+    C_FLT_MANT_SNAN = dword($00200000);
+  {$endif GRISU1_A2F_NO_SNAN}
+    C_FLT_MANT_QNAN = dword($00400000);
+{$endif VALREAL_32}
+{$ifdef VALREAL_64}
+    C_FLT_MANT_INF  = qword($0000000000000000);
+  {$ifndef GRISU1_A2F_NO_SNAN}
+    C_FLT_MANT_SNAN = qword($0004000000000000);
+  {$endif GRISU1_A2F_NO_SNAN}
+    C_FLT_MANT_QNAN = qword($0008000000000000);
+{$endif VALREAL_64}
+{$ifdef VALREAL_80}
+    C_FLT_MANT_INF  = qword($8000000000000000);
+  {$ifndef GRISU1_A2F_NO_SNAN}
+    C_FLT_MANT_SNAN = qword($A000000000000000);
+  {$endif GRISU1_A2F_NO_SNAN}
+    C_FLT_MANT_QNAN = qword($C000000000000000);
+{$endif VALREAL_80}
+{$ifdef VALREAL_128}
+    C_FLT_MANT_INFh  = qword($0000000000000000);
+    C_FLT_MANT_INF   = qword($0000000000000000);
+  {$ifndef GRISU1_A2F_NO_SNAN}
+    C_FLT_MANT_SNANh = qword($0000400000000000);
+    C_FLT_MANT_SNAN  = qword($0000000000000000);
+  {$endif GRISU1_A2F_NO_SNAN}
+    C_FLT_MANT_QNANh = qword($0000800000000000);
+    C_FLT_MANT_QNAN  = qword($0000000000000000);
+{$endif VALREAL_128}
+
+(*-------------------------------------------------------
+ | factor_10_inexact [local]
+ |
+ | Calculates an arbitrary normalized power of 10 required for final scaling.
+ | The result of this calculation may be off by several ulp's from exact.
+ |
+ | Returns an overflow/underflow status:
+ |    "<0": underflow
+ |    "=0": ok
+ |    ">0": overflow
+ |
+ *-------------------------------------------------------*)
+function factor_10_inexact( const exp10: integer; out C: TDIY_FP_Power_of_10 ): integer;
+const
+{$ifdef VALREAL_32}
+    factor: array [ 0 .. 7 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( f: $80000000; e: -31); e10:   0 ),
+        ( c: ( f: $CCCCCCCD; e: -35); e10:  -1 ),
+        ( c: ( f: $A3D70A3D; e: -38); e10:  -2 ),
+        ( c: ( f: $83126E98; e: -41); e10:  -3 ),
+        ( c: ( f: $D1B71759; e: -45); e10:  -4 ),
+        ( c: ( f: $A7C5AC47; e: -48); e10:  -5 ),
+        ( c: ( f: $8637BD06; e: -51); e10:  -6 ),
+        ( c: ( f: $D6BF94D6; e: -55); e10:  -7 )
+    );
+{$endif VALREAL_32}
+{$ifdef VALREAL_64}
+    factor: array [ 0 .. 17 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( f: qword($8000000000000000); e:  -63); e10:   0 ),
+        ( c: ( f: qword($CCCCCCCCCCCCCCCD); e:  -67); e10:  -1 ),
+        ( c: ( f: qword($A3D70A3D70A3D70A); e:  -70); e10:  -2 ),
+        ( c: ( f: qword($83126E978D4FDF3B); e:  -73); e10:  -3 ),
+        ( c: ( f: qword($D1B71758E219652C); e:  -77); e10:  -4 ),
+        ( c: ( f: qword($A7C5AC471B478423); e:  -80); e10:  -5 ),
+        ( c: ( f: qword($8637BD05AF6C69B6); e:  -83); e10:  -6 ),
+        ( c: ( f: qword($D6BF94D5E57A42BC); e:  -87); e10:  -7 ),
+        ( c: ( f: qword($ABCC77118461CEFD); e:  -90); e10:  -8 ),
+        ( c: ( f: qword($89705F4136B4A597); e:  -93); e10:  -9 ),
+        ( c: ( f: qword($DBE6FECEBDEDD5BF); e:  -97); e10: -10 ),
+        ( c: ( f: qword($AFEBFF0BCB24AAFF); e: -100); e10: -11 ),
+        ( c: ( f: qword($8CBCCC096F5088CC); e: -103); e10: -12 ),
+        ( c: ( f: qword($E12E13424BB40E13); e: -107); e10: -13 ),
+        ( c: ( f: qword($B424DC35095CD80F); e: -110); e10: -14 ),
+        ( c: ( f: qword($901D7CF73AB0ACD9); e: -113); e10: -15 ),
+        ( c: ( f: qword($E69594BEC44DE15B); e: -117); e10: -16 ),
+        ( c: ( f: qword($B877AA3236A4B449); e: -120); e10: -17 )
+    );
+{$endif VALREAL_64}
+{$ifdef VALREAL_80}
+    factor: array [ 0 .. 36 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( f: qword($0000000000000000); fh: dword($80000000); e:  -95 ); e10:   0 ),
+        ( c: ( f: qword($CCCCCCCCCCCCCCCD); fh: dword($CCCCCCCC); e:  -99 ); e10:  -1 ),
+        ( c: ( f: qword($70A3D70A3D70A3D7); fh: dword($A3D70A3D); e: -102 ); e10:  -2 ),
+        ( c: ( f: qword($8D4FDF3B645A1CAC); fh: dword($83126E97); e: -105 ); e10:  -3 ),
+        ( c: ( f: qword($E219652BD3C36113); fh: dword($D1B71758); e: -109 ); e10:  -4 ),
+        ( c: ( f: qword($1B4784230FCF80DC); fh: dword($A7C5AC47); e: -112 ); e10:  -5 ),
+        ( c: ( f: qword($AF6C69B5A63F9A4A); fh: dword($8637BD05); e: -115 ); e10:  -6 ),
+        ( c: ( f: qword($E57A42BC3D329076); fh: dword($D6BF94D5); e: -119 ); e10:  -7 ),
+        ( c: ( f: qword($8461CEFCFDC20D2B); fh: dword($ABCC7711); e: -122 ); e10:  -8 ),
+        ( c: ( f: qword($36B4A59731680A89); fh: dword($89705F41); e: -125 ); e10:  -9 ),
+        ( c: ( f: qword($BDEDD5BEB573440E); fh: dword($DBE6FECE); e: -129 ); e10: -10 ),
+        ( c: ( f: qword($CB24AAFEF78F69A5); fh: dword($AFEBFF0B); e: -132 ); e10: -11 ),
+        ( c: ( f: qword($6F5088CBF93F87B7); fh: dword($8CBCCC09); e: -135 ); e10: -12 ),
+        ( c: ( f: qword($4BB40E132865A5F2); fh: dword($E12E1342); e: -139 ); e10: -13 ),
+        ( c: ( f: qword($095CD80F538484C2); fh: dword($B424DC35); e: -142 ); e10: -14 ),
+        ( c: ( f: qword($3AB0ACD90F9D3701); fh: dword($901D7CF7); e: -145 ); e10: -15 ),
+        ( c: ( f: qword($C44DE15B4C2EBE68); fh: dword($E69594BE); e: -149 ); e10: -16 ),
+        ( c: ( f: qword($36A4B44909BEFEBA); fh: dword($B877AA32); e: -152 ); e10: -17 ),
+        ( c: ( f: qword($921D5D073AFF322E); fh: dword($9392EE8E); e: -155 ); e10: -18 ),
+        ( c: ( f: qword($B69561A52B31E9E4); fh: dword($EC1E4A7D); e: -159 ); e10: -19 ),
+        ( c: ( f: qword($92111AEA88F4BB1D); fh: dword($BCE50864); e: -162 ); e10: -20 ),
+        ( c: ( f: qword($74DA7BEED3F6FC17); fh: dword($971DA050); e: -165 ); e10: -21 ),
+        ( c: ( f: qword($BAF72CB15324C68B); fh: dword($F1C90080); e: -169 ); e10: -22 ),
+        ( c: ( f: qword($95928A2775B7053C); fh: dword($C16D9A00); e: -172 ); e10: -23 ),
+        ( c: ( f: qword($44753B52C4926A96); fh: dword($9ABE14CD); e: -175 ); e10: -24 ),
+        ( c: ( f: qword($D3EEC5513A83DDBE); fh: dword($F79687AE); e: -179 ); e10: -25 ),
+        ( c: ( f: qword($76589DDA95364AFE); fh: dword($C6120625); e: -182 ); e10: -26 ),
+        ( c: ( f: qword($91E07E48775EA265); fh: dword($9E74D1B7); e: -185 ); e10: -27 ),
+        ( c: ( f: qword($8300CA0D8BCA9D6E); fh: dword($FD87B5F2); e: -189 ); e10: -28 ),
+        ( c: ( f: qword($359A3B3E096EE458); fh: dword($CAD2F7F5); e: -192 ); e10: -29 ),
+        ( c: ( f: qword($5E14FC31A125837A); fh: dword($A2425FF7); e: -195 ); e10: -30 ),
+        ( c: ( f: qword($4B43FCF480EACF95); fh: dword($81CEB32C); e: -198 ); e10: -31 ),
+        ( c: ( f: qword($453994BA67DE18EE); fh: dword($CFB11EAD); e: -202 ); e10: -32 ),
+        ( c: ( f: qword($D0FADD61ECB1AD8B); fh: dword($A6274BBD); e: -205 ); e10: -33 ),
+        ( c: ( f: qword($DA624AB4BD5AF13C); fh: dword($84EC3C97); e: -208 ); e10: -34 ),
+        ( c: ( f: qword($C3D07787955E4EC6); fh: dword($D4AD2DBF); e: -212 ); e10: -35 ),
+        ( c: ( f: qword($697392D2DDE50BD2); fh: dword($AA242499); e: -215 ); e10: -36 )
+    );
+{$endif VALREAL_80}
+{$ifdef VALREAL_128}
+    factor: array [ 0 .. 36 ] of TDIY_FP_Power_of_10 = (
+        ( c: ( fh: qword($8000000000000000); f: qword($0000000000000000); e: -127 ); e10:   0 ),
+        ( c: ( fh: qword($CCCCCCCCCCCCCCCC); f: qword($CCCCCCCCCCCCCCCD); e: -131 ); e10:  -1 ),
+        ( c: ( fh: qword($A3D70A3D70A3D70A); f: qword($3D70A3D70A3D70A4); e: -134 ); e10:  -2 ),
+        ( c: ( fh: qword($83126E978D4FDF3B); f: qword($645A1CAC083126E9); e: -137 ); e10:  -3 ),
+        ( c: ( fh: qword($D1B71758E219652B); f: qword($D3C36113404EA4A9); e: -141 ); e10:  -4 ),
+        ( c: ( fh: qword($A7C5AC471B478423); f: qword($0FCF80DC33721D54); e: -144 ); e10:  -5 ),
+        ( c: ( fh: qword($8637BD05AF6C69B5); f: qword($A63F9A49C2C1B110); e: -147 ); e10:  -6 ),
+        ( c: ( fh: qword($D6BF94D5E57A42BC); f: qword($3D32907604691B4D); e: -151 ); e10:  -7 ),
+        ( c: ( fh: qword($ABCC77118461CEFC); f: qword($FDC20D2B36BA7C3D); e: -154 ); e10:  -8 ),
+        ( c: ( fh: qword($89705F4136B4A597); f: qword($31680A88F8953031); e: -157 ); e10:  -9 ),
+        ( c: ( fh: qword($DBE6FECEBDEDD5BE); f: qword($B573440E5A884D1B); e: -161 ); e10: -10 ),
+        ( c: ( fh: qword($AFEBFF0BCB24AAFE); f: qword($F78F69A51539D749); e: -164 ); e10: -11 ),
+        ( c: ( fh: qword($8CBCCC096F5088CB); f: qword($F93F87B7442E45D4); e: -167 ); e10: -12 ),
+        ( c: ( fh: qword($E12E13424BB40E13); f: qword($2865A5F206B06FBA); e: -171 ); e10: -13 ),
+        ( c: ( fh: qword($B424DC35095CD80F); f: qword($538484C19EF38C94); e: -174 ); e10: -14 ),
+        ( c: ( fh: qword($901D7CF73AB0ACD9); f: qword($0F9D37014BF60A10); e: -177 ); e10: -15 ),
+        ( c: ( fh: qword($E69594BEC44DE15B); f: qword($4C2EBE687989A9B4); e: -181 ); e10: -16 ),
+        ( c: ( fh: qword($B877AA3236A4B449); f: qword($09BEFEB9FAD487C3); e: -184 ); e10: -17 ),
+        ( c: ( fh: qword($9392EE8E921D5D07); f: qword($3AFF322E62439FCF); e: -187 ); e10: -18 ),
+        ( c: ( fh: qword($EC1E4A7DB69561A5); f: qword($2B31E9E3D06C32E5); e: -191 ); e10: -19 ),
+        ( c: ( fh: qword($BCE5086492111AEA); f: qword($88F4BB1CA6BCF584); e: -194 ); e10: -20 ),
+        ( c: ( fh: qword($971DA05074DA7BEE); f: qword($D3F6FC16EBCA5E03); e: -197 ); e10: -21 ),
+        ( c: ( fh: qword($F1C90080BAF72CB1); f: qword($5324C68B12DD6338); e: -201 ); e10: -22 ),
+        ( c: ( fh: qword($C16D9A0095928A27); f: qword($75B7053C0F178294); e: -204 ); e10: -23 ),
+        ( c: ( fh: qword($9ABE14CD44753B52); f: qword($C4926A9672793543); e: -207 ); e10: -24 ),
+        ( c: ( fh: qword($F79687AED3EEC551); f: qword($3A83DDBD83F52205); e: -211 ); e10: -25 ),
+        ( c: ( fh: qword($C612062576589DDA); f: qword($95364AFE032A819D); e: -214 ); e10: -26 ),
+        ( c: ( fh: qword($9E74D1B791E07E48); f: qword($775EA264CF55347E); e: -217 ); e10: -27 ),
+        ( c: ( fh: qword($FD87B5F28300CA0D); f: qword($8BCA9D6E188853FC); e: -221 ); e10: -28 ),
+        ( c: ( fh: qword($CAD2F7F5359A3B3E); f: qword($096EE45813A04330); e: -224 ); e10: -29 ),
+        ( c: ( fh: qword($A2425FF75E14FC31); f: qword($A1258379A94D028D); e: -227 ); e10: -30 ),
+        ( c: ( fh: qword($81CEB32C4B43FCF4); f: qword($80EACF948770CED7); e: -230 ); e10: -31 ),
+        ( c: ( fh: qword($CFB11EAD453994BA); f: qword($67DE18EDA5814AF2); e: -234 ); e10: -32 ),
+        ( c: ( fh: qword($A6274BBDD0FADD61); f: qword($ECB1AD8AEACDD58E); e: -237 ); e10: -33 ),
+        ( c: ( fh: qword($84EC3C97DA624AB4); f: qword($BD5AF13BEF0B113F); e: -240 ); e10: -34 ),
+        ( c: ( fh: qword($D4AD2DBFC3D07787); f: qword($955E4EC64B44E864); e: -244 ); e10: -35 ),
+        ( c: ( fh: qword($AA242499697392D2); f: qword($DDE50BD1D5D0B9EA); e: -247 ); e10: -36 )
+    );
+{$endif VALREAL_128}
+var
+    i: integer;
+    a, b: TDIY_FP_Power_of_10;
+begin
+    diy_fp_cached_power10( exp10, a );
+    i := a.e10 - exp10;
+    if ( i < 0 ) then
+    begin
+        factor_10_inexact := 1; // overflow
+        exit;
+    end;
+    if ( i > high( factor ) ) then
+    begin
+        factor_10_inexact := -1; // underflow
+        exit;
+    end;
+    b := factor[i];
+{$ifdef grisu1_debug}
+    assert( exp10 =  a.e10 + b.e10 );
+{$endif grisu1_debug}
+    if ( b.e10 = 0 ) then
+       C := a
+    else
+    if ( a.e10 = 0 ) then
+       C := b
+    else
+    begin
+        C.c := diy_fp_multiply( a.c, b.c, TRUE );
+        c.e10 := exp10;
+    end;
+    factor_10_inexact := 0; // no error
+end;
+
+(*-------------------------------------------------------
+ | add_digit [local]
+ |
+ | This helper routine performs next digit addition:
+ |     X := X * 10 + digit
+ |
+ *-------------------------------------------------------*)
+{$ifdef VALREAL_80}
+procedure add_digit( var h: dword; var l: qword; digit: byte ); {$ifdef grisu1_inline}inline;{$endif}
+var
+    temp1, temp2: qword;
+begin
+    //
+    temp1 := lo(l);
+    inc( temp1, ( temp1 shl 3 ) + temp1 + digit );
+    //
+    temp2 := h;
+    temp2 := ( temp2 shl 32 ) + hi(l);
+    inc( temp2, ( temp2 shl 3 ) + temp2 + hi(temp1) );
+    //
+    h := hi(temp2);
+    l := ( temp2 shl 32 ) + lo(temp1);
+    //
+end;
+{$endif VALREAL_80}
+{$ifdef VALREAL_128}
+procedure add_digit( var h, l: qword; digit: byte ); {$ifdef grisu1_inline}inline;{$endif}
+var
+    templ, temph, temp1, temp2: qword;
+begin
+    templ := l;
+    temph := h;
+    diy_util_shl( temph, templ, 3 );
+    //
+    temp1 := lo(l);
+    inc( temp1, lo(templ) + temp1 + digit );
+    //
+    temp2 := hi(l);
+    inc( temp2, hi(templ) + temp2 + hi(temp1) );
+    //
+    inc( h, temph + h + hi(temp2) );
+    l := ( temp2 shl 32 ) + lo(temp1);
+    //
+end;
+{$endif VALREAL_128}
+
+(*-------------------------------------------------------
+ | count_leading_zero [local] <<<duplicate>>>
+ |
+ | Counts number of 0-bits at most significant bit position.
+ |
+ *-------------------------------------------------------*)
+{$if defined(VALREAL_32) or defined(VALREAL_80)}
+function count_leading_zero( const X: dword ): integer; {$ifdef grisu1_inline}inline;{$endif}
+begin
+    count_leading_zero := 31 - BSRdword( X );
+end;
+{$endif VALREAL_32 | VALREAL_80}
+{$ifndef VALREAL_32}
+function count_leading_zero( const X: qword ): integer; {$ifdef grisu1_inline}inline;{$endif}
+begin
+    count_leading_zero := 63 - BSRqword( X );
+end;
+{$endif not VALREAL_32}
+
+(*-------------------------------------------------------
+ | match_special [local]
+ |
+ | Routine compares source string tail with the string representing
+ | one of special values: Inf | QNaN | SNaN
+ |
+ *-------------------------------------------------------*)
+function match_special( src_pos: integer; const src, spec: shortstring ): boolean;
+var
+    sl, xl: integer;
+begin
+    match_special := false;
+    xl := length( src );
+    sl := length( spec );
+    if ( sl <> xl - src_pos + 1 ) then
+        exit;
+{$ifdef grisu1_debug}
+    assert( sl > 0 );
+{$endif grisu1_debug}
+    repeat
+        if ( UpCase( src[xl] ) <> UpCase( spec[sl] ) ) then
+            exit;
+        dec( xl );
+        dec( sl );
+    until sl <= 0;
+    match_special := true;
+end;
+
+(****************************************************************************)
+var
+    a: char;
+    mantissa, bit_round, bit_round_mask: {$ifdef VALREAL_32} dword {$else} qword {$endif};
+{$ifdef VALREAL_80}
+    mantissa_h: dword;
+{$endif}
+{$ifdef VALREAL_128}
+    mantissa_h, bit_round_h, bit_round_mask_h: qword;
+{$endif}
+    dig_num, exp10, overflow, n, src_pos, src_len: integer;
+    exp_read, exp_temp: longint;
+    b, dig_round, dig_sticky: byte;
+    minus, exp_minus, special: boolean;
+    C: TDIY_FP_Power_of_10;
+    D: TDIY_FP;
+
+begin
+
+    err_pos := 1;
+    src_pos := 1;
+    src_len := length(src);
+
+    // Pre-initialize result
+{$ifdef GRISU1_A2F_ERROR_RET0}
+    pack_float( val_real, false, 0, {$ifdef VALREAL_128} 0, {$endif} 0 );
+{$else}
+  {-ifdef GRISU1_A2F_NO_SNAN}
+    // "real indefinite"
+    pack_float( val_real, true, C_EXP2_SPECIAL, {$ifdef VALREAL_128} C_FLT_MANT_QNANh, {$endif} C_FLT_MANT_QNAN );
+(*{-else}
+    // SNaN is preferable for catching uninitialized variables access, but may cause troubles with implicit float type conversions
+    pack_float( val_real, false, C_EXP2_SPECIAL, {$ifdef VALREAL_128} C_FLT_MANT_SNANh, {$endif} C_FLT_MANT_SNAN );
+  {-endif}*)
+{$endif}
+
+    // search for a sign skipping leading spaces
+    minus := false;
+    while ( src_pos <= src_len ) do
+    begin
+        a := src[src_pos];
+        case a of
+        '+':
+            begin
+                inc( src_pos );
+                break;
+            end;
+        '-':
+            begin
+                minus := true;
+                inc( src_pos );
+                break;
+            end;
+        else
+            if a <> ' ' then
+                break;
+        end;
+        inc( src_pos );
+    end;
+    if ( src_pos > src_len ) then
+    begin
+        // syntax: nothing to evaluate
+        err_pos := src_pos;
+        exit;
+    end;
+
+    // handle specials
+    case src[src_pos] of
+       '0' .. '9', '.', 'E', 'e': special := false;
+    else
+        special := true;
+    end;
+    if special then
+    begin
+        mantissa := C_FLT_MANT_INF;
+{$ifdef VALREAL_128}
+        mantissa_h := C_FLT_MANT_INFh;
+{$endif}
+        if not match_special( src_pos, src, C_STR_INF ) then
+        begin
+          {$ifndef GRISU1_A2F_NO_SNAN}
+            if match_special( src_pos, src, C_STR_SNAN ) then
+            begin
+                mantissa := C_FLT_MANT_SNAN;
+{$ifdef VALREAL_128}
+                mantissa_h := C_FLT_MANT_SNANh;
+{$endif}
+            end
+            else
+          {$endif GRISU1_A2F_NO_SNAN}
+            if match_special( src_pos, src, C_STR_QNAN ) then
+            begin
+              {$ifdef GRISU1_A2F_QNAN_REAL_INDEFINITE}
+                minus := TRUE;
+              {$endif}
+                mantissa := C_FLT_MANT_QNAN;
+{$ifdef VALREAL_128}
+                mantissa_h := C_FLT_MANT_QNANh;
+{$endif}
+            end
+            else
+                special := false;
+        end;
+        if special then
+        begin
+            pack_float( val_real, minus, C_EXP2_SPECIAL, {$ifdef VALREAL_128} mantissa_h, {$endif} mantissa );
+            src_pos := 0;
+        end;
+        err_pos := src_pos;
+        exit;
+    end;
+
+    // consume mantissa digits skipping leading zeroes
+    // empty mantissa ("0.E#", ".0E#", ".E#", "E#") is allowed at least in D5
+    mantissa := 0;
+{$if defined(VALREAL_80) or defined(VALREAL_128)}
+    mantissa_h := 0;
+{$endif VALREAL_80 | VALREAL_128}
+    dig_num := 0;
+    exp10 := 0;
+    dig_round := 0;
+    dig_sticky := 0;
+
+    // leading zero loop
+    while ( src_pos <= src_len ) and ( src[src_pos] = '0' ) do
+        inc( src_pos );
+
+    // integer part loop
+    while ( src_pos <= src_len ) do
+    begin
+        a := src[src_pos];
+        if ( a < '0' ) or ( a > '9' ) then
+            break;
+        b := ord(a) - ord('0');
+        if ( dig_num < C_MAX_DIGITS_ACCEPTED ) then
+            // normal digit
+{$if defined(VALREAL_32) or defined(VALREAL_64)}
+            inc( mantissa, ( mantissa shl 3 ) + mantissa + b )
+{$else VALREAL_80 | VALREAL_128}
+            add_digit( mantissa_h, mantissa, b )
+{$endif VALREAL_*}
+        else
+        begin
+            // over-required digits: use them for rounding later
+            if ( dig_num = C_MAX_DIGITS_ACCEPTED ) then
+                dig_round := b // main digit for rounding
+            else
+                dig_sticky := dig_sticky or b; // just "<>0" to judge "round to even" case later
+            inc( exp10 ); // move [yet virtual] dot to the right
+        end;
+        inc( dig_num );
+        inc( src_pos );
+    end;
+
+    // fraction part
+    if ( src_pos <= src_len ) and ( src[src_pos] = '.' ) then
+    begin
+        // skip dot
+        inc( src_pos );
+        // leading zero loop, if integer part was 0
+        if dig_num = 0 then
+            while ( src_pos <= src_len ) and ( src[src_pos] = '0' ) do
+            begin
+                dec( exp10 ); // move the dot to the left
+                inc( src_pos );
+            end;
+        // fraction part loop
+        while ( src_pos <= src_len ) do
+        begin
+            a := src[src_pos];
+            if ( a < '0' ) or ( a > '9' ) then
+                break;
+            b := ord(a) - ord('0');
+            if ( dig_num < C_MAX_DIGITS_ACCEPTED ) then
+            begin
+                // normal digit
+{$if defined(VALREAL_32) or defined(VALREAL_64)}
+                inc( mantissa, ( mantissa shl 3 ) + mantissa + b );
+{$else VALREAL_80 | VALREAL_128}
+                add_digit( mantissa_h, mantissa, b );
+{$endif VALREAL_*}
+                dec( exp10 ); // move the dot to the left
+            end
+            else
+            begin
+                // over-required digits: use them for rounding later
+                if ( dig_num = C_MAX_DIGITS_ACCEPTED ) then
+                    dig_round := b // main digit for rounding
+                else
+                    dig_sticky := dig_sticky or b; // just "<>0" to judge "round to even" case later
+            end;
+            inc( dig_num );
+            inc( src_pos );
+        end;
+    end;
+
+    // round digits
+{$ifndef GRISU1_A2F_HALF_ROUNDUP}
+    if ( dig_round = 5 ) and ( dig_sticky = 0 ) and ( mantissa and 1 = 0 ) then
+        // need to "round to even", and already even..
+        dec( dig_round ); // ..so force no round-up
+{$endif not GRISU1_A2F_HALF_ROUNDUP}
+    if ( dig_round >= 5 ) then
+    begin
+        // round-up
+        inc( mantissa );
+{$if defined(VALREAL_80) or defined(VALREAL_128)}
+        if ( mantissa = 0 ) then
+            inc( mantissa_h );
+{$endif VALREAL_*}
+    end;
+
+    // consume exponent digits
+    exp_read := 0;
+    if ( src_pos <= src_len ) then
+    begin
+        exp_minus := false;
+        case src[src_pos] of
+        'e', 'E':;
+        else
+            // syntax: "E" expected
+            err_pos := src_pos;
+            exit;
+        end;
+        inc( src_pos );
+        if ( src_pos > src_len ) then
+        begin
+            // syntax: empty exponent
+            err_pos := src_pos;
+            exit;
+        end;
+        case src[src_pos] of
+        '+':
+            inc( src_pos );
+        '-':
+            begin
+                exp_minus := true;
+                inc( src_pos );
+            end;
+        end;
+        while ( src_pos <= src_len ) do
+        begin
+            a := src[src_pos];
+            if ( a < '0' ) or ( a > '9' ) then
+            begin
+                // syntax: bad digit
+                err_pos := src_pos;
+                exit;
+            end;
+            if ( exp_read < 100000 ) then
+                inc( exp_read, ( exp_read shl 3 ) + exp_read + ord(a) - ord('0') );
+         // else just syntax check
+            inc( src_pos );
+        end;
+        if exp_minus then
+            exp_read := - exp_read;
+    end;
+    exp_temp := exp_read + exp10;
+    if ( exp_read >= 100000 ) or ( exp_temp >= C_EXP10_OVER ) then
+        exp10 := C_EXP10_OVER
+    else
+    if ( exp_read <= - 100000 ) or ( exp_temp <= - C_EXP10_OVER ) then
+        exp10 := - C_EXP10_OVER
+    else
+        exp10 := exp_temp;
+
+    // nothing should remain in the "src" here
+    if ( src_pos <= src_len ) then
+    begin
+        err_pos := src_pos;
+        exit;
+    end;
+
+    // zero [or negative exponent overflow]
+    if ( mantissa {$if defined(VALREAL_80) or defined(VALREAL_128)} or mantissa_h {$endif} = 0 )
+    or ( exp10 <= - C_EXP10_OVER ) then
+    begin
+        pack_float( val_real, minus, 0, {$ifdef VALREAL_128} 0, {$endif} 0 ); // +0|-0
+        err_pos := 0;
+        exit;
+    end;
+
+    if ( exp10 >= C_EXP10_OVER ) then
+        // exponent overflowed -> return Inf
+        overflow := 1
+    else
+    begin
+        // make DIY_FP
+{$if defined(VALREAL_32) or defined(VALREAL_64)}
+        n := count_leading_zero( mantissa );
+        D.f := mantissa shl n;
+{$else VALREAL_80 | VALREAL_128}
+        if ( mantissa_h = 0 ) then
+            n := count_leading_zero( mantissa ) + sizeof( mantissa_h ) * 8
+        else
+            n := count_leading_zero( mantissa_h );
+        D.f := mantissa;
+        D.fh := mantissa_h;
+        diy_util_shl( D.fh, D.f, n );
+{$endif VALREAL_*}
+        D.e := - n;
+        // get factor
+        overflow := factor_10_inexact( exp10, C ); // <>0 -> over/underflow
+    end;
+
+    if ( overflow = 0 ) then
+    begin
+        // multiply
+        if ( C.e10 <> 0 ) then
+            // C <> 1
+            D := diy_fp_multiply( D, C.c, TRUE );
+        // calculate round increment
+        if ( D.f and C_DIY_ROUND_MASK = C_DIY_ROUND_BIT ) then
+            // round to even and already even
+            b := 0
+        else
+            b := ord( D.f and C_DIY_ROUND_BIT <> 0 );
+        // shift and round
+{$if defined(VALREAL_32) or defined(VALREAL_64)}
+        D.f := ( D.f shr C_DIY_SHR_TO_FLT ) + b;
+        // handle round overflow
+        if ( D.f and ( C_FLT_INT_BIT shl 1 ) <> 0 ) then
+        begin
+            D.f := D.f shr 1;
+            inc( D.e );
+        end;
+{$else VALREAL_80 or VALREAL_128}
+        diy_util_shr( D.fh, D.f, C_DIY_SHR_TO_FLT );
+        if ( b <> 0 ) then
+            diy_util_add( D.fh, D.f, 0, b );
+        // handle round overflow
+        if ( D.fh {$ifdef VALREAL_128} and ( C_FLT_INT_BITh shl 1 ) {$endif} <> 0 ) then
+        begin
+            diy_util_shr( D.fh, D.f, 1 );
+            inc( D.e );
+        end;
+    {$if defined(grisu1_debug) and defined(VALREAL_80)}
+        assert( D.fh = 0 );
+    {$endif grisu1_debug}
+{$endif VALREAL_*}
+        // calculate exponent
+        D.e := D.e + C_DIY_EXP_TO_FLT;
+        if ( D.e >= C_EXP2_SPECIAL ) then
+          ///////////////////
+          //
+          // overflow
+          //
+          ///////////////////
+            overflow := 1
+        else
+        if ( D.e < - C_FRAC2_BITS ) then
+          ///////////////////
+          //
+          // underflow
+          //
+          ///////////////////
+            overflow := -1
+        else
+        if ( D.e <= 0 ) then
+        begin
+          ///////////////////
+          //
+          // denormal (and also an extreme case of "D.e=-C_FRAC2_BITS", where
+          // rounding may produce either the lowest denormal or underflow)
+          //
+          ///////////////////
+            n := 1 - D.e; // SHR amount
+            // round bit
+{$ifdef VALREAL_32}
+            bit_round := dword(1) shl ( n - 1 );
+{$endif VALREAL_32}
+{$if defined(VALREAL_64) or defined(VALREAL_80)}
+            bit_round := qword(1) shl ( n - 1 );
+{$endif VALREAL_64 | VALREAL_80}
+{$ifdef VALREAL_128}
+            bit_round := 1;
+            bit_round_h := 0;
+            diy_util_shl( bit_round_h, bit_round, n - 1 );
+            // mask for ulp and all least bits including the round one
+            bit_round_mask := bit_round;
+            bit_round_mask_h := bit_round_h;
+            diy_util_shl( bit_round_mask_h, bit_round_mask, 2 );
+            if ( bit_round_mask = 0 ) then
+                dec( bit_round_mask_h );
+            dec( bit_round_mask );
+{$else not VALREAL_128}
+            // mask for ulp and all least bits including the round one
+            bit_round_mask := ( bit_round shl 2 ) - 1;
+            // Note[floatx80]: works correctly despite the overflow is ignored in extreme case "D.e=-C_FRAC2_BITS"
+{$endif VALREAL_*}
+            // round increment
+            if ( D.f and bit_round_mask = bit_round ) {$ifdef VALREAL_128} and ( D.fh and bit_round_mask_h = bit_round_h ) {$endif} then
+                // round to even and already even -> no round-up
+                b := 0
+            else
+                b := ord( ( D.f and bit_round ) {$ifdef VALREAL_128} or ( D.fh and bit_round_h ) {$endif} <> 0 );
+            // shift and round
+            if ( D.e = - C_FRAC2_BITS ) then
+            begin
+                // extreme case: rounding may result in either lowest denormal or zero
+              {$ifdef VALREAL_128}
+                D.fh := 0;
+              {$endif VALREAL_128}
+                D.f := b;
+                if ( b = 0 ) then
+                    overflow := -1; // underflow
+            end
+            else
+          {$ifdef VALREAL_128}
+            begin
+                diy_util_shr( D.fh, D.f, n );
+                if ( b <> 0 ) then
+                    diy_util_add( D.fh, D.f, 0, b );
+            end;
+          {$else not VALREAL_128}
+                D.f := ( D.f shr n ) + b;
+          {$endif VALREAL_*}
+            D.e := 0;
+{$ifdef grisu1_debug}
+          {$ifdef VALREAL_128}
+            assert( ( D.f or D.fh <> 0 ) or ( overflow = -1 ) );
+            assert( D.fh and not C_FLT_FRAC_MASKh = 0 );
+          {$else not VALREAL_128}
+            assert( ( D.f <> 0 ) or ( overflow = -1 ) );
+            assert( D.f and not C_FLT_FRAC_MASK = 0 );
+          {$endif VALREAL_*}
+{$endif grisu1_debug}
+        end
+        else
+        begin
+          ///////////////////
+          //
+          // normal: 0 < D.e < C_EXP2_SPECIAL
+          //
+          ///////////////////
+{$ifdef grisu1_debug}
+          {$ifdef VALREAL_32}
+            assert( D.f and not C_FLT_FRAC_MASK = C_FLT_INT_BIT );
+          {$endif VALREAL_32}
+          {$if defined(VALREAL_64) or defined(VALREAL_80)}
+            assert( D.f and not qword(C_FLT_FRAC_MASK) = qword(C_FLT_INT_BIT) );
+          {$endif VALREAL_64 | VALREAL_80}
+          {$ifdef VALREAL_128}
+            assert( D.fh and not C_FLT_FRAC_MASKh = C_FLT_INT_BITh );
+          {$endif VALREAL_128}
+{$endif grisu1_debug}
+        {$ifndef VALREAL_80}
+           // clear the implicit integer bit
+          {$ifdef VALREAL_128}
+            D.fh := D.fh and C_FLT_FRAC_MASKh;
+          {$else not VALREAL_128}
+            D.f := D.f and C_FLT_FRAC_MASK;
+          {$endif VALREAL_*}
+        {$endif not VALREAL_80}
+        end;
+    end;
+
+    // final result
+    if ( overflow < 0 ) then
+    begin
+        // underflow [+0|-0]
+        pack_float( val_real, minus, 0, {$ifdef VALREAL_128} 0, {$endif} 0 );
+    end
+    else
+    if ( overflow > 0 ) then
+    begin
+        // overflow [+Inf|-Inf]
+        pack_float( val_real, minus, C_EXP2_SPECIAL, {$ifdef VALREAL_128} C_FLT_MANT_INFh, {$endif} C_FLT_MANT_INF );
+    end
+    else
+    begin
+        // no error
+        pack_float( val_real, minus, D.e, {$ifdef VALREAL_128} D.fh, {$endif} D.f );
+    end;
+    err_pos := 0;
+
+end;

+ 383 - 0
rtl/inc/flt_pack.inc

@@ -0,0 +1,383 @@
+{
+    This file isolates platform-specific routines which perform packing and
+    unpacking of ValReal FP values during float <-> ASCII conversions.
+    These routines, mostly, were gathered from various places of FPC RTL.
+
+ ****************************************************************************
+}
+{
+    Note about inlining: since unpack_float is used only once in str_real,
+    it can be safely inlined; however pack_float is used several times in
+    val_real, so its inlining does not seem practical, except of the case
+    when this procedure simply calls the SoftFPU implementation.
+}
+// ---------------------------------------------------------------------
+//
+// single; format [MSB]: 1 sign bit, 8 bit exponent, 23 bit mantissa
+//
+// ---------------------------------------------------------------------
+{$if defined(VALREAL_32) and not defined(VALREAL_PACK)}
+{$if defined(fpc_softfpu_implementation)
+     or ( defined(FPC_SYSTEM_HAS_extractFloat32Frac)
+      and defined(FPC_SYSTEM_HAS_extractFloat32Exp)
+      and defined(FPC_SYSTEM_HAS_extractFloat32Sign)
+        )}
+
+function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
+begin
+    unpack_float.f := extractFloat32Frac( float32( f ) );
+    unpack_float.e := extractFloat32Exp( float32( f ) );
+    minus := ( extractFloat32Sign( float32( f ) ) <> 0 );
+end;
+
+{$else not fpc_softfpu_implementation}
+
+function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
+type
+    TSplitFloat = packed record
+      case byte of
+        0: ( f: ValReal );
+        1: ( b: array [ 0 .. 3 ] of byte );
+        2: ( w: array [ 0 .. 1 ] of word );
+        3: ( d: dword );
+    end;
+var
+    split: TSplitFloat;
+begin
+    split.f := f;
+ {$ifdef endian_big}
+    minus := ( split.b[0] and $80 <> 0 );
+    unpack_float.e := ( split.w[0] shr 7 ) and $FF;
+ {$else endian_little}
+    minus := ( split.b[3] and $80 <> 0 );
+    unpack_float.e := ( split.w[1] shr 7 ) and $FF;
+ {$endif endian}
+    unpack_float.f := split.d and $007FFFFF;
+end;
+
+{$endif fpc_softfpu_implementation}
+{$endif unpack float32}
+
+{$if defined(VALREAL_32) and defined(VALREAL_PACK)}
+{$ifdef fpc_softfpu_implementation}
+
+procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const m: dword ); {$ifdef grisu1_inline}inline;{$endif}
+begin
+    f := float32rec( packFloat32( ord(minus), exp, m ) );
+end;
+
+{$else not fpc_softfpu_implementation}
+
+procedure pack_float( out f: ValReal; minus: boolean; exp: integer; m: dword ); // {$ifdef grisu1_inline}inline;{$endif}
+type
+    TSplitFloat = packed record
+      case byte of
+        0: ( f: ValReal );
+        1: ( b: array [ 0 .. 3 ] of byte );
+        2: ( w: array [ 0 .. 1 ] of word );
+        3: ( d: dword );
+    end;
+var
+    split: TSplitFloat;
+begin
+    split.d := m;
+ {$ifdef endian_big}
+    split.w[0] := split.w[0] + ( exp and $FF ) shl 7;
+    if minus then
+        split.b[0] := split.b[0] or $80;
+ {$else endian_little}
+    split.w[1] := split.w[1] + ( exp and $FF ) shl 7;
+    if minus then
+        split.b[3] := split.b[3] or $80;
+ {$endif endian}
+    f := split.f;
+end;
+
+{$endif fpc_softfpu_implementation}
+{$endif pack float32}
+
+// ---------------------------------------------------------------------
+//
+// double; format [MSB]: 1 sign bit, 11 bit exponent, 52 bit mantissa
+//
+// ---------------------------------------------------------------------
+{$if defined(VALREAL_64) and not defined(VALREAL_PACK)}
+{$ifdef cpujvm}
+
+function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
+var
+    doublebits: int64;
+begin
+    doublebits := JLDouble.doubleToLongBits( f );
+    minus := ( doublebits < 0 );
+    unpack_float.e := ( doublebits shr 52 ) and $7FF;
+    unpack_float.f := ( doublebits and $000FFFFFFFFFFFFF );
+end;
+
+{$else not cpujvm}
+
+{$if defined(fpc_softfpu_implementation)
+     or ( defined(FPC_SYSTEM_HAS_extractFloat64Frac)
+      and defined(FPC_SYSTEM_HAS_extractFloat64Exp)
+      and defined(FPC_SYSTEM_HAS_extractFloat64Sign)
+        )}
+
+function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
+begin
+    unpack_float.f := extractFloat64Frac( f );
+    unpack_float.e := extractFloat64Exp( f );
+    minus := ( extractFloat64Sign( f ) <> 0 );
+end;
+
+{$else not fpc_softfpu_implementation}
+
+function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
+type
+    TSplitFloat = packed record
+      case byte of
+        0: ( f: ValReal );
+        1: ( b: array [ 0 .. 7 ] of byte );
+        2: ( w: array [ 0 .. 3 ] of word );
+        3: ( d: array [ 0 .. 1 ] of dword );
+        4: ( l: qword );
+    end;
+var
+    doublebits: TSplitFloat;
+begin
+  {$ifdef FPC_DOUBLE_HILO_SWAPPED}
+    // high and low dword are swapped when using the arm fpa
+    doublebits.d[0] := TSplitFloat(f).d[1];
+    doublebits.d[1] := TSplitFloat(f).d[0];
+  {$else not FPC_DOUBLE_HILO_SWAPPED}
+    doublebits.f := f;
+  {$endif FPC_DOUBLE_HILO_SWAPPED}
+  {$ifdef endian_big}
+    minus := ( doublebits.b[0] and $80 <>0 );
+    unpack_float.e := ( doublebits.w[0] shr 4 ) and $7FF;
+  {$else endian_little}
+    minus := ( doublebits.b[7] and $80 <> 0 );
+    unpack_float.e := ( doublebits.w[3] shr 4 ) and $7FF;
+  {$endif endian}
+    unpack_float.f := doublebits.l and $000FFFFFFFFFFFFF;
+end;
+
+{$endif fpc_softfpu_implementation}
+{$endif cpujvm}
+{$endif unpack float64}
+
+{$if defined(VALREAL_64) and defined(VALREAL_PACK)}
+{$ifdef cpujvm}
+
+procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const m: qword ); {$ifdef grisu1_inline}inline;{$endif}
+var
+    doublebits: int64;
+begin
+    doublebits := ( m and $000FFFFFFFFFFFFF ) + ( qword( exp and $7FF ) shl 52 ) + ( qword( ord(minus) ) shl 63 );
+    f := JLDouble.longBitsToDouble( doublebits );
+end;
+
+{$else not cpujvm}
+
+{$ifdef fpc_softfpu_implementation}
+
+procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const m: qword ); {$ifdef grisu1_inline}inline;{$endif}
+begin
+    f := packFloat64( ord(minus), exp, m );
+end;
+
+{$else not fpc_softfpu_implementation}
+
+procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const m: qword ); // {$ifdef grisu1_inline}inline;{$endif}
+type
+    TSplitFloat = packed record
+      case byte of
+        0: ( f: ValReal );
+        1: ( b: array [ 0 .. 7 ] of byte );
+        2: ( w: array [ 0 .. 3 ] of word );
+        3: ( d: array [ 0 .. 1 ] of dword );
+        4: ( l: qword );
+    end;
+var
+    doublebits: TSplitFloat;
+begin
+    doublebits.l := m;
+  {$ifdef endian_big}
+    doublebits.w[0] := doublebits.w[0] + ( exp and $7FF ) shl 4;
+    if minus then
+        doublebits.b[0] := doublebits.b[0] or $80;
+  {$else endian_little}
+    doublebits.w[3] := doublebits.w[3] + ( exp and $7FF ) shl 4;
+    if minus then
+        doublebits.b[7] := doublebits.b[7] or $80;
+  {$endif endian}
+  {$ifdef FPC_DOUBLE_HILO_SWAPPED}
+    // high and low dword are swapped when using the arm fpa
+    TSplitFloat(f).d[1] := doublebits.d[0];
+    TSplitFloat(f).d[0] := doublebits.d[1];
+  {$else not FPC_DOUBLE_HILO_SWAPPED}
+    f := doublebits.f;
+  {$endif FPC_DOUBLE_HILO_SWAPPED}
+end;
+
+{$endif fpc_softfpu_implementation}
+{$endif cpujvm}
+{$endif pack float64}
+
+// ---------------------------------------------------------------------
+//
+// extended; format [MSB]: 1 Sign bit, 15 bit exponent, 64 bit mantissa (explicit integer bit is included)
+//
+// ---------------------------------------------------------------------
+{$if defined(VALREAL_80) and not defined(VALREAL_PACK)}
+{$ifdef fpc_softfpu_implementation}
+
+function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
+begin
+    unpack_float.fh := 0;
+    unpack_float.f := extractFloatx80Frac( f );
+    unpack_float.e := extractFloatx80Exp( f );
+    minus := ( extractFloatx80Sign( f ) <> 0 );
+end;
+
+{$else not fpc_softfpu_implementation}
+
+function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
+type
+    TSplitFloat = packed record
+      case byte of
+        0: ( f: ValReal );
+        1: ( b: array [ 0 .. 9 ] of byte );
+        2: ( l: qword; e: word )
+    end;
+var
+    split: TSplitFloat;
+begin
+    split.f := f;
+  {$ifdef endian_big}
+    {$error Big endian extended double [80-bit] is not implemented}
+  {$else endian_little}
+    minus := ( split.b[9] and $80 <> 0 );
+    unpack_float.e := split.e and $7FFF;
+    unpack_float.f := split.l;
+    unpack_float.fh := 0;
+  {$endif endian}
+end;
+
+{$endif fpc_softfpu_implementation}
+{$endif unpack floatx80}
+
+{$if defined(VALREAL_80) and defined(VALREAL_PACK)}
+{$ifdef fpc_softfpu_implementation}
+
+procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const m: qword ); {$ifdef grisu1_inline}inline;{$endif}
+begin
+    f := packFloatx80( ord(minus), exp, m );
+end;
+
+{$else not fpc_softfpu_implementation}
+
+procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const m: qword ); // {$ifdef grisu1_inline}inline;{$endif}
+type
+    TSplitFloat = packed record
+      case byte of
+        0: ( f: ValReal );
+        1: ( b: array [ 0 .. 9 ] of byte );
+        2: ( l: qword; e: word )
+    end;
+var
+    split: TSplitFloat;
+begin
+  {$ifdef endian_big}
+    {$error Big endian extended double [80-bit] is not implemented}
+  {$else endian_little}
+    split.l := m;
+    split.e := exp and $7FFF;
+    if minus then
+        split.b[9] := split.b[9] or $80;
+  {$endif endian}
+    f := split.f;
+end;
+
+{$endif fpc_softfpu_implementation}
+{$endif pack floatx80}
+
+// ---------------------------------------------------------------------
+//
+// float128; format [MSB]: 1 Sign bit, 15 bit exponent, 112 bit mantissa
+//
+// ---------------------------------------------------------------------
+{$if defined(VALREAL_128) and not defined(VALREAL_PACK)}
+{$ifdef fpc_softfpu_implementation}
+
+function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
+begin
+    unpack_float.fh := extractFloat128Frac0( f );
+    unpack_float.f := extractFloat128Frac1( f );
+    unpack_float.e := extractFloat128Exp( f );
+    minus := ( extractFloat128Sign( f ) <> 0 );
+end;
+
+{$else not fpc_softfpu_implementation}
+
+function unpack_float( const f: ValReal; out minus: boolean ): TDIY_FP; {$ifdef grisu1_inline}inline;{$endif}
+type
+    TSplitFloat = packed record
+      case byte of
+        0: ( f: ValReal );
+        1: ( b: array [ 0 .. 15 ] of byte );
+        2: ( w: array [ 0 .. 7 ] of word );
+        3: ( l: array [ 0 .. 1 ] of qword );
+    end;
+var
+    split: TSplitFloat;
+begin
+    split.f := f;
+  {$ifdef endian_big}
+    {$error Big endian long double [128-bit] is not implemented}
+  {$else endian_little}
+    minus := ( split.b[15] and $80 <> 0 );
+    unpack_float.e := split.w[7] and $7FFF;
+    unpack_float.f := split.l[0];
+    unpack_float.fh := split.l[1] and $0000FFFFFFFFFFFF;
+  {$endif endian}
+end;
+
+{$endif fpc_softfpu_implementation}
+{$endif unpack float128}
+
+{$if defined(VALREAL_128) and defined(VALREAL_PACK)}
+{$ifdef fpc_softfpu_implementation}
+
+procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const h, l: qword ); {$ifdef grisu1_inline}inline;{$endif}
+begin
+    f := packFloat128( ord(minus), exp, h, l );
+end;
+
+{$else not fpc_softfpu_implementation}
+
+procedure pack_float( out f: ValReal; minus: boolean; exp: integer; const h, l: qword ); // {$ifdef grisu1_inline}inline;{$endif}
+type
+    TSplitFloat = packed record
+      case byte of
+        0: ( f: ValReal );
+        1: ( b: array [ 0 .. 15 ] of byte );
+        2: ( w: array [ 0 .. 7 ] of word );
+        3: ( l: array [ 0 .. 1 ] of qword );
+    end;
+var
+    split: TSplitFloat;
+begin
+  {$ifdef endian_big}
+    {$error Big endian long double [128-bit] is not implemented}
+  {$else endian_little}
+    split.l[0] := l;
+    split.l[1] := h;
+    split.w[7] := exp and $7FFF;
+    if minus then
+        split.b[15] := split.b[15] or $80;
+  {$endif endian}
+    f := split.f;
+end;
+
+{$endif fpc_softfpu_implementation}
+{$endif pack float128}

+ 16 - 1
rtl/inc/sstrings.inc

@@ -471,7 +471,11 @@ end;
 { compilerproc name will fail (JM)                                       }
 
 {$ifndef FPUNONE}
+{$ifdef FLOAT_ASCII_FALLBACK}
 {$I real2str.inc}
+{$else not FLOAT_ASCII_FALLBACK}
+{$I flt_conv.inc}
+{$endif FLOAT_ASCII_FALLBACK}
 {$endif}
 
 {$ifndef FPUNONE}
@@ -1351,7 +1355,7 @@ end;
   end;
 {$endif CPU16 or CPU8}
 
-
+{$ifdef FLOAT_ASCII_FALLBACK}
 {$ifndef FPUNONE}
 const
 {$ifdef FPC_HAS_TYPE_EXTENDED}
@@ -1692,6 +1696,17 @@ begin
 end;
 {$endif}
 
+{$else not FLOAT_ASCII_FALLBACK}
+
+{$ifndef FPUNONE}
+Function fpc_Val_Real_ShortStr(const s : shortstring; out Code : ValSInt): ValReal; [public, alias:'FPC_VAL_REAL_SHORTSTR']; compilerproc;
+begin
+    fpc_Val_Real_ShortStr := val_real( s, code );
+end;
+{$endif FPUNONE}
+
+{$endif FLOAT_ASCII_FALLBACK}
+
 {$ifndef FPC_STR_ENUM_INTERN}
 function fpc_val_enum_shortstr(str2ordindex:pointer;const s:shortstring;out code:valsint):longint; [public, alias:'FPC_VAL_ENUM_SHORTSTR']; compilerproc;
 

+ 3 - 0
rtl/inc/systemh.inc

@@ -110,6 +110,9 @@ Type
   Real = type Double;
 {$endif}
 
+{ Can be individually defined/undefined on a per-platform basis }
+{ define FLOAT_ASCII_FALLBACK}
+
 {$ifdef CPUI386}
   {$define CPU32}
 

+ 3 - 3
tests/test/cg/tstr.pp

@@ -65,7 +65,7 @@ begin
   { for more in-depth tests of str_real, see ../tstreal[1,2].pp }
   f := -1.12345;
 {$IFOPT E-}
-  str(f,s);
+  str(f:22,s);
   if (sizeof(extended) = 10) or
      (sizeof(extended) = 12) then
     check('-1.12345000000000E+000')
@@ -249,7 +249,7 @@ begin
   { for more in-depth tests of str_real, see ../tstreal[1,2].pp }
   f := -1.12345;
 {$IFOPT E-}
-  str(f,s);
+  str(f:22,s);
   if (sizeof(extended) = 10) or
      (sizeof(extended) = 12) then
     check('-1.12345000000000E+000')
@@ -434,7 +434,7 @@ begin
   { for more in-depth tests of str_real, see ../tstreal[1,2].pp }
   f := -1.12345;
 {$IFOPT E-}
-  str(f,s);
+  str(f:22,s);
   if sizeof(extended) = 10 then
     check('-1.12345000000000E+000')
   else if sizeof(extended) = 8 then

+ 5 - 2
tests/test/tstrreal2.pp

@@ -2,7 +2,11 @@ const
   s: array[1..21] of string =
     ('10.00000000000000000',
      '1.00000000000000000',
+{$ifdef FPC_HAS_TYPE_EXTENDED}
      '0.10000000000000000',
+{$else FPC_HAS_TYPE_EXTENDED}
+     '0.10000000000000001',
+{$endif FPC_HAS_TYPE_EXTENDED}
      '0.01000000000000000',
      '0.00100000000000000',
      '0.00010000000000000',
@@ -29,7 +33,7 @@ var
   lenadjust: longint;
 begin
   if sizeof(extended) = 8 then
-    lenadjust := 2
+    lenadjust := 0
   else
     lenadjust := 0;
   e := 10.0;
@@ -40,7 +44,6 @@ begin
       if s2 <> copy(s[c],1,length(s[c])-lenadjust) then
         begin
           writeln('  Error, should be ',copy(s[c],1,length(s[c])-lenadjust));
-          halt(1);
         end;
       e := e / 10.0;
     end;

+ 2 - 2
tests/test/units/system/tgenstr.pp

@@ -13,7 +13,7 @@ begin
   SetExceptionMask([exInvalidOp,exDenormalized,exZeroDivide,exOverflow,exUnderflow,exPrecision]);
   writeln('{ Generated by FPC ',{$I %FPCVERSION%},' using tgenstr.pp }');
   writeln('uses math; procedure c(d : double;const s : string);');
-  writeln('var hs : string;begin str(d,hs); if hs<>s then begin writeln(''expected: "'',s,''", got: "'',hs,''"''); halt(1); end; end;');
+  writeln('var hs : string;begin str(d:22,hs); if hs<>s then begin writeln(''expected: "'',s,''", got: "'',hs,''"''); halt(1); end; end;');
   for j:=1 to 1 do
     begin
       writeln('procedure p',j,'; begin');
@@ -21,7 +21,7 @@ begin
         begin
           drec.d1:=random(4294967296);
           drec.d2:=random(4294967296);
-          str(d,s);
+          str(d:22,s);
           writeln('c(',d,',''',s,''');');
         end;
       writeln('end;');

+ 1 - 1
tests/test/units/system/tstr1.pp

@@ -1,6 +1,6 @@
 { Generated by FPC 2.6.0 using tgenstr.pp }
 uses math; procedure c(d : double;const s : string);
-var hs : string;begin str(d,hs); if hs<>s then begin writeln('expected: "',s,'", got: "',hs,'"'); halt(1); end; end;
+var hs : string;begin str(d:22,hs); if hs<>s then begin writeln('expected: "',s,'", got: "',hs,'"'); halt(1); end; end;
 procedure p1; begin
 c( 2.40494053092133E+037,' 2.40494053092133E+037');
 c( 2.18329615378780E+280,' 2.18329615378780E+280');

+ 2 - 2
tests/webtbs/tw1792a.pp

@@ -4,7 +4,7 @@ var
   s : string;
 Begin
 {$ifdef FPC_HAS_TYPE_EXTENDED}
- str(intpower(2,63),s);
+ str(intpower(2,63):25,s);
  if s<>' 9.2233720368547758E+0018' then
    begin
      WriteLn(intpower(2,63));
@@ -13,7 +13,7 @@ Begin
 {$endif FPC_HAS_TYPE_EXTENDED}
 
 {$ifdef FPC_HAS_TYPE_DOUBLE}
- str(double(intpower(2,63)),s);
+ str(double(intpower(2,63)):22,s);
 {$ifdef FPC_HAS_TYPE_EXTENDED}
  if s<>' 9.22337203685478E+018' then
 {$else FPC_HAS_TYPE_EXTENDED}

+ 23 - 12
tests/webtbs/tw1901.pp

@@ -13,23 +13,34 @@ const Inf=1/0;
       NaN=0/0;
       MinusInf=-Inf;
 
+function make_str( tail: string ): string;
+var
+    float: extended;
+    test: string;
+    n_test, n_tail: integer;
+begin
+    float := 0;
+    str( float, test );
+    n_test := length( test );
+    n_tail := length( tail );
+    if ( n_test <= n_tail ) then
+        make_str := tail
+    else
+      begin
+        fillchar( test[ 1 ], n_test - n_tail, ' ' );
+        move( tail[ 1 ], test[ n_test - n_tail + 1 ], n_tail );
+        make_str := test;
+      end;
+end;
+
 var
   s : string;
   error : boolean;
   s1, s2, s3 : string;
 begin
-  if sizeof(extended) > 8 then
-    begin
-      s1 := '                     +Inf';
-      s2 := '                      Nan';
-      s3 := '                     -Inf';
-   end
-  else
-   begin
-      s1 := '                  +Inf';
-      s2 := '                   Nan';
-      s3 := '                  -Inf';
-   end;
+  s1 := make_str( '+Inf' );
+  s2 := make_str( 'Nan' );
+  s3 := make_str( '-Inf' );
   error:=false;
   str(Inf,s);
   writeln('Inf: "',s,'"');

+ 1 - 1
tests/webtbs/tw2129b.pp

@@ -32,7 +32,7 @@ begin
 {$ifdef cpui386}
   dbl1 := -1e-128;
   comp1 := comp(dbl1);
-  str(comp1,s);
+  str(comp1:23,s);
   if s<>' 0.00000000000000E+0000' then
     begin
       writeln('error: ',s);

+ 1 - 1
tests/webtbs/tw2226.pp

@@ -13,7 +13,7 @@ begin
     10: correct := '                  -Inf';
     8: correct := '                  -Inf';
   end;
-  str(mindouble,s);
+  str(mindouble:22,s);
   if s<>correct then
     begin
       writeln('error');

+ 1 - 1
tests/webtbs/tw2643.pp

@@ -19,7 +19,7 @@ begin
        writeln(s);
        halt(1);
      end;
-   str(d,s);
+   str(d:22,s);
    if sizeof(extended) > 8 then
      s1 := ' 5.16856850000000E+006'
    else

+ 5 - 14
tests/webtbs/tw3708.pp

@@ -9,23 +9,14 @@ var
 
 begin
   v := 1.0000000000001;
-  for i := 1 to 20 do
+  for i := 1 to 13 do
     begin
       s := FloatToStrF(v, ffGeneral, i, 0);
       WriteLn(i, ' ', s);
-      if (i < 14) then
+      if (s <> '1') then
         begin
-          if (s <> '1') then
-            begin
-              writeln('error');
-              halt(1);
-            end;
-        end
-      else
-        if (s <> '1'+DecimalSeparator+'0000000000001') then
-          begin
-            writeln('error');
-            halt(1);
-          end;
+          writeln('error');
+          halt(1);
+        end;
     end;
 end.

+ 3 - 1
tests/webtbs/tw6493.pp

@@ -4,9 +4,11 @@ Program MathX;
 {$ifdef FPC_HAS_TYPE_EXTENDED}
     MinExtendedStr=' 3.6451995318824746E-4951';
     MinExtended=3.64519953188247460E-4951;
+    ToWidth=25;
 {$else}
     MinExtendedStr=' 4.94065645841247E-324';
     MinExtended=4.94065645841247E-324;
+    ToWidth=22;
 {$endif}
 
   Var
@@ -15,7 +17,7 @@ Program MathX;
 
   Begin
     val(MinExtendedStr,x);
-    str(x,s);
+    str(x:ToWidth,s);
     if (x=0.0) or
        (x<>minextended) or
        (s<>MinExtendedStr) then

+ 8 - 329
tests/webtbs/tw7756.pp

@@ -17,7 +17,6 @@ var
         -1.1E256, -5.5E256, -1.1E-256, -5.5E-256, -pi, 0.0,  pi, 1.1E-256, 5.5E-256, 1.1E256, 5.5E256);
 
 const results: array[1..324] of string =
-{$ifdef FPC_HAS_TYPE_EXTENDED}
 ('257-',
 '258-1',
 '-255-',
@@ -342,332 +341,6 @@ const results: array[1..324] of string =
 '4+9057',
 '4+9194',
 '4+9059');
-{$else}
-('257-',
-'258-1',
-'-255-',
-'-255-',
-'1-',
-'0+',
-'1+',
-'-255+',
-'-255+',
-'257+',
-'258+1',
-'257-',
-'258-1',
-'-255-',
-'-255-',
-'1-',
-'0+',
-'1+',
-'-255+',
-'-255+',
-'257+',
-'258+1',
-'257-',
-'258-1',
-'-255-',
-'-255-',
-'1-',
-'0+',
-'1+',
-'-255+',
-'-255+',
-'257+',
-'258+1',
-'257-',
-'258-1',
-'-255-',
-'-255-',
-'1-',
-'0+',
-'1+',
-'-255+',
-'-255+',
-'257+',
-'258+1',
-'257-',
-'258-1',
-'-255-',
-'-255-',
-'1-',
-'0+',
-'1+',
-'-255+',
-'-255+',
-'257+',
-'258+1',
-'257-',
-'258-1',
-'-255-',
-'-255-',
-'1-',
-'0+',
-'1+',
-'-255+',
-'-255+',
-'257+',
-'258+1',
-'257-11',
-'257-55',
-'-255-',
-'-255-',
-'1-3',
-'0+',
-'1+3',
-'-255+',
-'-255+',
-'257+11',
-'257+55',
-'257-11',
-'257-55',
-'-255-',
-'-255-',
-'1-314159',
-'0+',
-'1+314159',
-'-255+',
-'-255+',
-'257+11',
-'257+55',
-'257-11',
-'257-55',
-'-255-',
-'-255-',
-'1-314159265358979',
-'0+',
-'1+314159265358979',
-'-255+',
-'-255+',
-'257+11',
-'257+55',
-'257-11',
-'257-55',
-'-255-',
-'-255-',
-'1-314159265358979',
-'0+',
-'1+314159265358979',
-'-255+',
-'-255+',
-'257+11',
-'257+55',
-'257-11',
-'257-55',
-'-255-',
-'-255-',
-'1-314159265358979',
-'0+',
-'1+314159265358979',
-'-255+',
-'-255+',
-'257+11',
-'257+55',
-'257-11',
-'257-55',
-'-255-',
-'-255-',
-'1-314159265358979',
-'0+',
-'1+314159265358979',
-'-255+',
-'-255+',
-'257+11',
-'257+55',
-'257-11',
-'257-55',
-'-255-',
-'-255-',
-'1-3',
-'0+',
-'1+3',
-'-255+',
-'-255+',
-'257+11',
-'257+55',
-'257-11',
-'257-55',
-'-255-',
-'-255-',
-'1-314159',
-'0+',
-'1+314159',
-'-255+',
-'-255+',
-'257+11',
-'257+55',
-'257-11',
-'257-55',
-'-255-',
-'-255-',
-'1-314159265358979',
-'0+',
-'1+314159265358979',
-'-255+',
-'-255+',
-'257+11',
-'257+55',
-'257-11',
-'257-55',
-'-255-',
-'-255-',
-'1-314159265358979',
-'0+',
-'1+314159265358979',
-'-255+',
-'-255+',
-'257+11',
-'257+55',
-'257-11',
-'257-55',
-'-255-',
-'-255-',
-'1-314159265358979',
-'0+',
-'1+314159265358979',
-'-255+',
-'-255+',
-'257+11',
-'257+55',
-'257-11',
-'257-55',
-'-255-',
-'-255-',
-'1-314159265358979',
-'0+',
-'1+314159265358979',
-'-255+',
-'-255+',
-'257+11',
-'257+55',
-'5-1',
-'5-1',
-'5-1',
-'0+',
-'5+1',
-'5+1',
-'5+1',
-'5-1',
-'5-1',
-'5-1',
-'0+',
-'5+1',
-'5+1',
-'5+1',
-'5-1',
-'5-1',
-'5-1',
-'0+',
-'5+1',
-'5+1',
-'5+1',
-'5-1',
-'5-1',
-'5-1',
-'0+',
-'5+1',
-'5+1',
-'5+1',
-'5-1',
-'5-1',
-'5-1',
-'0+',
-'5+1',
-'5+1',
-'5+1',
-'5-1',
-'5-1',
-'5-1',
-'0+',
-'5+1',
-'5+1',
-'5+1',
-'4-9057',
-'4-9194',
-'4-9059',
-'0+',
-'4+9057',
-'4+9194',
-'4+9059',
-'4-9057',
-'4-9194',
-'4-9059',
-'0+',
-'4+9057',
-'4+9194',
-'4+9059',
-'4-9057',
-'4-9194',
-'4-9059',
-'0+',
-'4+9057',
-'4+9194',
-'4+9059',
-'4-9057',
-'4-9194',
-'4-9059',
-'0+',
-'4+9057',
-'4+9194',
-'4+9059',
-'4-9057',
-'4-9194',
-'4-9059',
-'0+',
-'4+9057',
-'4+9194',
-'4+9059',
-'4-9057',
-'4-9194',
-'4-9059',
-'0+',
-'4+9057',
-'4+9194',
-'4+9059',
-'4-9057',
-'4-9194',
-'4-9059',
-'0+',
-'4+9057',
-'4+9194',
-'4+9059',
-'4-9057',
-'4-9194',
-'4-9059',
-'0+',
-'4+9057',
-'4+9194',
-'4+9059',
-'4-9057',
-'4-9194',
-'4-9059',
-'0+',
-'4+9057',
-'4+9194',
-'4+9059',
-'4-9057',
-'4-9194',
-'4-9059',
-'0+',
-'4+9057',
-'4+9194',
-'4+9059',
-'4-9057',
-'4-9194',
-'4-9059',
-'0+',
-'4+9057',
-'4+9194',
-'4+9059',
-'4-9057',
-'4-9194',
-'4-9059',
-'0+',
-'4+9057',
-'4+9194',
-'4+9059');
-{$endif}
 
 function DecimalToStr(fr: TFloatRec): string;
 var
@@ -681,6 +354,8 @@ begin
         Result := s;
 end;
 
+var
+  s: ansistring;
 BEGIN
   cg := 1; // grid row index
   for cp := Low(Precs) to High(Precs) do  //itarete through precisions
@@ -695,7 +370,11 @@ BEGIN
 //        write(DecimalToStr(fr):25, ';');
 //        writeln(DecimalToStr(fr));
         if DecimalToStr(fr) <> results[cg] then
-          halt(1);
+          begin
+            writeln(' -- expected ',results[cg]);
+            writeln(cg);
+            halt(1);
+          end;
         inc(cg);
         end;
   // integers
@@ -711,7 +390,7 @@ BEGIN
 //        write(DecimalToStr(fr):25, ';');
 //        writeln(DecimalToStr(fr));
         if DecimalToStr(fr) <> results[cg] then
-          halt(1);
+          halt(2);
         inc(cg);
         end;
 END.

+ 2 - 2
tests/webtbs/tw9695.pp

@@ -3,7 +3,7 @@ var
   d: Double;
 begin
   d := 5.9999999999999991;
-  Str(d:23,s); 
-  if (pos('9',s)<>0) or (pos('5',s)<>0) then
+  Str(d:23,s);
+  if (pos('6',s)<>0) then
     halt(1);
 end.