github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/eval/vals/num.go (about)

     1  package vals
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"math/big"
     7  	"strconv"
     8  	"strings"
     9  )
    10  
    11  // Design notes:
    12  //
    13  // The choice and relationship of number types in Elvish is closely modelled
    14  // after R6RS's numerical tower (with the omission of complex types for now). In
    15  // fact, there is a 1:1 correspondence between number types in Elvish and a
    16  // typical R6RS implementation (the list below uses Chez Scheme's terminology;
    17  // see https://www.scheme.com/csug8/numeric.html):
    18  //
    19  // int      : fixnum
    20  // *big.Int : bignum
    21  // *big.Rat : ratnum
    22  // float64  : flonum
    23  //
    24  // Similar to Chez Scheme, *big.Int is only used for representing integers
    25  // outside the range of int, and *big.Rat is only used for representing
    26  // non-integer rationals. Furthermore, *big.Rat values are always in simplest
    27  // form (this is guaranteed by the math/big library). As a consequence, each
    28  // number in Elvish only has a single unique representation.
    29  //
    30  // Note that the only machine-native integer type included in the system is int.
    31  // This is done primarily for the uniqueness of representation for each number,
    32  // but also for simplicity - the vast majority of Go functions that take
    33  // machine-native integers take int. When there is a genuine need to work with
    34  // other machine-native integer types, you may have to manually convert from and
    35  // to *big.Int and check for the relevant range of integers.
    36  
    37  // Num is a stand-in type for int, *big.Int, *big.Rat or float64. This type
    38  // doesn't offer type safety, but is useful as a marker; for example, it is
    39  // respected when parsing function arguments.
    40  type Num interface{}
    41  
    42  // NumSlice is a stand-in type for []int, []*big.Int, []*big.Rat or []float64.
    43  // This type doesn't offer type safety, but is useful as a marker.
    44  type NumSlice interface{}
    45  
    46  // ParseNum parses a string into a suitable number type. If the string does not
    47  // represent a valid number, it returns nil.
    48  func ParseNum(s string) Num {
    49  	if strings.ContainsRune(s, '/') {
    50  		// Parse as big.Rat
    51  		if z, ok := new(big.Rat).SetString(s); ok {
    52  			return NormalizeBigRat(z)
    53  		}
    54  		return nil
    55  	}
    56  	// Try parsing as big.Int
    57  	if z, ok := new(big.Int).SetString(s, 0); ok {
    58  		return NormalizeBigInt(z)
    59  	}
    60  	// Try parsing as float64
    61  	if f, err := strconv.ParseFloat(s, 64); err == nil {
    62  		return f
    63  	}
    64  	return nil
    65  }
    66  
    67  // NumType represents a number type.
    68  type NumType uint8
    69  
    70  // PromoteToBigInt converts an int or *big.Int to a *big.Int. It panics if n is
    71  // any other type.
    72  // Possible values for NumType, sorted in the order of implicit conversion
    73  // (lower types can be implicitly converted to higher types).
    74  const (
    75  	Int NumType = iota
    76  	BigInt
    77  	BigRat
    78  	Float64
    79  )
    80  
    81  // UnifyNums unifies the given slice of numbers into the same type, converting
    82  // those with lower NumType to the highest NumType present in the slice. The typ
    83  // argument can be used to force the minimum NumType (use 0 if no minimal
    84  // NumType is needed).
    85  func UnifyNums(nums []Num, typ NumType) NumSlice {
    86  	for _, num := range nums {
    87  		if t := getNumType(num); t > typ {
    88  			typ = t
    89  		}
    90  	}
    91  	switch typ {
    92  	case Int:
    93  		// PromoteToBigInt converts an int or *big.Int, a *big.I or *big.Ratnt. It
    94  		// paniRat if n is any other type.
    95  		unified := make([]int, len(nums))
    96  		for i, num := range nums {
    97  			unified[i] = num.(int)
    98  		}
    99  		return unified
   100  	case BigInt:
   101  		unified := make([]*big.Int, len(nums))
   102  		for i, num := range nums {
   103  			unified[i] = PromoteToBigInt(num)
   104  		}
   105  		return unified
   106  	case BigRat:
   107  		unified := make([]*big.Rat, len(nums))
   108  		for i, num := range nums {
   109  			unified[i] = PromoteToBigRat(num)
   110  		}
   111  		return unified
   112  	case Float64:
   113  		unified := make([]float64, len(nums))
   114  		for i, num := range nums {
   115  			unified[i] = ConvertToFloat64(num)
   116  		}
   117  		return unified
   118  	default:
   119  		panic("unreachable")
   120  	}
   121  }
   122  
   123  // UnifyNums2 is like UnifyNums, but is optimized for two numbers.
   124  func UnifyNums2(n1, n2 Num, typ NumType) (u1, u2 Num) {
   125  	t1 := getNumType(n1)
   126  	if typ < t1 {
   127  		typ = t1
   128  	}
   129  	t2 := getNumType(n2)
   130  	if typ < t2 {
   131  		typ = t2
   132  	}
   133  	switch typ {
   134  	case Int:
   135  		return n1, n2
   136  	case BigInt:
   137  		return PromoteToBigInt(n1), PromoteToBigInt(n2)
   138  	case BigRat:
   139  		return PromoteToBigRat(n1), PromoteToBigRat(n2)
   140  	case Float64:
   141  		return ConvertToFloat64(n1), ConvertToFloat64(n2)
   142  	default:
   143  		panic("unreachable")
   144  	}
   145  }
   146  
   147  // getNumType returns the type of the interface if the value is a number; otherwise, it panics since
   148  // that is a "can't happen" case.
   149  func getNumType(n Num) NumType {
   150  	switch n.(type) {
   151  	case int:
   152  		return Int
   153  	case *big.Int:
   154  		return BigInt
   155  	case *big.Rat:
   156  		return BigRat
   157  	case float64:
   158  		return Float64
   159  	default:
   160  		panic("invalid num type " + fmt.Sprintf("%T", n))
   161  	}
   162  }
   163  
   164  // PromoteToBigInt converts an int or *big.Int to a *big.Int. It panics if n is
   165  // any other type.
   166  func PromoteToBigInt(n Num) *big.Int {
   167  	switch n := n.(type) {
   168  	case int:
   169  		return big.NewInt(int64(n))
   170  	case *big.Int:
   171  		return n
   172  	default:
   173  		panic("invalid num type " + fmt.Sprintf("%T", n))
   174  	}
   175  }
   176  
   177  // PromoteToBigRat converts an int, *big.Int or *big.Rat to a *big.Rat. It
   178  // panics if n is any other type.
   179  func PromoteToBigRat(n Num) *big.Rat {
   180  	switch n := n.(type) {
   181  	case int:
   182  		return big.NewRat(int64(n), 1)
   183  	case *big.Int:
   184  		var r big.Rat
   185  		r.SetInt(n)
   186  		return &r
   187  	case *big.Rat:
   188  		return n
   189  	default:
   190  		panic("invalid num type " + fmt.Sprintf("%T", n))
   191  	}
   192  }
   193  
   194  // ConvertToFloat64 converts any number to float64. It panics if num is not a
   195  // number value.
   196  func ConvertToFloat64(num Num) float64 {
   197  	switch num := num.(type) {
   198  	case int:
   199  		return float64(num)
   200  	case *big.Int:
   201  		if num.IsInt64() {
   202  			// Number can be converted losslessly to int64, so do that and then
   203  			// rely on the builtin conversion. Numbers too large to fit in
   204  			// float64 will be handled appropriately by the builtin conversion,
   205  			// overflowing to +Inf or -Inf.
   206  			return float64(num.Int64())
   207  		}
   208  		// Number doesn't fit in int64, so definitely won't fit in float64;
   209  		// handle this by overflowing.
   210  		return math.Inf(num.Sign())
   211  	case *big.Rat:
   212  		f, _ := num.Float64()
   213  		return f
   214  	case float64:
   215  		return num
   216  	default:
   217  		panic("invalid num type " + fmt.Sprintf("%T", num))
   218  	}
   219  }
   220  
   221  // NormalizeBigInt converts a big.Int to an int if it is within the range of
   222  // int. Otherwise it returns n as is.
   223  func NormalizeBigInt(z *big.Int) Num {
   224  	if i, ok := getInt(z); ok {
   225  		return i
   226  	}
   227  	return z
   228  }
   229  
   230  // NormalizeBigRat converts a big.Rat to a big.Int (or an int if within the
   231  // range) if its denominator is 1.
   232  func NormalizeBigRat(z *big.Rat) Num {
   233  	if z.IsInt() {
   234  		n := z.Num()
   235  		if i, ok := getInt(n); ok {
   236  			return i
   237  		}
   238  		return n
   239  	}
   240  	return z
   241  }
   242  
   243  func getInt(z *big.Int) (int, bool) {
   244  	// TODO: Use a more efficient implementation by examining z.Bits
   245  	if z.IsInt64() {
   246  		i64 := z.Int64()
   247  		i := int(i64)
   248  		if int64(i) == i64 {
   249  			return i, true
   250  		}
   251  	}
   252  	return -1, false
   253  }