diff --git a/tccgen.c b/tccgen.c
index 1c9eb834..dabc897f 100644
--- a/tccgen.c
+++ b/tccgen.c
@@ -5880,19 +5880,47 @@ ST_FUNC void unary(void)
         unary();
         t = vtop->type.t & VT_BTYPE;
 	if (is_float(t)) {
-            /* In IEEE negate(x) isn't subtract(0,x), but rather
-	       subtract(-0, x).  */
-	    vpush(&vtop->type);
-	    if (t == VT_FLOAT)
-	        vtop->c.f = -1.0 * 0.0;
-	    else if (t == VT_DOUBLE)
-	        vtop->c.d = -1.0 * 0.0;
-            else
-	        vtop->c.ld = -1.0 * 0.0;
-	} else
-	    vpushi(0);
-	vswap();
-	gen_op('-');
+            if ((vtop->r & VT_VALMASK) == VT_CONST) {
+                /* This is what gen_opif would do if we had a NEG operation.  */
+                if (t == VT_FLOAT)
+                  vtop->c.f = -vtop->c.f;
+                else if (t == VT_DOUBLE)
+                  vtop->c.d = -vtop->c.d;
+                else
+                  vtop->c.ld = -vtop->c.ld;
+            } else {
+                /* In IEEE negate(x) isn't subtract(0,x).  Without NaNs it's
+                   subtract(-0, x), but with them it's really a sign flip
+                   operation.  We implement this with bit manipulation and have
+                   to do some type reinterpretation for this, which TCC can do
+                   only via memory.  */
+                int align, size = type_size(&vtop->type, &align);
+                save_reg(gv(RC_TYPE(t)));
+                vdup();
+                gaddrof();
+                vtop->type = char_pointer_type;
+                /* Byte of sign bit.  For big endian, this would have to
+                   add zero always.  */
+#if defined(TCC_TARGET_X86_64) || defined(TCC_TARGET_I386)
+                /* sizeof long double is 12 or 16 here, but it's
+                   really the 80bit extended float format.  */
+                if (t == VT_LDOUBLE)
+                  size = 10;
+#endif
+                vpushi(size - 1);
+                gen_op('+');
+                indir();
+                vdup();
+                vpushi(0x80); /* flip sign */
+                gen_op('^');
+                vstore();
+                vpop();
+            }
+	} else {
+            vpushi(0);
+            vswap();
+            gen_op('-');
+        }
         break;
     case TOK_LAND:
         if (!gnu_ext)
diff --git a/tests/tcctest.c b/tests/tcctest.c
index d0ebe3c5..6c6753f7 100644
--- a/tests/tcctest.c
+++ b/tests/tcctest.c
@@ -2227,6 +2227,12 @@ void prefix ## signed_zeros(void) \
   else\
     printf ("x != -y; this is wrong!\n");\
 }\
+void prefix ## nan(void)\
+{\
+    type nan = 0.0/0.0;\
+    type nnan = -nan; \
+    printf("nantest: " fmt " " fmt "\n", nan, nnan);\
+}\
 void prefix ## test(void)\
 {\
     printf("testing '%s'\n", #typename);\
@@ -2237,6 +2243,7 @@ void prefix ## test(void)\
     prefix ## fcast(-2334.6);\
     prefix ## call();\
     prefix ## signed_zeros();\
+    prefix ## nan();\
 }
 
 FTEST(f, float, float, "%f")