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 (http://www.gnu.org/software/libc/manual/pdf/libc.pdf) 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 (https://sourceware.org/git/?p=glibc.git;a=blob;f=stdio-common/printf.c;h=4c8f3a2a0c38ab27a2eed4d2ff3b804980aa8f9f;hb=3321010338384ecdc6633a8b032bb0ed6aa9b19a). All the fancy format magic goes in vfprintf (https://sourceware.org/git/?p=glibc.git;a=blob;f=stdio-common/vfprintf.c;h=fc370e8cbc4e9652a2ed377b1c6f2324f15b1bf9;hb=3321010338384ecdc6633a8b032bb0ed6aa9b19a). 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
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]
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 (https://sourceware.org/git/?p=glibc.git;a=blob;f=stdio-common/printf_fp.c;h=43c43c20390759df75731856d0068a606b3291ff;hb=1311b164df26ee49740b805d4f32fffde163b1e6).
Line 329 points to __mpn_extract_long_double (http://osxr.org/glibc/source/sysdeps/ieee754/ldbl-128/ldbl2mpn.c?v=glibc-2.17#0033):
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 :(
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 (https://sourceware.org/git/?p=glibc.git;a=blob;f=stdio-common/printf_fp.c;h=43c43c20390759df75731856d0068a606b3291ff;hb=1311b164df26ee49740b805d4f32fffde163b1e6).
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 (http://www.akkadia.org/drepper/cpumemory.pdf) and sophisticated code. That is what counts.
Gunther
deleted
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
deleted
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 (http://stackoverflow.com/questions/4089174/printf-and-long-double), good ol' Microsoft CRT simply doesn't know what a long double is. We also had a thread on printf and long integers (http://masm32.com/board/index.php?topic=2769.0) some time ago, but the solution proposed there (msvcrt100.dll) doesn't work with floats. Take MasmBasic Str$() (http://www.webalice.it/jj2006/MasmBasicQuickReference.htm#Mb1186) - 19 digits... ;-)
deleted
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 (http://www.masmforum.com/board/index.php?topic=14790.0) or here (http://masm32.com/board/index.php?topic=1768.msg17962#msg17962). 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$() (http://www.webalice.it/jj2006/MasmBasicQuickReference.htm#Mb1186) - 19 digits... ;-)
Except numbers that start with 923 (http://masm32.com/board/index.php?topic=94.msg41105#msg41105). 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
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
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
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
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.
Erol,
never mind. The new version, included in Real10ValC.zip works fine. I'll check the code and build process in detail tomorrow, because I've to finish my kernel-mode driver. It works now as expected, but I've to write the documentation, which is a lot of work to do.
Gunther
Hi Gunther,
No any problem. The internals of MinGW are very complicated and probably the A and B examples are missing something important.
Erol,
Quote from: Vortex on January 04, 2015, 03:40:05 AM
The internals of MinGW are very complicated and probably the A and B examples are missing something important.
that's my impression, too. But my idea should be practicable in both worlds (Win-32 and Win-64). By the way, what's the behavior of Pelle's C with long double values?
Gunther
Quote from: Gunther on January 04, 2015, 04:30:02 AMwhat's the behavior of Pelle's C with long double values?
long double ld=1234567890.1234567890;
printf("This is a long double: %.9f\n", ld);
This is a long double: 1234567890.123456746
@Erol: What about a dll or static lib version for the ordinary mortals among us?
Hi Jochen,
Quote@Erol: What about a dll or static lib version for the ordinary mortals among us?
As I mentioned above, the linking process managed by ld.exe is very complicated. We have to understand the relations between the MinGW libraries. For the moment, it's better to mimic some portions of the verbose logging. Not a perfect solution but it seems to work.
Thanks to Vortex and Gunther diving the necessary command line, I've created some dlls for just the various __mingw_printf / scanf functions. It's pretty much just a matter of using a def to say what to export and adding in all those libraries to the linker. The f* versions aren't there, since the FILE struct is likely to be different for other compilers / versions of msvcrt, but you can just uncomment them from the def if you want them.
It seems to work from the quick test I gave it.
Hi adeyblue,
thank you for your distribution. I'll test it this evening. Could one integrate it into VS?
Gunther
As far as you can call the functions, yes. You can't print long doubles directly using the C/C++ compiler since they're really only normal 64-bit doubles, but you can print them if you get them via other means (like below where its defined in hex).
MASM should be fine though.
void PrintIt(const char* p, ...)
{
va_list a;
va_start(a, p);
int ret = __mingw_vprintf(p, a);
va_end(a);
}
void test()
{
unsigned doub[4]; // 1234567890.123456789 in hex
doub[3] = 0;
doub[2] = 0x401d;
doub[1] = 0x932C05A4;
doub[0] = 0x3F35BA6E;
__m128 val = _mm_loadu_ps((float*)&doub[0]);
__mingw_printf("Value is %.9Lf\n", val);
PrintIt("Value is %.9Lf\n", val);
}
I'm trying to build the relevant bits from source, but the resulting dll is crash happy.
Hi adeyblue,
Quote from: adeyblue on January 06, 2015, 08:40:51 AM
I'm trying to build the relevant bits from source, but the resulting dll is crash happy.
yes, entire topic is complicated, but your plan could be successful.
Gunther
Quote from: adeyblue on January 06, 2015, 08:40:51 AMI'm trying to build the relevant bits from source, but the resulting dll is crash happy.
MS VC 2010 gave me "Cannot load the project due to a corrupt project file" (I've never seen a successful conversion, VS is such a crap :icon_redface:).
Can you build the DLL in 32-bit and post it here? I'd like to compare the results to my own code.
The .vcproj file contains absolute paths "f:\dev-cpp\lib32;f:\dev-cpp\lib32\gcc\4.7.2\" which may not exist on you computer.
I've just downloaded the archive and found both 32 abd 64 bit dlls in "files" folder.
Hi vertograd,
Quote from: vertograd on January 07, 2015, 08:30:47 AM
The .vcproj file contains absolute paths "f:\dev-cpp\lib32;f:\dev-cpp\lib32\gcc\4.7.2\" which may not exist on you computer.
I've just downloaded the archive and found both 32 abd 64 bit dlls in "files" folder.
oh yes, these tricky project files. What about this: Writing a batch file and using the command line compiler? I hope that Microsoft provides it. That avoids the trouble.
Gunther
Hi Gunther,
The command line for MS VS project can be terrific :shock:
Once I started to write python script that parses .vcproj and converts it to linux makefile (for opengl examples ) . The first part was done but the later is not yet finished .
Quote from: vertograd on January 07, 2015, 08:30:47 AMI've just downloaded the archive and found both 32 abd 64 bit dlls in "files" folder.
Oops, I was so busy trying to compile that I had not even looked into that folder. Thanks, adeyblue & vertograd :t
Here is a first test with the 32-bit DLL (left: MasmBasic Str$() (http://www.webalice.it/jj2006/MasmBasicQuickReference.htm#Mb1186), right: __mingw_sprintf). It seems that while you can specify more than 19 digits with sprintf(), their value is kind of doubtful:
digits 12345678901234567890 12345678901234567890
#1 0.1111111111111111111 0.1111111111111111111028290
#2 0.2222222222222222222 0.2222222222222222222056580
#3 0.3333333333333333333 0.3333333333333333333152633
#4 0.4444444444444444444 0.4444444444444444444113160
#5 0.5555555555555555555 0.5555555555555555555073688
#6 0.6666666666666666666 0.6666666666666666666305266
#7 0.7777777777777777777 0.7777777777777777777536844
#8 0.8888888888888888888 0.8888888888888888888768422
#9 1.000000000000000000 1.0000000000000000000000000
#10 1.111111111111111111 1.1111111111111111111231578
#11 1.222222222222222222 1.2222222222222222222463156
#86 9.555555555555555549 9.5555555555555555490021558
#87 9.666666666666666660 9.6666666666666666600168933
#88 9.777777777777777771 9.7777777777777777710316309
#89 9.888888888888888882 9.8888888888888888820463685
#90 9.999999999999999993 9.9999999999999999930611061
#91 10.11111111111111110 10.1111111111111111040758437
#92 10.22222222222222221 10.2222222222222222150905813
#93 10.33333333333333333 10.3333333333333333261053189
#94 10.44444444444444444 10.4444444444444444371200564
#95 10.55555555555555555 10.5555555555555555481347940
#96 10.66666666666666666 10.6666666666666666591495316
#97 10.77777777777777777 10.7777777777777777701642692
#98 10.88888888888888888 10.8888888888888888811790068
#99 10.99999999999999999 10.9999999999999999921937444
#100 11.11111111111111110 11.1111111111111111032084819
#996 110.6666666666666673 110.6666666666666673096708351
#997 110.7777777777777784 110.7777777777777784215529344
#998 110.8888888888888895 110.8888888888888895334350337
#999 111.0000000000000006 111.0000000000000006453171331
#1000 111.1111111111111118 111.1111111111111117571992324
Source:
include \masm32\MasmBasic\MasmBasic.inc ; download (http://masm32.com/board/index.php?topic=94.0)
SetGlobals MyR10:REAL10, Add01:REAL10=0.111111111111111111111, buffer[100]:BYTE
Init
Dll "\Masm32\bin\MingwPrintf32.dll" ; adjust path if necessary
Declare void __mingw_sprintf, C:? ; ccall, vararg
xor ecx, ecx
Print "digits", Tb$, " 12345678901234567890", Tb$, " 12345678901234567890"
.Repeat
fld MyR10 ; load variable on FPU
fld Add01 ; load modifier
fadd ; modify
fstp MyR10 ; store back to memory
.if ecx<11 || ecx>=85 && ecx<100 || ecx>=995
__mingw_sprintf(addr buffer, "%1.25Lf", MyR10)
Print Str$("\n#%i\t", ecx+1), Str$(MyR10), Tb$, addr buffer
.elseif ecx==11 || ecx==100
Print ; insert a blank line
.endif
inc ecx
.Until ecx>=1000
Inkey CrLf$, "--- hit any key ---"
Exit
end start
Source & exe attached, requires the latest MB version of 7 Jan.
Yeah, you have to modify those absolute paths to point to your lib / lib\gcc\x.x.x\ directories if you want to create the dll. It was just about under the forum size limit without including the actual .o files (one which is 5MB by itself). The VC linker command line used is at the top of printf.c.
Anyhoo, I managed to compile it from source which has slightly minimized the size of the dll. I've attached what I ended up with. A static library and the dll are in the lib* directories. To build it GCC needs to be in your path, and you'll have to point the include and lib variables in source_code\build.cmd to point to your various MinGW include and lib dirs. It'll build 64 and 32-bit, optimized and debug versions of the code, provided you have the 64-bit MinGW and both sets of libs. I don't know what'll happen otherwise.
There's tons of warnings about -fpic not being necessary, primarily because I couldn't be bothered to figure out what it was needed for and what it wasn't. The static lib works with MinGW and VC2008 and above, it might not be compatible with any other CRT like Pelles due to the differences in FILE struct size. The dll should be usable by anything that can link to dlls.
@adeyblue: Works like a charm, see reply #28 (http://masm32.com/board/index.php?topic=3915.msg41394#msg41394) (with old and latest DLL alike) :t
Hi adeyblue,
special thanks to you. :t You've done a good job. I think a lot of our forum members are very grateful.
Gunther