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