goprotobuf: Write C++-compatible ±Inf and NaN floats in text format.

R=r
CC=golang-dev
https://codereview.appspot.com/10113046
diff --git a/proto/testdata/test.proto b/proto/testdata/test.proto
index 890efde..73e5436 100644
--- a/proto/testdata/test.proto
+++ b/proto/testdata/test.proto
@@ -351,3 +351,7 @@
     optional int32 y = 3;
   }
 }
+
+message FloatingPoint {
+  required double f = 1;
+}
diff --git a/proto/text.go b/proto/text.go
index 8c39b8a..ff8a0bb 100644
--- a/proto/text.go
+++ b/proto/text.go
@@ -39,6 +39,7 @@
 	"fmt"
 	"io"
 	"log"
+	"math"
 	"os"
 	"reflect"
 	"sort"
@@ -55,6 +56,9 @@
 	backslashT      = []byte{'\\', 't'}
 	backslashDQ     = []byte{'\\', '"'}
 	backslashBS     = []byte{'\\', '\\'}
+	posInf          = []byte("inf")
+	negInf          = []byte("-inf")
+	nan             = []byte("nan")
 )
 
 type writer interface {
@@ -292,8 +296,27 @@
 func writeAny(w *textWriter, v reflect.Value, props *Properties) error {
 	v = reflect.Indirect(v)
 
+	// Floats have special cases.
+	if v.Kind() == reflect.Float32 || v.Kind() == reflect.Float64 {
+		x := v.Float()
+		var b []byte
+		switch {
+		case math.IsInf(x, 1):
+			b = posInf
+		case math.IsInf(x, -1):
+			b = negInf
+		case math.IsNaN(x):
+			b = nan
+		}
+		if b != nil {
+			_, err := w.Write(b)
+			return err
+		}
+		// Other values are handled below.
+	}
+
 	// We don't attempt to serialise every possible value type; only those
-	// that can occur in protocol buffers, plus a few extra that were easy.
+	// that can occur in protocol buffers.
 	switch v.Kind() {
 	case reflect.Slice:
 		// Should only be a []byte; repeated fields are handled in writeStruct.
diff --git a/proto/text_test.go b/proto/text_test.go
index e3bb791..f5d0574 100644
--- a/proto/text_test.go
+++ b/proto/text_test.go
@@ -35,6 +35,7 @@
 	"bytes"
 	"errors"
 	"io/ioutil"
+	"math"
 	"strings"
 	"testing"
 
@@ -333,3 +334,24 @@
 		}
 	}
 }
+
+func TestFloats(t *testing.T) {
+	tests := []struct {
+		f    float64
+		want string
+	}{
+		{0, "0"},
+		{4.7, "4.7"},
+		{math.Inf(1), "inf"},
+		{math.Inf(-1), "-inf"},
+		{math.NaN(), "nan"},
+	}
+	for _, test := range tests {
+		msg := &pb.FloatingPoint{F: &test.f}
+		got := strings.TrimSpace(msg.String())
+		want := `f:` + test.want
+		if got != want {
+			t.Errorf("f=%f: got %q, want %q", test.f, got, want)
+		}
+	}
+}