News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Printing REAL10 values with the full range of decimal digits

Started by Gunther, January 01, 2015, 02:38:57 PM

Previous topic - Next topic

Gunther

To print out REAL10 values with an average accuracy of 19.2 significant decimal digits under Windows seems to be a real challenge. The Windows RTL (I think it's msvcrt.dll) doesn't support REAL10 values and maps long double to double. That gives a mess. We had a lot of discussions in the old and the new forum about this point. There are workarounds available and here is another one.

The attached archive r10.zip contains the sources and binaries for a small test program. Here is the output:
Quote
x = 3.1415926535897932385

Please, press enter to end the application ...
For re-compiling you'll need the MinGW, MSVC won't work. The reason is simple: The GNU libc (glibc) has the full support for REAL10 numbers. But as a factory default, the MinGW uses the procedures of MSVC for printf, scanf etc. However, MinGW also comes with a set of alternative implementations that do properly support long doubles. These must be linked in. How to call those procedures shows my little test program.

I would not say that this technique to call printf from assembly language is ideal. But I've no other way at the moment. On the other hand, my example prints out 20 significant decimal digits; the rounding in the last digit is okay. So far I have found no number where this technique fails (on average, 19 decimal digits are printed). But it may well be that there are such numbers.

My next idea was, to check printf from the glibc to collect some ideas. My goal is to write an appropriate assembly language procedure, which can be called from MSVC or assembly language. But that's not a simple task. The glibc manual has 1095 pages. The function printf itself is very short:

__printf (const char *format, ...)
{
   va_list arg;
   int done;

  va_start (arg, format);
  done = vfprintf (stdout, format, arg);
  va_end (arg);

  return done;
}

It uses varargs to get the arguments in a variable length argument list. The full code is here. All the fancy format magic goes in vfprintf. It has over 2000 sophisticated lines of code.

I've to figure out now the REAL10 parts and what happens there. Any help is welcome. Testing results and comments are welcome, too.

Gunther
You have to know the facts before you can distort them.

jj2007

Interesting stuff, Gunther :t
The vfprintf source is C code, i.e. the step from C to assembly is not transparent. The music plays in the two functions marked in red below.
00405595        ³.ÚEB 22          jmp short 004055B9
00405597        ³> ³892C24         Úmov [local.42], ebp
0040559A        ³. ³C74424 08 0000 ³mov dword ptr [local.40], 0
004055A2        ³. ³C74424 04 0A00 ³mov dword ptr [local.41], 0A
004055AA        ³. ³E8 F1130000    ³call 004069A0
004055AF        ³. ³838424 8C00000 ³add dword ptr [local.7], 1
004055B7        ³. ³89C5           ³mov ebp, eax
004055B9        ³> À897C24 04      +mov [local.41], edi
004055BD        ³.  892C24         ³mov [local.42], ebp
004055C0        ³.  E8 BB0E0000    ³call 00406480
004055C5        ³.  83C0 30        ³add eax, 30
004055C8        ³.  8806           ³mov [esi], al
004055CA        ³.  83C6 01        ³add esi, 1
004055CD        ³.  3B9C24 8C00000 ³cmp ebx, [local.7]
004055D4        ³. 7F C1          Àjg short 00405597
004055D6        ³.  8B5C24 24      mov ebx, [local.33]

adeyblue


jj2007

Line 329 points to __mpn_extract_long_double:
QuoteConvert a `long double' in IEEE854 quad-precision format to a
0028    multi-precision integer representing the significand scaled up by its
0029    number of bits (113 for long double) and an integral power of two
0030    (MPN frexpl).

But still no assembly code there :(

Gunther

Jochen,

Quote from: jj2007 on January 01, 2015, 09:05:45 PM
Interesting stuff, Gunther :t

I hope so.

Quote from: jj2007 on January 01, 2015, 09:05:45 PM
The vfprintf source is C code, i.e. the step from C to assembly is not transparent. The music plays in the two functions marked in red below.

There's good news on the way. Since gcc compiles in every language always via assembly (gas is the back end), the assembly language source is available in any case. That costs nothing, only one more compiler run with the switch -S. As an example I've included the assembly language source of r10.c. This is r10.s and it can be read with every editor (clean ASCII text). I think that's transparent enough. Of course, one has to pay a price. The assembly language source of the C code is in AT&T syntax. But that's no big deal, because I've enough experiences in reading and writing AT&T syntax. I won't go the way over a debugger, because:

  • The C source is available.
  • We have the same problem under 64-bit Windows and we need a solution for both worlds.

adeyblue,

Quote from: adeyblue on January 02, 2015, 02:34:16 AM
In the source, the float stuff is in __print_fp, and in there it pretty much just offloads to GNU MP, line 329 seems to be where the actual fun begins.

good catch.  :t It's probably sufficient to examine this procedure.  The author is Ulrich Drepper. He was over years the maintainer of the glibc. He is a complicated personality with a large ego. We know that well from our forum, too. But he writes good articles and sophisticated code. That is what counts.

Gunther
You have to know the facts before you can distort them.

nidud

deleted

jj2007

Quote from: nidud on January 02, 2015, 10:47:51 PM
and there seems to be a bug there in JWASM (invoke)

Indeed. Below the standard Masm32 version for testing.
Besides, printf apparently can't handle REAL10:
x = -88796093704934486000000000000000000000000000.0000000000000000000

include \masm32\include\masm32rt.inc

.data
format  db "x = %1.19Lf",10,0
num dt 3.14159265358979323846264338327

.code
start:
invoke crt_printf, addr format, num
xor eax, eax
ret
end start

nidud

deleted

jj2007

Quote from: nidud on January 03, 2015, 01:01:26 AM
I haven't seen 'L' being used for LONG in a format string ?

Wellllll... that was you who proposed that format, right? ;-)
It seems it's a known problem, good ol' Microsoft CRT simply doesn't know what a long double is. We also had a thread on printf and long integers some time ago, but the solution proposed there (msvcrt100.dll) doesn't work with floats. Take MasmBasic Str$() - 19 digits... ;-)

nidud

deleted

Gunther

Hi nidud,

thank you for your answers.
Quote from: nidud on January 02, 2015, 10:47:51 PM
Gunther, I did some testing on printing long double
and there seems to be a bug there in JWASM (invoke)
...
the stack is destroyed here on return

that's real bad news. Will become jWasm more and more problematic? Since I'm not a friend of the fancy macro stuff (invoke etc.) I couldn't find that error. I think Sinsi has a similar point of view.

I've described the limitations of the MS RTL in my first post of this thread. We had a lot of discussions about it, for example here or here. So that's not new and the behavior of msvcrt.dll is also not new.

Quote from: jj2007 on January 03, 2015, 02:47:56 AM
Take MasmBasic Str$() - 19 digits... ;-)

Except numbers that start with 923. For such numbers, one has to use glibc:
Quote
x = 923.6666666666666667

Please, press enter to end the application ...

That gives 19 numbers and the rounding in the last decimal digit is okay. The archive r10U1.zip is attached under this post.

A further step forward would be, for example, to write assembly language code for ml, jWasm, nasm, fasm or whatever assembler and using the GNU linker ld for building the EXE with the glibc library. I've at the moment a good discussion via PM with Vortex about this point. But the build process isn't easy. Here is the build dump:
Quote
c:\gcc\work>gcc -v -o r10.exe r10.o r10.obj
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=c:/mingw/bin/../libexec/gcc/i686-w64-mingw32/4.8.0/lto-wrapp
er.exe
Target: i686-w64-mingw32
Configured with: ../../../src/gcc-trunk/configure --host=i686-w64-mingw32 --buil
d=i686-w64-mingw32 --target=i686-w64-mingw32 --prefix=/mingw32 --with-sysroot=/t
emp/x32-trunk-win32-sjlj/mingw32 --enable-shared --enable-static --enable-target
s=all --enable-multilib --enable-languages=c,c++,fortran,lto --enable-libstdcxx-
time=yes --enable-threads=win32 --enable-libgomp --enable-lto --enable-graphite
--enable-checking=release --enable-fully-dynamic-string --enable-version-specifi
c-runtime-libs --enable-sjlj-exceptions --disable-isl-version-check --disable-cl
oog-version-check --disable-libstdcxx-pch --disable-libstdcxx-debug --disable-bo
otstrap --disable-rpath --disable-win32-registry --disable-nls --disable-werror
--disable-symvers --with-gnu-as --with-gnu-ld --with-arch-32=i686 --with-arch-64
=nocona --with-tune-32=generic --with-tune-64=core2 --with-host-libstdcxx='-stat
ic -lstdc++' --with-libiconv --with-system-zlib --with-gmp=/temp/mingw-prereq/i6
86-w64-mingw32-static --with-mpfr=/temp/mingw-prereq/i686-w64-mingw32-static --w
ith-mpc=/temp/mingw-prereq/i686-w64-mingw32-static --with-isl=/temp/mingw-prereq
/i686-w64-mingw32-static --with-cloog=/temp/mingw-prereq/i686-w64-mingw32-static
--enable-cloog-backend=isl --with-pkgversion='rev, Built by MinGW-builds projec
t' --with-bugurl=http://sourceforge.net/projects/mingwbuilds/ CFLAGS='-O2 -pipe
-I/temp/x32-trunk-win32-sjlj/libs/include -I/temp/mingw-prereq/x32-zlib/include
-I/temp/mingw-prereq/i686-w64-mingw32-static/include' CXXFLAGS='-O2 -pipe -I/tem
p/x32-trunk-win32-sjlj/libs/include -I/temp/mingw-prereq/x32-zlib/include -I/tem
p/mingw-prereq/i686-w64-mingw32-static/include' CPPFLAGS= LDFLAGS='-pipe -L/temp
/x32-trunk-win32-sjlj/libs/lib -L/temp/mingw-prereq/x32-zlib/lib -L/temp/mingw-p
rereq/i686-w64-mingw32-static/lib -L/temp/x32-trunk-win32-sjlj/mingw32/opt/lib'
Thread model: win32
gcc version 4.8.0 20130314 (experimental) (rev, Built by MinGW-builds project)
COMPILER_PATH=c:/mingw/bin/../libexec/gcc/i686-w64-mingw32/4.8.0/;c:/mingw/bin/.
./libexec/gcc/;c:/mingw/bin/../lib/gcc/i686-w64-mingw32/4.8.0/../../../../i686-w
64-mingw32/bin/
LIBRARY_PATH=c:/mingw/bin/../lib/gcc/i686-w64-mingw32/4.8.0/;c:/mingw/bin/../lib
/gcc/;c:/mingw/bin/../lib/gcc/i686-w64-mingw32/4.8.0/../../../../i686-w64-mingw3
2/lib/../lib/;c:/mingw/bin/../lib/gcc/i686-w64-mingw32/4.8.0/../../../../lib/;c:
/mingw/bin/../lib/gcc/i686-w64-mingw32/4.8.0/../../../../i686-w64-mingw32/lib/;c
:/mingw/bin/../lib/gcc/i686-w64-mingw32/4.8.0/../../../
COLLECT_GCC_OPTIONS='-v' '-o' 'r10.exe' '-mtune=generic' '-march=i686'
c:/mingw/bin/../libexec/gcc/i686-w64-mingw32/4.8.0/collect2.exe --sysroot=C:/gc
cbuild/msys/temp/x32-trunk-win32-sjlj/mingw32 -m i386pe -Bdynamic -o r10.exe c:/
mingw/bin/../lib/gcc/i686-w64-mingw32/4.8.0/../../../../i686-w64-mingw32/lib/../
lib/crt2.o c:/mingw/bin/../lib/gcc/i686-w64-mingw32/4.8.0/crtbegin.o -Lc:/mingw/
bin/../lib/gcc/i686-w64-mingw32/4.8.0 -Lc:/mingw/bin/../lib/gcc -Lc:/mingw/bin/.
./lib/gcc/i686-w64-mingw32/4.8.0/../../../../i686-w64-mingw32/lib/../lib -Lc:/mi
ngw/bin/../lib/gcc/i686-w64-mingw32/4.8.0/../../../../lib -Lc:/mingw/bin/../lib/
gcc/i686-w64-mingw32/4.8.0/../../../../i686-w64-mingw32/lib -Lc:/mingw/bin/../li
b/gcc/i686-w64-mingw32/4.8.0/../../.. r10.o r10.obj -lmingw32 -lgcc -lgcc_eh -lm
oldname -lmingwex -lmsvcrt -ladvapi32 -lshell32 -luser32 -lkernel32 -liconv -lmi
ngw32 -lgcc -lgcc_eh -lmoldname -lmingwex -lmsvcrt c:/mingw/bin/../lib/gcc/i686-
w64-mingw32/4.8.0/crtend.o
So, Erol is working on it, I've to work on my kernel-mode driver with an approaching dead line. It will take some time before we've results.

Without any doubts, that would be only another workaround, because that won't work with Microsoft's VS.

Gunther
You have to know the facts before you can distort them.

Vortex

Hi Gunther,

I guess I found the dependencies \ libraries involved to build the project. Tools used :

- Agner Fog's objconv tool to extract the members from libraries :

objconv -lx library.a

objconv was used also to disassemble the object modules to find the other dependencies. __mingw_vprintf was the first module but it looks like that there are a lot of member functions linked by ld.exe

- Nirsoft's SearchMyFiles to search the module names inside libraries.

http://www.nirsoft.net/utils/search_my_files.html

There are two builds. Real10ValA is using all the necessary object modules extracted from libraries.

A more simple version, Real10ValB is using the libraries but I was forced to specify pseudo-reloc.o in the command-line to avoid linkage error.

I used my tiny C run-time library adapted to MinGW. The size of r10.exe is reduced from 77537 bytes to 29184 bytes

Gunther

Great job, Erol.  :t Thank you for the fast work. I'll try that tomorrow. It seems to me that we've another workaround.  :t

Gunther
You have to know the facts before you can distort them.

Gunther

Erol,

I've tested both applications and both crashed, as well under Win7-64 as under WinXP. I think you're on the right way. It's probably a minor detail.

Gunther
You have to know the facts before you can distort them.

Vortex

Hi Gunther,

My apologies for the inconvenience. This time, I rebuilt the project using the parameters provided by the verbose report. You can check the image parameters.PNG to see the options copied to the batch file. The paths in the .bat file can be simplified.