compiler: avoid negative zero in float constants

Check for negative numbers with very small magnitudes that will round
to negative zero, and force them to positive zero instead.

This implements the spec clarification in https://golang.org/cl/14727.
The test is in https://golang.org/cl/91895.

Fixes golang/go#12621

Change-Id: Iaf8221d40b8afb4318848990800d2ca2e43f0f8b
Reviewed-on: https://go-review.googlesource.com/91915
Reviewed-by: Than McIntosh <thanm@google.com>
diff --git a/go/expressions.cc b/go/expressions.cc
index 8555c05..03f8c68 100644
--- a/go/expressions.cc
+++ b/go/expressions.cc
@@ -16158,10 +16158,16 @@
   this->clear();
   this->classification_ = NC_FLOAT;
   this->type_ = type;
+
   // Numeric constants do not have negative zero values, so remove
   // them here.  They also don't have infinity or NaN values, but we
   // should never see them here.
-  if (mpfr_zero_p(val))
+  int bits = 0;
+  if (type != NULL
+      && type->float_type() != NULL
+      && !type->float_type()->is_abstract())
+    bits = type->float_type()->bits();
+  if (Numeric_constant::is_float_zero(val, bits))
     mpfr_init_set_ui(this->u_.float_val, 0, GMP_RNDN);
   else
     mpfr_init_set(this->u_.float_val, val, GMP_RNDN);
@@ -16175,8 +16181,50 @@
   this->clear();
   this->classification_ = NC_COMPLEX;
   this->type_ = type;
+
+  // Avoid negative zero as in set_float.
+  int bits = 0;
+  if (type != NULL
+      && type->complex_type() != NULL
+      && !type->complex_type()->is_abstract())
+    bits = type->complex_type()->bits() / 2;
+
+  mpfr_t real;
+  mpfr_init_set(real, mpc_realref(val), GMP_RNDN);
+  if (Numeric_constant::is_float_zero(real, bits))
+    mpfr_set_ui(real, 0, GMP_RNDN);
+
+  mpfr_t imag;
+  mpfr_init_set(imag, mpc_imagref(val), GMP_RNDN);
+  if (Numeric_constant::is_float_zero(imag, bits))
+    mpfr_set_ui(imag, 0, GMP_RNDN);
+
   mpc_init2(this->u_.complex_val, mpc_precision);
-  mpc_set(this->u_.complex_val, val, MPC_RNDNN);
+  mpc_set_fr_fr(this->u_.complex_val, real, imag, MPC_RNDNN);
+
+  mpfr_clear(real);
+  mpfr_clear(imag);
+}
+
+// Return whether VAL, at a precision of BITS, is zero.  BITS may be
+// zero in which case it is ignored.
+
+bool
+Numeric_constant::is_float_zero(const mpfr_t val, int bits)
+{
+  if (mpfr_zero_p(val))
+    return true;
+  switch (bits)
+    {
+    case 0:
+      return false;
+    case 32:
+      return mpfr_get_flt(val, GMP_RNDN) == 0;
+    case 64:
+      return mpfr_get_d(val, GMP_RNDN) == 0;
+    default:
+      go_unreachable();
+    }
 }
 
 // Get an int value.
diff --git a/go/expressions.h b/go/expressions.h
index d16a284..3acaeb2 100644
--- a/go/expressions.h
+++ b/go/expressions.h
@@ -4220,6 +4220,9 @@
   bool
   check_complex_type(Complex_type*, bool, Location);
 
+  static bool
+  is_float_zero(const mpfr_t, int bits);
+
   // The kinds of constants.
   enum Classification
   {