github.com/richardwilkes/toolbox@v1.121.0/xmath/fixed/f64/f64.go (about) 1 // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved. 2 // 3 // This Source Code Form is subject to the terms of the Mozilla Public 4 // License, version 2.0. If a copy of the MPL was not distributed with 5 // this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 // 7 // This Source Code Form is "Incompatible With Secondary Licenses", as 8 // defined by the Mozilla Public License, version 2.0. 9 10 package f64 11 12 import ( 13 "fmt" 14 "math" 15 "reflect" 16 "strconv" 17 "strings" 18 19 "github.com/richardwilkes/toolbox/errs" 20 "github.com/richardwilkes/toolbox/txt" 21 "github.com/richardwilkes/toolbox/xmath" 22 "github.com/richardwilkes/toolbox/xmath/fixed" 23 "gopkg.in/yaml.v3" 24 ) 25 26 const ( 27 // Max holds the maximum value. 28 Max = math.MaxInt64 29 // Min holds the minimum value. 30 Min = math.MinInt64 31 ) 32 33 // Int holds a fixed-point value. Values are truncated, not rounded. Values can be added and subtracted directly. For 34 // multiplication and division, the provided Mul() and Div() methods should be used. 35 type Int[T fixed.Dx] int64 36 37 // MaxSafeMultiply returns the maximum value that can be safely multiplied without overflow. 38 func MaxSafeMultiply[T fixed.Dx]() Int[T] { 39 return Int[T](Max / Multiplier[T]()) 40 } 41 42 // MaxDecimalDigits returns the maximum number of digits after the decimal that will be used. 43 func MaxDecimalDigits[T fixed.Dx]() int { 44 var t T 45 return t.Places() 46 } 47 48 // Multiplier returns the multiplier used. 49 func Multiplier[T fixed.Dx]() int64 { 50 var t T 51 return t.Multiplier() 52 } 53 54 // From creates a new value. 55 func From[T fixed.Dx, FROM xmath.Numeric](value FROM) Int[T] { 56 return Int[T](value * FROM(Multiplier[T]())) 57 } 58 59 // FromString creates a new value from a string. 60 func FromString[T fixed.Dx](str string) (Int[T], error) { 61 if str == "" { 62 return 0, errs.New("empty string is not valid") 63 } 64 str = strings.ReplaceAll(str, ",", "") 65 if strings.ContainsAny(str, "Ee") { 66 // Given a floating-point value with an exponent, which technically 67 // isn't valid input, but we'll try to convert it anyway. 68 f, err := strconv.ParseFloat(str, 64) 69 if err != nil { 70 return 0, err 71 } 72 return From[T](f), nil 73 } 74 mult := Multiplier[T]() 75 parts := strings.SplitN(str, ".", 2) 76 var value, fraction int64 77 var neg bool 78 var err error 79 switch parts[0] { 80 case "": 81 case "-", "-0": 82 neg = true 83 default: 84 if value, err = strconv.ParseInt(parts[0], 10, 64); err != nil { 85 return 0, errs.Wrap(err) 86 } 87 if value < 0 { 88 neg = true 89 value = -value 90 } 91 value *= mult 92 } 93 if len(parts) > 1 { 94 cutoff := 1 + MaxDecimalDigits[T]() 95 var buffer strings.Builder 96 buffer.WriteString("1") 97 buffer.WriteString(parts[1]) 98 for buffer.Len() < cutoff { 99 buffer.WriteString("0") 100 } 101 frac := buffer.String() 102 if len(frac) > cutoff { 103 frac = frac[:cutoff] 104 } 105 if fraction, err = strconv.ParseInt(frac, 10, 64); err != nil { 106 return 0, errs.Wrap(err) 107 } 108 value += fraction - mult 109 } 110 if neg { 111 value = -value 112 } 113 return Int[T](value), nil 114 } 115 116 // FromStringForced creates a new value from a string. 117 func FromStringForced[T fixed.Dx](str string) Int[T] { 118 f, _ := FromString[T](str) //nolint:errcheck // failure results in 0, which is acceptable here 119 return f 120 } 121 122 // Add adds this value to the passed-in value, returning a new value. Note that this method is only provided to make 123 // text templates easier to use with these objects, since you can just add two Int[T] values together like they were 124 // primitive types. 125 func (f Int[T]) Add(value Int[T]) Int[T] { 126 return f + value 127 } 128 129 // Sub subtracts the passed-in value from this value, returning a new value. Note that this method is only provided to 130 // make text templates easier to use with these objects, since you can just subtract two Int[T] values together like 131 // they were primitive types. 132 func (f Int[T]) Sub(value Int[T]) Int[T] { 133 return f - value 134 } 135 136 // Mul multiplies this value by the passed-in value, returning a new value. 137 func (f Int[T]) Mul(value Int[T]) Int[T] { 138 return f * value / Int[T](Multiplier[T]()) 139 } 140 141 // Div divides this value by the passed-in value, returning a new value. 142 func (f Int[T]) Div(value Int[T]) Int[T] { 143 return f * Int[T](Multiplier[T]()) / value 144 } 145 146 // Mod returns the remainder after subtracting all full multiples of the passed-in value. 147 func (f Int[T]) Mod(value Int[T]) Int[T] { 148 return f - (value.Mul(f.Div(value).Trunc())) 149 } 150 151 // Abs returns the absolute value of this value. 152 func (f Int[T]) Abs() Int[T] { 153 if f < 0 { 154 return -f 155 } 156 return f 157 } 158 159 // Trunc returns a new value which has everything to the right of the decimal place truncated. 160 func (f Int[T]) Trunc() Int[T] { 161 mult := Int[T](Multiplier[T]()) 162 return f / mult * mult 163 } 164 165 // Ceil returns the value rounded up to the nearest whole number. 166 func (f Int[T]) Ceil() Int[T] { 167 v := f.Trunc() 168 if f > 0 && f != v { 169 v += Int[T](Multiplier[T]()) 170 } 171 return v 172 } 173 174 // Round returns the nearest integer, rounding half away from zero. 175 func (f Int[T]) Round() Int[T] { 176 one := Int[T](Multiplier[T]()) 177 value := f.Trunc() 178 rem := f - value 179 if rem >= one/2 { 180 value += one 181 } else if rem < -one/2 { 182 value -= one 183 } 184 return value 185 } 186 187 // Min returns the minimum of this value or its argument. 188 func (f Int[T]) Min(value Int[T]) Int[T] { 189 if f < value { 190 return f 191 } 192 return value 193 } 194 195 // Max returns the maximum of this value or its argument. 196 func (f Int[T]) Max(value Int[T]) Int[T] { 197 if f > value { 198 return f 199 } 200 return value 201 } 202 203 // Inc returns the value incremented by 1. 204 func (f Int[T]) Inc() Int[T] { 205 return f + Int[T](Multiplier[T]()) 206 } 207 208 // Dec returns the value decremented by 1. 209 func (f Int[T]) Dec() Int[T] { 210 return f - Int[T](Multiplier[T]()) 211 } 212 213 // As returns the equivalent value in the destination type. 214 func As[T fixed.Dx, TO xmath.Numeric](f Int[T]) TO { 215 var n TO 216 switch reflect.TypeOf(n).Kind() { 217 case reflect.Float32, reflect.Float64: 218 return TO(float64(f) / float64(Multiplier[T]())) 219 default: 220 return TO(int64(f) / Multiplier[T]()) 221 } 222 } 223 224 // CheckedAs is the same as As(), except that it returns an error if the value cannot be represented exactly in the 225 // requested destination type. 226 func CheckedAs[T fixed.Dx, TO xmath.Numeric](f Int[T]) (TO, error) { 227 var n TO 228 switch reflect.TypeOf(n).Kind() { 229 case reflect.Float32, reflect.Float64: 230 n = TO(float64(f) / float64(Multiplier[T]())) 231 if strconv.FormatFloat(float64(n), 'g', -1, reflect.TypeOf(n).Bits()) != f.String() { 232 return 0, fixed.ErrDoesNotFitInRequestedType 233 } 234 default: 235 n = TO(int64(f) / Multiplier[T]()) 236 if From[T](n) != f { 237 return 0, fixed.ErrDoesNotFitInRequestedType 238 } 239 } 240 return n, nil 241 } 242 243 // CommaWithSign returns the same as Comma(), but prefixes the value with a '+' if it is positive 244 func (f Int[T]) CommaWithSign() string { 245 if f >= 0 { 246 return "+" + f.Comma() 247 } 248 return f.Comma() 249 } 250 251 // Comma returns the same as String(), but with commas for values of 1000 and greater. 252 func (f Int[T]) Comma() string { 253 return txt.CommaFromStringNum(f.String()) 254 } 255 256 // StringWithSign returns the same as String(), but prefixes the value with a '+' if it is positive 257 func (f Int[T]) StringWithSign() string { 258 if f >= 0 { 259 return "+" + f.String() 260 } 261 return f.String() 262 } 263 264 func (f Int[T]) String() string { 265 mult := Int[T](Multiplier[T]()) 266 integer := f / mult 267 fraction := f % mult 268 if fraction == 0 { 269 return strconv.FormatInt(int64(integer), 10) 270 } 271 if fraction < 0 { 272 fraction = -fraction 273 } 274 fraction += mult 275 fStr := strconv.FormatInt(int64(fraction), 10) 276 for i := len(fStr) - 1; i > 0; i-- { 277 if fStr[i] != '0' { 278 fStr = fStr[1 : i+1] 279 break 280 } 281 } 282 var neg string 283 if integer == 0 && f < 0 { 284 neg = "-" 285 } else { 286 neg = "" 287 } 288 return fmt.Sprintf("%s%d.%s", neg, integer, fStr) 289 } 290 291 // MarshalText implements the encoding.TextMarshaler interface. 292 func (f Int[T]) MarshalText() ([]byte, error) { 293 return []byte(f.String()), nil 294 } 295 296 // UnmarshalText implements the encoding.TextUnmarshaler interface. 297 func (f *Int[T]) UnmarshalText(text []byte) error { 298 f1, err := FromString[T](txt.Unquote(string(text))) 299 if err != nil { 300 return err 301 } 302 *f = f1 303 return nil 304 } 305 306 // MarshalJSON implements json.Marshaler. 307 func (f Int[T]) MarshalJSON() ([]byte, error) { 308 return []byte(f.String()), nil 309 } 310 311 // UnmarshalJSON implements json.Unmarshaler. 312 func (f *Int[T]) UnmarshalJSON(in []byte) error { 313 v, err := FromString[T](txt.Unquote(string(in))) 314 if err != nil { 315 return err 316 } 317 *f = v 318 return nil 319 } 320 321 // MarshalYAML implements yaml.Marshaler. 322 func (f Int[T]) MarshalYAML() (any, error) { 323 return yaml.Node{ 324 Kind: yaml.ScalarNode, 325 Value: f.String(), 326 }, nil 327 } 328 329 // UnmarshalYAML implements yaml.Unmarshaler. 330 func (f *Int[T]) UnmarshalYAML(unmarshal func(any) error) error { 331 var str string 332 if err := unmarshal(&str); err != nil { 333 return err 334 } 335 v, err := FromString[T](str) 336 if err != nil { 337 return err 338 } 339 *f = v 340 return nil 341 }