This code:
#include <math.h> #include <stdio.h> #include <string.h> int main() { long double ld = nanl("nan"); __mingw_printf("nan (%d): %Lf\n", isnan(ld), ld); unsigned char data[10]={0x5b, 0x01, 0x04, 0x5e, 0x85, 0x00, 0x00, 0x00, 0xd8, 0x59}; memcpy(&ld, data, 10); __mingw_printf("nan (%d): %Lf\n", isnan(ld), ld); }
Will output the following on Windows 8.1 x64 with g++ (x86_64-win32-seh-rev2, Built by MinGW-W64 project) 6.3.0:
nan (1): nan nan (1): 51121152321801233769528628351853187448384447522053840083788534546827429704643290381340679219510392362325477918202365924663653936157016779794625974445014197143562076732008008738128391263363163574965197975470359672288313174823442406254048743002805452006157937878331142146798072696855831972926249999727974322449691877753828930695850548966676286079757955984128191916481949201177743973903996572963765928618534081793280129105149856531260982551553469556429687623577078046545852198566211939155677028446451165609819075837450164249776102845298460469771918289548752998338223854607227934434765179856884186807766927334957254748394233630664914775000455646748666717708093934285269156086702020354011540591392665589375067807455867173656994975148894173206984974769181739207306862381317984266053473189056331126277978170416496979674173995459863769696678139373469071902631219029009882566470150137655735124177599243342172252211543365526650540919950000331153682021554718999864489566506339904693091924474536686798677359626831249371422914336837875642156815196149795381551612204397230483764946192534114535346234814185380840663925616737129762828956809594806005021218926647104971253221663691633803548451583289405163859070274525946904733996109568233540983324711005688985472233093988319080327085238864951989022529775137959073989229879976005224619428151507207204127577684739182214549342293405410680249028464282743001152759784716025069416795757606758511947646024601325845023201020928965906189624416406030848379505691504516615002554935359779127043369953207671477905398294201081775159338488386294932914644247061475060610844251702566871004568191800019003391839796982595646233069513712001461158765658211174522442116616289845700046396282353720595469749930911408197188628124217336603684988877899113364285268336103879082322140847309239491610769009092472379988705831151518195226501624576728449337637413597709101054377371751575601666492702420321446551893642494512728924667549191793875921344186506961309343308327172881506107392.000000
It works fine on linux (ideone uses gcc 6.3, but I also tested on lbunutu with gcc 6.2):
I'll try to find the relevant source file but I have no clue where to start...
Okay, the solution is to either fix
__fpclassifyl
(x64 uses a bad software implementation) or just use__builtin_fpclassify
:Which prints:
I have confirmed this fix, by replacing the implementations of
__fpclassifyl
with__builtin_fpclassify
everything works correctly, but I don't know how to properly fix this (there are various direct uses of__fpclassifyl
inmingw_pformat.c
so my solution would be to replace those withfpclassify
and then replace the macro forfpclassify
with a call to__builtin_fpclassify
).The number in question is an invalid operand, because its exponent is neither all-zeroes nor all-ones and its bit 63 is zero.
Hm okay, so is
nan
an incorrect classification or is that what should be looked at for the software implementation?Okay I found and the bug in the software implementation (it also appears to exist in glibc by the way). I still believe
__builtin_fpclassify
should be called in thefpclassify
macro and the__fpclassifyX
family of functions should never be used directly.This is the code for
math.h
(I'm pretty sure the other software implementations are also broken, forlong double
but also the other floating point types):If we weren't emulating it, a hardware solution is available here (the code is purely in Intel syntax and requires a little modification if you are using AT&T syntax).
Nevertheless,
fxam
could still say the operand is unsupported by setting C3, C2 and C0 to all zeroes, which is impossible on 32-bit, single precision or 64-bit, double precision floating point numbers, both of which don't have the extra bit in their mantissas.Also have a look at the C99 standard (ISO/IEC WG14 N1256):
The set of standard number classification macros does not contain a value for invalid operands, I am looking forward to an implementation-defined one for it, as the 80-bit, extended precision
long double
has already been implementation-defined.Last edit: LIU Hao 2017-04-25
The builtin classifier for gcc is very clear: that value is nan. The 32 bit
inline assembly is also very clear: nan. These macros are based on the
hardware implementation so following the hardware is definitely correct I
think. Another argument for using what the hardware tells you is that if
you don't you get the kind of garbage output I got because the value is
invalid but you try to do something with it anyway.
Last edit: Duncan Ogilvie 2017-04-25
To clarify: the hardware implementation I'm talking about is also in that
function for 32 bit.
Last edit: Duncan Ogilvie 2017-04-25
No that value is not a NaN.
A NaN can be copied without generating an exception, no matter whether it is a signaling NaN (SNaN) or a quiet NaN (QNaN). It is just that if you don't do arithmetic operations on it, you don't get an exception. If you do arithmetic operations on a QNaN, you get a QNaN. If you do arithmetic operations on a SNaN you get an #IA exception (the exception can be masked, but it is generated unconditionally).
This invalid number, on the other hand, can't be copied. you can
FST
it, but x87 will not actually store it but generate an #IA instead. This can be observed by unmasking the LSB of the x87 control word usingFSTCW
beforeFST
.The hardware isn't lying either.
FXAM
tells you that the number is unsupported.FXAM
doesn't say the number is a NaN, in which case bytewise and'ing the x87 status word with0x4500
would yield0x0100
instead of0x0000
.FST
does not generate an #IA exception for a SNaN in 80-bit format, but it does generate an exception for a SNaN if the memory location is in 64-bit or 32-bit format.Hm so then there is a bug in GCC... Or because it's implementation defined they chose to classify this as NAN :-D
http://x86.renejeschke.de/html/file_module_x86_id_125.html
I'm currently rather unclear about how this table relates to the values of FP_NAN, etc. but I can add FP_UNSUPPORTED. Could you check if my current implementation of that wikipedia table is correct?
Keep an eye on https://gcc.gnu.org/ml/gcc-help/2017-04/msg00101.html.
BTW, why the fuck do you want to
fuck.lh
? :<^^^ This is the number in question and it should be
FP_UNSUPPORTED
in my opinion.Sorry about the swearing I forgot to clean that up lol. Also I agree, just missed that one. Does this look correct? I'm not particularly sure about the bit manipulation stuff...
Just get some numbers and test it. :>
Why don't you just declare the mantissa as a
uint64_t
? You could just right-shift it by 62 andswitch
the result, so if bit 63 and 62 are both zeroes you get 0, and if both are ones you get 3, and so on.This code:
Shows (on GCC 6.3):
And on Apple LLVM version 8.1.0 (clang-802.0.38):
So clang has a similar issue apparently...
I did some more testing where I take all possible
sign_exponent
andhigh
values that influence the classification according to wikipedia and compare my implementation (with#define FP_UNSUPPORTED FP_NAN
) with__builtin_fpclassify
and it appears that my implementation is correct (or at least matches the builtin classification): https://ideone.com/RB1sZmI will do some more testing also on the 32 bit version of GCC and mingw-w64 later to see if the 32 bit builtin/software implementation is the same. And yes this code is dumb but it works and tests all the cases so I'm fine with it.
Okay that code is broken I will try again...
Here is my final code, the function
fxclassify
is a candidate to replace___fxclassifyl
on x64 (it is correct in the sense that the currently unimplementationFP_UNSUPPORTED
flag is returned asFP_NAN
).The function
hwclassify
is inline assembly and this works, I added support forFP_UNSUPPORTED
(and if you define unsupported as nan it works).The macro
btclassify
calls__builtin_fpclassify
and should not be used because it is incorrect (admittedly only in certain edge cases where you have a denormal zero value).The function
myclassify
is an implementation of the Wikipedia article and has been tested against all possible representations of 10-byte floating point values that can affect the status of the number. This code is implemented to be as readable as possible and to reflect the article, not to be efficient.The testing mentioned above is implemented by iterating over all possible
exponent
values (sign is set to 0 since it doesn't affect the classification) and then over bits 63,62,61 which allow you to represent all cases. The bits 60-0 have to be set to 0 for this to work! (bit 61/62 fulfil the conditions zero/nonzero of the whole range). Themyclassify
function also sets bits 0-11 (one bit for every case) of thevisited
global variable which allow you to test if all cases at least happened once.Because I don't have any experience with standard libraries I will refrain from a submitting a patch, but to fix the original issue you just have to fake
fxclassify
and adapt it in all__fpclassifyl
implementations. For my use case I callmyclassify
before actually printing the number to support theFP_UNSUPPORTED
so my work is done.