#define __USE_MINGW_ANSI_STDIO 1
#include <stdio.h>
#include <math.h>
void qbsprintf (char *ResultString, char *format, long double *x)
{
sprintf(ResultString, format, *x);
}
int qbsscanf (long double *result, char *InputString)
{
return sscanf(InputString, "%Lf", result);
}
long double qbdiv(long double *x, long double *y)
{
return (*x)/(*y);
}
long double qbdivf(long double *x, long double *y)
{
long double z;
short oldcw, extended = 0x37f;
//set fpu control word to extended precision
asm (
"fstcw %[oldcw] \n"
"fldcw %[extended] \n"
:[oldcw]"=m"(oldcw),[extended]"=m"(extended)
:
:
);
z=(*x)/(*y);
//restore fpu control word
asm (
"fldcw %[oldcw] \n"
:[oldcw]"=m"(oldcw)
:
:
);
return z;
}
demo.bas' c_include.h needs to be in QB64 folder
$CONSOLE:ONLY
_DEST _CONSOLE
DECLARE CUSTOMTYPE LIBRARY ".\qb_include"
SUB qbsprintf (res AS STRING, frmt AS STRING, x AS _FLOAT)
FUNCTION qbsscanf& (f AS _FLOAT, sfloat AS STRING)
FUNCTION qbdiv## (x AS _FLOAT, y AS _FLOAT)
FUNCTION qbdivf## (x AS _FLOAT, y AS _FLOAT)
END DECLARE
DIM x AS _FLOAT
DIM y AS _FLOAT
DIM z AS _FLOAT
DIM SHARED strout AS STRING
x = 5##
y = x / 9##
PRINT "QB64 division "; print_strout(y)
s$ = "5L"
k& = qbsscanf(x, s$)
k& = qbsscanf(y, "9L")
z = qbdiv(x, y)
PRINT "C division low prec "; print_strout(z)
z = qbdivf(x, y)
PRINT "C division ext prec "; print_strout(z)
FUNCTION print_strout$ (x AS _FLOAT)
strout = " "
CALL qbsprintf(strout, " %.20Lg", x)
print_strout$ = strout
END FUNCTION
outputQB64 division 0.55555555555555558023
C division low prec 0.55555555555555558023
C division ext prec 0.55555555555555555556
I included the sscanf but it's probably not needed
At the end of the day, QB64 just translates BAS code to C code. mingw is the compiler we use to then compile that C code to an EXE.
GENERALLY SPEAKING: *
G++ 32-bit uses the 80-bit precision X87 FPU math processors by default.
G++ 64-bit uses 64-bit precision SSE2 math processors by default, as they’re much faster.
That gives us a noticeable difference in results as the precision limits are different. Usually this is a difference of something like 0.000000002 or such, and it’s hardly noticeable — BUT when rounding it can cause a huge change in values.
INT(15.9999999999999999) = 15
INT(16.0000000000000001) = 16
Only .0000000000000002 difference in those values, but their INT value is quite different.
* You notice I mentioned GENERALLY SPEAKING above?? That’s because various machines and OSes have different architecture that they default to. From what I’ve heard, Mac OS X and up all use 64-bit SSE2 processing — even on 32-bit Macs...
If one wants to alter these type of default behaviors, they usually just need to set the proper flags to tell the compiler ”I want the slower, 80-bit FPU math, rather than the faster 64-bit SSE2 math”.
A few quick wiki links to help:
https://en.wikipedia.org/wiki/X87
https://en.wikipedia.org/wiki/SSE2
Quote
Differences between x87 FPU and SSE2
FPU (x87) instructions provide higher precision by calculating intermediate results with 80 bits of precision, by default, to minimise roundoff error in numerically unstable algorithms (see IEEE 754 design rationale and references therein). However, the x87 FPU is a scalar unit only whereas SSE2 can process a small vector of operands in parallel.
If codes designed for x87 are ported to the lower precision double precision SSE2 floating point, certain combinations of math operations or input datasets can result in measurable numerical deviation, which can be an issue in reproducible scientific computations, e.g. if the calculation results must be compared against results generated from a different machine architecture. A related issue is that, historically, language standards and compilers had been inconsistent in their handling of the x87 80-bit registers implementing double extended precision variables, compared with the double and single precision formats implemented in SSE2: the rounding of extended precision intermediate values to double precision variables was not fully defined and was dependent on implementation details such as when registers were spilled to memory.
look into the source itself and see if you can correct the issue as it occurs for your system. The fix for you may be as simple as just using a different set of compiler options with your system, and you can find a list of those switches here: https://linux.die.net/man/1/g++
Some of the flags which seem like they may be relevant to your issue, seems to me to possibly be:
-mabi=ibmlongdouble: Change the current ABI to use IBM extended precision long double. This is a PowerPC 32-bit SYSV ABI option.
-mabi=ieeelongdouble: Change the current ABI to use IEEE extended precision long double. This is a PowerPC 32-bit Linux ABI option.
-mlong-double-64//-mlong-double-128: These switches control the size of "long double" type. A size of 64bit makes the "long double" type equivalent to the "double" type. This is the default.
-mhard-quad-float: Generate output containing quad-word (long double) floating point instructions.
-msoft-quad-float: Generate output containing library calls for quad-word (long double) floating point instructions.
-mno-align-double: Control whether GCC aligns "double", "long double", and "long long" variables on a two word boundary or a one word boundary. Aligning "double" variables on a two word boundary will produce code that runs somewhat faster on a Pentium at the expense of more memory.
On x86-64, -malign-double is enabled by default.
-mfpmath=387: Use the standard 387 floating point coprocessor present majority of chips and emulated otherwise. Code compiled with this option will run almost everywhere. The temporary results are computed in 80bit precision instead of precision specified by the type resulting in slightly different results compared to most of other chips.
@SMcNeill
I am not going to to read all the irrelevant information you posted, my code is short and simple and the output speaks for itself.
somewhere in a unknown QB64 library function the FPU is set to double, perhaps for performance
void set_dpfpu() { unsigned int mode = 0x37F; asm ("fldcw %0" : : "m" (*&mode));}
void set_qbfpu() { unsigned int mode = 0x27F; asm ("fldcw %0" : : "m" (*&mode));}