Implement fmt.Stringer for atomic types (#76)

Add safe `String()` methods for atomic types that replicate the same
behavior as `fmt.Sprintf("%v", x.Load())` without the allocations.

As with json.Marshaler/Unmarshaler, we've omitted the `atomic.Value`
type for now.

Resolves #50
diff --git a/atomic.go b/atomic.go
index b7b116c..8bdf5d7 100644
--- a/atomic.go
+++ b/atomic.go
@@ -25,6 +25,7 @@
 import (
 	"encoding/json"
 	"math"
+	"strconv"
 	"sync/atomic"
 	"time"
 )
@@ -97,6 +98,11 @@
 	return nil
 }
 
+// String encodes the wrapped value as a string.
+func (b *Bool) String() string {
+	return strconv.FormatBool(b.Load())
+}
+
 // Float64 is an atomic wrapper around float64.
 type Float64 struct {
 	nocmp // disallow non-atomic comparison
@@ -155,6 +161,12 @@
 	return nil
 }
 
+// String encodes the wrapped value as a string.
+func (f *Float64) String() string {
+	// 'g' is the behavior for floats with %v.
+	return strconv.FormatFloat(f.Load(), 'g', -1, 64)
+}
+
 // Duration is an atomic wrapper around time.Duration
 // https://godoc.org/time#Duration
 type Duration struct {
@@ -213,6 +225,11 @@
 	return nil
 }
 
+// String encodes the wrapped value as a string.
+func (d *Duration) String() string {
+	return d.Load().String()
+}
+
 // Value shadows the type of the same name from sync/atomic
 // https://godoc.org/sync/atomic#Value
 type Value struct {
diff --git a/atomic_test.go b/atomic_test.go
index b57d197..cb3768e 100644
--- a/atomic_test.go
+++ b/atomic_test.go
@@ -71,6 +71,19 @@
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		t.Run("true", func(t *testing.T) {
+			assert.Equal(t, "true", NewBool(true).String(),
+				"String() returned an unexpected value.")
+		})
+
+		t.Run("false", func(t *testing.T) {
+			var b Bool
+			assert.Equal(t, "false", b.String(),
+				"String() returned an unexpected value.")
+		})
+	})
 }
 
 func TestFloat64(t *testing.T) {
@@ -106,6 +119,11 @@
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		assert.Equal(t, "42.5", NewFloat64(42.5).String(),
+			"String() returned an unexpected value.")
+	})
 }
 
 func TestDuration(t *testing.T) {
@@ -143,6 +161,11 @@
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		assert.Equal(t, "42s", NewDuration(42*time.Second).String(),
+			"String() returned an unexpected value.")
+	})
 }
 
 func TestValue(t *testing.T) {
diff --git a/int32.go b/int32.go
index c3b7d4f..357f178 100644
--- a/int32.go
+++ b/int32.go
@@ -24,6 +24,7 @@
 
 import (
 	"encoding/json"
+	"strconv"
 	"sync/atomic"
 )
 
@@ -93,3 +94,9 @@
 	i.Store(v)
 	return nil
 }
+
+// String encodes the wrapped value as a string.
+func (i *Int32) String() string {
+	v := i.Load()
+	return strconv.FormatInt(int64(v), 10)
+}
diff --git a/int32_test.go b/int32_test.go
index 19c9ef7..9992251 100644
--- a/int32_test.go
+++ b/int32_test.go
@@ -22,8 +22,10 @@
 
 import (
 	"encoding/json"
+	"math"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -63,4 +65,18 @@
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		t.Run("positive", func(t *testing.T) {
+			atom := NewInt32(math.MaxInt32)
+			assert.Equal(t, "2147483647", atom.String(),
+				"String() returned an unexpected value.")
+		})
+
+		t.Run("negative", func(t *testing.T) {
+			atom := NewInt32(math.MinInt32)
+			assert.Equal(t, "-2147483648", atom.String(),
+				"String() returned an unexpected value.")
+		})
+	})
 }
diff --git a/int64.go b/int64.go
index 180dbbc..8448018 100644
--- a/int64.go
+++ b/int64.go
@@ -24,6 +24,7 @@
 
 import (
 	"encoding/json"
+	"strconv"
 	"sync/atomic"
 )
 
@@ -93,3 +94,9 @@
 	i.Store(v)
 	return nil
 }
+
+// String encodes the wrapped value as a string.
+func (i *Int64) String() string {
+	v := i.Load()
+	return strconv.FormatInt(int64(v), 10)
+}
diff --git a/int64_test.go b/int64_test.go
index b012196..ed5a104 100644
--- a/int64_test.go
+++ b/int64_test.go
@@ -22,8 +22,10 @@
 
 import (
 	"encoding/json"
+	"math"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -63,4 +65,18 @@
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		t.Run("positive", func(t *testing.T) {
+			atom := NewInt64(math.MaxInt64)
+			assert.Equal(t, "9223372036854775807", atom.String(),
+				"String() returned an unexpected value.")
+		})
+
+		t.Run("negative", func(t *testing.T) {
+			atom := NewInt64(math.MinInt64)
+			assert.Equal(t, "-9223372036854775808", atom.String(),
+				"String() returned an unexpected value.")
+		})
+	})
 }
