github.com/richardwilkes/toolbox@v1.121.0/xmath/fixed/f128/f128.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 f128 11 12 import ( 13 "fmt" 14 "math/big" 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 "github.com/richardwilkes/toolbox/xmath/num" 24 "gopkg.in/yaml.v3" 25 ) 26 27 // Int holds a fixed-point value. Values are truncated, not rounded. 28 type Int[T fixed.Dx] struct { 29 data num.Int128 30 } 31 32 // Maximum returns the maximum possible value the type can hold. 33 func Maximum[T fixed.Dx]() Int[T] { 34 return Int[T]{data: num.MaxInt128} 35 } 36 37 // Minimum returns the minimum possible value the type can hold. 38 func Minimum[T fixed.Dx]() Int[T] { 39 return Int[T]{data: num.MinInt128} 40 } 41 42 // MaxSafeMultiply returns the maximum value that can be safely multiplied without overflow. 43 func MaxSafeMultiply[T fixed.Dx]() Int[T] { 44 return Maximum[T]().Div(Multiplier[T]()) 45 } 46 47 // MaxDecimalDigits returns the maximum number of digits after the decimal that will be used. 48 func MaxDecimalDigits[T fixed.Dx]() int { 49 var t T 50 return t.Places() 51 } 52 53 // Multiplier returns the multiplier used. 54 func Multiplier[T fixed.Dx]() Int[T] { 55 return Int[T]{data: multiplier[T]()} 56 } 57 58 func multiplier[T fixed.Dx]() num.Int128 { 59 var t T 60 return num.Int128From64(t.Multiplier()) 61 } 62 63 // From creates a new value. 64 func From[T fixed.Dx, FROM xmath.Numeric](value FROM) Int[T] { 65 switch reflect.TypeOf(value).Kind() { 66 case reflect.Float32, reflect.Float64: 67 f, _ := FromString[T](new(big.Float).SetPrec(128).SetFloat64(float64(value)).Text('f', MaxDecimalDigits[T]()+1)) //nolint:errcheck // Failure means 0 68 return f 69 default: 70 var t T 71 return Int[T]{data: num.Int128From64(int64(value)).Mul(num.Int128From64(t.Multiplier()))} 72 } 73 } 74 75 // FromString creates a new value from a string. 76 func FromString[T fixed.Dx](str string) (Int[T], error) { 77 if str == "" { 78 return Int[T]{}, errs.New("empty string is not valid") 79 } 80 str = strings.ReplaceAll(str, ",", "") 81 if strings.ContainsAny(str, "Ee") { 82 // Given a floating-point value with an exponent, which technically 83 // isn't valid input, but we'll try to convert it anyway. 84 f, err := strconv.ParseFloat(str, 64) 85 if err != nil { 86 return Int[T]{}, err 87 } 88 return From[T](f), nil 89 } 90 parts := strings.SplitN(str, ".", 2) 91 var neg bool 92 value := new(big.Int) 93 fraction := new(big.Int) 94 var t T 95 switch parts[0] { 96 case "": 97 case "-", "-0": 98 neg = true 99 default: 100 if _, ok := value.SetString(parts[0], 10); !ok { 101 return Int[T]{}, errs.Newf("invalid value: %s", str) 102 } 103 if value.Sign() < 0 { 104 neg = true 105 value.Neg(value) 106 } 107 value.Mul(value, big.NewInt(t.Multiplier())) 108 } 109 if len(parts) > 1 { 110 cutoff := 1 + MaxDecimalDigits[T]() 111 var buffer strings.Builder 112 buffer.WriteString("1") 113 buffer.WriteString(parts[1]) 114 for buffer.Len() < cutoff { 115 buffer.WriteString("0") 116 } 117 frac := buffer.String() 118 if len(frac) > cutoff { 119 frac = frac[:cutoff] 120 } 121 if _, ok := fraction.SetString(frac, 10); !ok { 122 return Int[T]{}, errs.Newf("invalid value: %s", str) 123 } 124 value.Add(value, fraction).Sub(value, big.NewInt(t.Multiplier())) 125 } 126 if neg { 127 value.Neg(value) 128 } 129 return Int[T]{data: num.Int128FromBigInt(value)}, nil 130 } 131 132 // FromStringForced creates a new value from a string. 133 func FromStringForced[T fixed.Dx](str string) Int[T] { 134 f, _ := FromString[T](str) //nolint:errcheck // failure results in 0, which is acceptable here 135 return f 136 } 137 138 // Add adds this value to the passed-in value, returning a new value. 139 func (f Int[T]) Add(value Int[T]) Int[T] { 140 return Int[T]{data: f.data.Add(value.data)} 141 } 142 143 // Sub subtracts the passed-in value from this value, returning a new value. 144 func (f Int[T]) Sub(value Int[T]) Int[T] { 145 return Int[T]{data: f.data.Sub(value.data)} 146 } 147 148 // Mul multiplies this value by the passed-in value, returning a new value. 149 func (f Int[T]) Mul(value Int[T]) Int[T] { 150 return Int[T]{data: f.data.Mul(value.data).Div(multiplier[T]())} 151 } 152 153 // Div divides this value by the passed-in value, returning a new value. 154 func (f Int[T]) Div(value Int[T]) Int[T] { 155 return Int[T]{data: f.data.Mul(multiplier[T]()).Div(value.data)} 156 } 157 158 // Mod returns the remainder after subtracting all full multiples of the passed-in value. 159 func (f Int[T]) Mod(value Int[T]) Int[T] { 160 return f.Sub(value.Mul(f.Div(value).Trunc())) 161 } 162 163 // Neg negates this value, returning a new value. 164 func (f Int[T]) Neg() Int[T] { 165 return Int[T]{data: f.data.Neg()} 166 } 167 168 // Abs returns the absolute value of this value. 169 func (f Int[T]) Abs() Int[T] { 170 return Int[T]{data: f.data.Abs()} 171 } 172 173 // Cmp returns 1 if i > n, 0 if i == n, and -1 if i < n. 174 func (f Int[T]) Cmp(n Int[T]) int { 175 return f.data.Cmp(n.data) 176 } 177 178 // GreaterThan returns true if i > n. 179 func (f Int[T]) GreaterThan(n Int[T]) bool { 180 return f.data.GreaterThan(n.data) 181 } 182 183 // GreaterThanOrEqual returns true if i >= n. 184 func (f Int[T]) GreaterThanOrEqual(n Int[T]) bool { 185 return f.data.GreaterThanOrEqual(n.data) 186 } 187 188 // Equal returns true if i == n. 189 func (f Int[T]) Equal(n Int[T]) bool { 190 return f.data.Equal(n.data) 191 } 192 193 // LessThan returns true if i < n. 194 func (f Int[T]) LessThan(n Int[T]) bool { 195 return f.data.LessThan(n.data) 196 } 197 198 // LessThanOrEqual returns true if i <= n. 199 func (f Int[T]) LessThanOrEqual(n Int[T]) bool { 200 return f.data.LessThanOrEqual(n.data) 201 } 202 203 // Trunc returns a new value which has everything to the right of the decimal place truncated. 204 func (f Int[T]) Trunc() Int[T] { 205 m := multiplier[T]() 206 return Int[T]{data: f.data.Div(m).Mul(m)} 207 } 208 209 // Ceil returns the value rounded up to the nearest whole number. 210 func (f Int[T]) Ceil() Int[T] { 211 v := f.Trunc() 212 if f.GreaterThan(Int[T]{}) && f != v { 213 v = v.Add(Multiplier[T]()) 214 } 215 return v 216 } 217 218 // Round returns the nearest integer, rounding half away from zero. 219 func (f Int[T]) Round() Int[T] { 220 one := Multiplier[T]() 221 half := Int[T]{data: one.data.Div(num.Int128From64(2))} 222 negHalf := half.Neg() 223 value := f.Trunc() 224 rem := f.Sub(value) 225 if rem.GreaterThanOrEqual(half) { 226 value = value.Add(one) 227 } else if rem.LessThan(negHalf) { 228 value = value.Sub(one) 229 } 230 return value 231 } 232 233 // Min returns the minimum of this value or its argument. 234 func (f Int[T]) Min(value Int[T]) Int[T] { 235 if f.data.LessThan(value.data) { 236 return f 237 } 238 return value 239 } 240 241 // Max returns the maximum of this value or its argument. 242 func (f Int[T]) Max(value Int[T]) Int[T] { 243 if f.data.GreaterThan(value.data) { 244 return f 245 } 246 return value 247 } 248 249 // Inc returns the value incremented by 1. 250 func (f Int[T]) Inc() Int[T] { 251 return f.Add(Multiplier[T]()) 252 } 253 254 // Dec returns the value decremented by 1. 255 func (f Int[T]) Dec() Int[T] { 256 return f.Sub(Multiplier[T]()) 257 } 258 259 // As returns the equivalent value in the destination type. 260 func As[T fixed.Dx, TO xmath.Numeric](f Int[T]) TO { 261 var n TO 262 switch reflect.TypeOf(n).Kind() { 263 case reflect.Float32, reflect.Float64: 264 var t T 265 f64, _ := new(big.Float).SetPrec(128).Quo(f.data.AsBigFloat(), 266 new(big.Float).SetPrec(128).SetInt(big.NewInt(t.Multiplier()))).Float64() 267 return TO(f64) 268 default: 269 return TO(f.data.Div(multiplier[T]()).AsInt64()) 270 } 271 } 272 273 // CheckedAs is the same as As(), except that it returns an error if the value cannot be represented exactly in the 274 // requested destination type. 275 func CheckedAs[T fixed.Dx, TO xmath.Numeric](f Int[T]) (TO, error) { 276 var n TO 277 switch reflect.TypeOf(n).Kind() { 278 case reflect.Float32, reflect.Float64: 279 var t T 280 f64, _ := new(big.Float).SetPrec(128).Quo(f.data.AsBigFloat(), 281 new(big.Float).SetPrec(128).SetInt(big.NewInt(t.Multiplier()))).Float64() 282 n = TO(f64) 283 if strconv.FormatFloat(float64(n), 'g', -1, reflect.TypeOf(n).Bits()) != f.String() { 284 return 0, fixed.ErrDoesNotFitInRequestedType 285 } 286 default: 287 n = TO(f.data.Div(multiplier[T]()).AsInt64()) 288 if From[T](n) != f { 289 return 0, fixed.ErrDoesNotFitInRequestedType 290 } 291 } 292 return n, nil 293 } 294 295 // CommaWithSign returns the same as Comma(), but prefixes the value with a '+' if it is positive. 296 func (f Int[T]) CommaWithSign() string { 297 if f.data.Sign() >= 0 { 298 return "+" + f.Comma() 299 } 300 return f.Comma() 301 } 302 303 // Comma returns the same as String(), but with commas for values of 1000 and greater. 304 func (f Int[T]) Comma() string { 305 return txt.CommaFromStringNum(f.String()) 306 } 307 308 // StringWithSign returns the same as String(), but prefixes the value with a '+' if it is positive. 309 func (f Int[T]) StringWithSign() string { 310 if f.data.Sign() >= 0 { 311 return "+" + f.String() 312 } 313 return f.String() 314 } 315 316 func (f Int[T]) String() string { 317 mult := multiplier[T]() 318 integer := f.data.Div(mult) 319 iStr := integer.String() 320 fraction := f.data.Sub(integer.Mul(mult)) 321 if fraction.IsZero() { 322 return iStr 323 } 324 if fraction.Sign() < 0 { 325 fraction = fraction.Neg() 326 } 327 fStr := fraction.Add(mult).String() 328 for i := len(fStr) - 1; i > 0; i-- { 329 if fStr[i] != '0' { 330 fStr = fStr[1 : i+1] 331 break 332 } 333 } 334 var neg string 335 if integer.IsZero() && f.data.Sign() < 0 { 336 neg = "-" 337 } else { 338 neg = "" 339 } 340 return fmt.Sprintf("%s%s.%s", neg, iStr, fStr) 341 } 342 343 // MarshalText implements the encoding.TextMarshaler interface. 344 func (f Int[T]) MarshalText() ([]byte, error) { 345 return []byte(f.String()), nil 346 } 347 348 // UnmarshalText implements the encoding.TextUnmarshaler interface. 349 func (f *Int[T]) UnmarshalText(text []byte) error { 350 f1, err := FromString[T](txt.Unquote(string(text))) 351 if err != nil { 352 return err 353 } 354 *f = f1 355 return nil 356 } 357 358 // MarshalJSON implements json.Marshaler. 359 func (f Int[T]) MarshalJSON() ([]byte, error) { 360 return []byte(f.String()), nil 361 } 362 363 // UnmarshalJSON implements json.Unmarshaler. 364 func (f *Int[T]) UnmarshalJSON(in []byte) error { 365 v, err := FromString[T](txt.Unquote(string(in))) 366 if err != nil { 367 return err 368 } 369 *f = v 370 return nil 371 } 372 373 // MarshalYAML implements yaml.Marshaler. 374 func (f Int[T]) MarshalYAML() (any, error) { 375 return yaml.Node{ 376 Kind: yaml.ScalarNode, 377 Value: f.String(), 378 }, nil 379 } 380 381 // UnmarshalYAML implements yaml.Unmarshaler. 382 func (f *Int[T]) UnmarshalYAML(unmarshal func(any) error) error { 383 var str string 384 if err := unmarshal(&str); err != nil { 385 return err 386 } 387 v, err := FromString[T](str) 388 if err != nil { 389 return err 390 } 391 *f = v 392 return nil 393 }