1package null
2
3import (
4	"database/sql"
5	"encoding/json"
6	"fmt"
7	"math"
8	"reflect"
9	"strconv"
10)
11
12// Float is a nullable float64.
13// It does not consider zero values to be null.
14// It will decode to null, not zero, if null.
15type Float struct {
16	sql.NullFloat64
17}
18
19// NewFloat creates a new Float
20func NewFloat(f float64, valid bool) Float {
21	return Float{
22		NullFloat64: sql.NullFloat64{
23			Float64: f,
24			Valid:   valid,
25		},
26	}
27}
28
29// FloatFrom creates a new Float that will always be valid.
30func FloatFrom(f float64) Float {
31	return NewFloat(f, true)
32}
33
34// FloatFromPtr creates a new Float that be null if f is nil.
35func FloatFromPtr(f *float64) Float {
36	if f == nil {
37		return NewFloat(0, false)
38	}
39	return NewFloat(*f, true)
40}
41
42// ValueOrZero returns the inner value if valid, otherwise zero.
43func (f Float) ValueOrZero() float64 {
44	if !f.Valid {
45		return 0
46	}
47	return f.Float64
48}
49
50// UnmarshalJSON implements json.Unmarshaler.
51// It supports number and null input.
52// 0 will not be considered a null Float.
53// It also supports unmarshalling a sql.NullFloat64.
54func (f *Float) UnmarshalJSON(data []byte) error {
55	var err error
56	var v interface{}
57	if err = json.Unmarshal(data, &v); err != nil {
58		return err
59	}
60	switch x := v.(type) {
61	case float64:
62		f.Float64 = float64(x)
63	case string:
64		str := string(x)
65		if len(str) == 0 {
66			f.Valid = false
67			return nil
68		}
69		f.Float64, err = strconv.ParseFloat(str, 64)
70	case map[string]interface{}:
71		err = json.Unmarshal(data, &f.NullFloat64)
72	case nil:
73		f.Valid = false
74		return nil
75	default:
76		err = fmt.Errorf("json: cannot unmarshal %v into Go value of type null.Float", reflect.TypeOf(v).Name())
77	}
78	f.Valid = err == nil
79	return err
80}
81
82// UnmarshalText implements encoding.TextUnmarshaler.
83// It will unmarshal to a null Float if the input is a blank or not an integer.
84// It will return an error if the input is not an integer, blank, or "null".
85func (f *Float) UnmarshalText(text []byte) error {
86	str := string(text)
87	if str == "" || str == "null" {
88		f.Valid = false
89		return nil
90	}
91	var err error
92	f.Float64, err = strconv.ParseFloat(string(text), 64)
93	f.Valid = err == nil
94	return err
95}
96
97// MarshalJSON implements json.Marshaler.
98// It will encode null if this Float is null.
99func (f Float) MarshalJSON() ([]byte, error) {
100	if !f.Valid {
101		return []byte("null"), nil
102	}
103	if math.IsInf(f.Float64, 0) || math.IsNaN(f.Float64) {
104		return nil, &json.UnsupportedValueError{
105			Value: reflect.ValueOf(f.Float64),
106			Str:   strconv.FormatFloat(f.Float64, 'g', -1, 64),
107		}
108	}
109	return []byte(strconv.FormatFloat(f.Float64, 'f', -1, 64)), nil
110}
111
112// MarshalText implements encoding.TextMarshaler.
113// It will encode a blank string if this Float is null.
114func (f Float) MarshalText() ([]byte, error) {
115	if !f.Valid {
116		return []byte{}, nil
117	}
118	return []byte(strconv.FormatFloat(f.Float64, 'f', -1, 64)), nil
119}
120
121// SetValid changes this Float's value and also sets it to be non-null.
122func (f *Float) SetValid(n float64) {
123	f.Float64 = n
124	f.Valid = true
125}
126
127// Ptr returns a pointer to this Float's value, or a nil pointer if this Float is null.
128func (f Float) Ptr() *float64 {
129	if !f.Valid {
130		return nil
131	}
132	return &f.Float64
133}
134
135// IsZero returns true for invalid Floats, for future omitempty support (Go 1.4?)
136// A non-null Float with a 0 value will not be considered zero.
137func (f Float) IsZero() bool {
138	return !f.Valid
139}
140
141// Equal returns true if both floats have the same value or are both null.
142// Warning: calculations using floating point numbers can result in different ways
143// the numbers are stored in memory. Therefore, this function is not suitable to
144// compare the result of a calculation. Use this method only to check if the value
145// has changed in comparison to some previous value.
146func (f Float) Equal(other Float) bool {
147	return f.Valid == other.Valid && (!f.Valid || f.Float64 == other.Float64)
148}
149