diff --git a/internal/gen-atomicint/main.go b/internal/gen-atomicint/main.go
index 3bd4e92..4ffcb94 100644
--- a/internal/gen-atomicint/main.go
+++ b/internal/gen-atomicint/main.go
@@ -129,6 +129,7 @@
 
 import (
 	"encoding/json"
+	"strconv"
 	"sync/atomic"
 )
 
@@ -204,4 +205,14 @@
 	i.Store(v)
 	return nil
 }
+
+// String encodes the wrapped value as a string.
+func (i *{{ .Name }}) String() string {
+	v := i.Load()
+	{{ if .Unsigned -}}
+		return strconv.FormatUint(uint64(v), 10)
+	{{- else -}}
+		return strconv.FormatInt(int64(v), 10)
+	{{- end }}
+}
 `))
diff --git a/string_ext.go b/string_ext.go
index c0357e3..a346358 100644
--- a/string_ext.go
+++ b/string_ext.go
@@ -20,6 +20,11 @@
 
 package atomic
 
+// String returns the wrapped value.
+func (s *String) String() string {
+	return s.Load()
+}
+
 // MarshalText encodes the wrapped string into a textual form.
 //
 // This makes it encodable as JSON, YAML, XML, and more.
diff --git a/string_test.go b/string_test.go
index 8e1fcd2..a730f5f 100644
--- a/string_test.go
+++ b/string_test.go
@@ -25,6 +25,7 @@
 	"encoding/xml"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -75,4 +76,10 @@
 		require.NoError(t, err, "xml.Unmarshal errored unexpectedly.")
 		require.Equal(t, "bar", atom.Load(), "xml.Unmarshal didn't set the correct value.")
 	})
+
+	t.Run("String", func(t *testing.T) {
+		atom := NewString("foo")
+		assert.Equal(t, "foo", atom.String(),
+			"String() returned an unexpected value.")
+	})
 }
diff --git a/uint32.go b/uint32.go
index 3e61026..3fbb696 100644
--- a/uint32.go
+++ b/uint32.go
@@ -24,6 +24,7 @@
 
 import (
 	"encoding/json"
+	"strconv"
 	"sync/atomic"
 )
 
@@ -93,3 +94,9 @@
 	i.Store(v)
 	return nil
 }
+
+// String encodes the wrapped value as a string.
+func (i *Uint32) String() string {
+	v := i.Load()
+	return strconv.FormatUint(uint64(v), 10)
+}
diff --git a/uint32_test.go b/uint32_test.go
index 4149a47..d251730 100644
--- a/uint32_test.go
+++ b/uint32_test.go
@@ -22,8 +22,10 @@
 
 import (
 	"encoding/json"
+	"math"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -63,4 +65,12 @@
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		// Use an integer with the signed bit set. If we're converting
+		// incorrectly, we'll get a negative value here.
+		atom := NewUint32(math.MaxUint32)
+		assert.Equal(t, "4294967295", atom.String(),
+			"String() returned an unexpected value.")
+	})
 }
diff --git a/uint64.go b/uint64.go
index 1ed01a2..7cec96b 100644
--- a/uint64.go
+++ b/uint64.go
@@ -24,6 +24,7 @@
 
 import (
 	"encoding/json"
+	"strconv"
 	"sync/atomic"
 )
 
@@ -93,3 +94,9 @@
 	i.Store(v)
 	return nil
 }
+
+// String encodes the wrapped value as a string.
+func (i *Uint64) String() string {
+	v := i.Load()
+	return strconv.FormatUint(uint64(v), 10)
+}
diff --git a/uint64_test.go b/uint64_test.go
index 7ff370b..9b8f399 100644
--- a/uint64_test.go
+++ b/uint64_test.go
@@ -22,8 +22,10 @@
 
 import (
 	"encoding/json"
+	"math"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -63,4 +65,12 @@
 		assertErrorJSONUnmarshalType(t, err,
 			"json.Unmarshal failed with unexpected error %v, want UnmarshalTypeError.", err)
 	})
+
+	t.Run("String", func(t *testing.T) {
+		// Use an integer with the signed bit set. If we're converting
+		// incorrectly, we'll get a negative value here.
+		atom := NewUint64(math.MaxUint64)
+		assert.Equal(t, "18446744073709551615", atom.String(),
+			"String() returned an unexpected value.")
+	})
 }