mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-06 17:28:23 +02:00
2751 lines
93 KiB
PHP
2751 lines
93 KiB
PHP
{
|
|
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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( var 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( var 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;
|
|
|
|
(*-------------------------------------------------------
|
|
| do_fillchar [local]
|
|
|
|
|
| Fills string region with certain character.
|
|
|
|
|
*-------------------------------------------------------*)
|
|
{$ifdef cpujvm}
|
|
procedure do_fillchar( var str: shortstring; pos, count: integer; c: AnsiChar );
|
|
begin
|
|
while count>0 do
|
|
begin
|
|
str[pos]:=c;
|
|
inc(pos);
|
|
dec(count);
|
|
end;
|
|
end;
|
|
{$else not cpujvm}
|
|
procedure do_fillchar( var str: shortstring; pos, count: integer; c: AnsiChar ); {$ifdef grisu1_inline}inline;{$endif}
|
|
begin
|
|
fillchar( str[pos], count, c );
|
|
end;
|
|
{$endif cpujvm}
|
|
|
|
(*-------------------------------------------------------
|
|
| 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
|
|
{$ifdef cpujvm}
|
|
temp_round := digits;
|
|
{$else not cpujvm}
|
|
if ( n_digits_have > 0 ) then
|
|
move( digits, temp_round, n_digits_have * sizeof( digits[0] ) );
|
|
{$endif cpujvm}
|
|
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
|
|
do_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] := AnsiChar( temp_round[j] + ord('0') );
|
|
inc( i );
|
|
inc( j );
|
|
dec( n_before_dot );
|
|
end
|
|
else
|
|
while ( n_before_dot > 0 ) do
|
|
begin
|
|
str[i] := AnsiChar( digits[j] + ord('0') );
|
|
inc( i );
|
|
inc( j );
|
|
dec( n_before_dot );
|
|
end;
|
|
// Integer 0-padding
|
|
if ( n_before_dot_pad0 > 0 ) then
|
|
begin
|
|
do_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
|
|
do_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] := AnsiChar( temp_round[j] + ord('0') );
|
|
inc( i );
|
|
inc( j );
|
|
dec( n_after_dot );
|
|
end
|
|
else
|
|
while ( n_after_dot > 0 ) do
|
|
begin
|
|
str[i] := AnsiChar( digits[j] + ord('0') );
|
|
inc( i );
|
|
inc( j );
|
|
dec( n_after_dot );
|
|
end;
|
|
// Tail 0-padding
|
|
if ( n_tail_pad0 > 0 ) then
|
|
begin
|
|
do_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
|
|
do_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] := AnsiChar( 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] := AnsiChar( digits[j] + ord('0') );
|
|
inc( i );
|
|
inc( j );
|
|
end;
|
|
// Fraction 0-padding
|
|
j := n_digits_req - j;
|
|
if ( j > 0 ) then
|
|
begin
|
|
do_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
|
|
do_fillchar( str, i, j, '0' );
|
|
inc( i, j );
|
|
end;
|
|
// Exponent digits
|
|
for j := 0 to n_exp - 1 do
|
|
begin
|
|
str[i] := AnsiChar( 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
|
|
do_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
|
|
while slen>0 do
|
|
begin
|
|
str[i+slen-1] := spec[slen];
|
|
dec(slen);
|
|
end;
|
|
end;
|
|
|
|
{$if defined(VALREAL_80) or defined(VALREAL_128)}
|
|
{$ifndef FPC_SYSTEM_HAS_U128_DIV_U64_TO_U64}
|
|
(*-------------------------------------------------------
|
|
| 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, remainder: qword ): boolean;
|
|
var
|
|
b, // Number base
|
|
v : qword; // Norm. divisor
|
|
un1, un0, // Norm. dividend LSD's
|
|
vn1, vn0 : dword; // 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 ( hi(q1) <> 0 ) 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 ( hi(q0) <> 0 ) or ( q0 * vn0 > b * rhat + un0 ) do
|
|
begin
|
|
dec( q0 );
|
|
inc( rhat, vn1 );
|
|
if ( hi(rhat) <> 0 ) then
|
|
break;
|
|
end;
|
|
// Result
|
|
remainder := ( un21 * b + un0 - q0 * v ) shr s;
|
|
quotient := q1 * b + q0;
|
|
u128_div_u64_to_u64 := true;
|
|
end;
|
|
{$endif FPC_SYSTEM_HAS_U128_DIV_U64_TO_U64}
|
|
{$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 {$else} dword {$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 {$else} dword {$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: shortstring );
|
|
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: AnsiChar;
|
|
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;
|