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  }