github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/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  // Possible values for NumType, sorted in the order of implicit conversion
    71  // (lower types can be implicitly converted to higher types).
    72  const (
    73  	Int NumType = iota
    74  	BigInt
    75  	BigRat
    76  	Float64
    77  )
    78  
    79  // UnifyNums unifies the given slice of numbers into the same type, converting
    80  // those with lower NumType to the higest NumType present in the slice. The typ
    81  // argument can be used to force the minimum NumType.
    82  func UnifyNums(nums []Num, typ NumType) NumSlice {
    83  	for _, num := range nums {
    84  		if t := getNumType(num); t > typ {
    85  			typ = t
    86  		}
    87  	}
    88  	switch typ {
    89  	case Int:
    90  		unified := make([]int, len(nums))
    91  		for i, num := range nums {
    92  			unified[i] = num.(int)
    93  		}
    94  		return unified
    95  	case BigInt:
    96  		unified := make([]*big.Int, len(nums))
    97  		for i, num := range nums {
    98  			unified[i] = promoteToBigInt(num)
    99  		}
   100  		return unified
   101  	case BigRat:
   102  		unified := make([]*big.Rat, len(nums))
   103  		for i, num := range nums {
   104  			unified[i] = promoteToBigRat(num)
   105  		}
   106  		return unified
   107  	case Float64:
   108  		unified := make([]float64, len(nums))
   109  		for i, num := range nums {
   110  			unified[i] = convertToFloat64(num)
   111  		}
   112  		return unified
   113  	default:
   114  		panic("unreachable")
   115  	}
   116  }
   117  
   118  // UnifyNums2 is like UnifyNums, but is optimized for two numbers.
   119  func UnifyNums2(n1, n2 Num, typ NumType) (u1, u2 Num) {
   120  	t1 := getNumType(n1)
   121  	if typ < t1 {
   122  		typ = t1
   123  	}
   124  	t2 := getNumType(n2)
   125  	if typ < t2 {
   126  		typ = t2
   127  	}
   128  	switch typ {
   129  	case Int:
   130  		return n1, n2
   131  	case BigInt:
   132  		return promoteToBigInt(n1), promoteToBigInt(n2)
   133  	case BigRat:
   134  		return promoteToBigRat(n1), promoteToBigRat(n2)
   135  	case Float64:
   136  		return convertToFloat64(n1), convertToFloat64(n2)
   137  	default:
   138  		panic("unreachable")
   139  	}
   140  }
   141  
   142  func getNumType(n Num) NumType {
   143  	switch n.(type) {
   144  	case int:
   145  		return Int
   146  	case *big.Int:
   147  		return BigInt
   148  	case *big.Rat:
   149  		return BigRat
   150  	case float64:
   151  		return Float64
   152  	default:
   153  		panic("invalid num type " + fmt.Sprintf("%T", n))
   154  	}
   155  }
   156  
   157  func promoteToBigInt(n Num) *big.Int {
   158  	switch n := n.(type) {
   159  	case int:
   160  		return big.NewInt(int64(n))
   161  	case *big.Int:
   162  		return n
   163  	default:
   164  		panic("unreachable")
   165  	}
   166  }
   167  
   168  func promoteToBigRat(n Num) *big.Rat {
   169  	switch n := n.(type) {
   170  	case int:
   171  		return big.NewRat(int64(n), 1)
   172  	case *big.Int:
   173  		var r big.Rat
   174  		r.SetInt(n)
   175  		return &r
   176  	case *big.Rat:
   177  		return n
   178  	default:
   179  		panic("unreachable")
   180  	}
   181  }
   182  
   183  func convertToFloat64(num Num) float64 {
   184  	switch num := num.(type) {
   185  	case int:
   186  		return float64(num)
   187  	case *big.Int:
   188  		if num.IsInt64() {
   189  			// Might fit in float64
   190  			return float64(num.Int64())
   191  		} else {
   192  			// Definitely won't fit in float64
   193  			return math.Inf(num.Sign())
   194  		}
   195  	case *big.Rat:
   196  		f, _ := num.Float64()
   197  		return f
   198  	case float64:
   199  		return num
   200  	default:
   201  		panic("unreachable")
   202  	}
   203  }
   204  
   205  // NormalizeBigInt converts a big.Int to an int if it is within the range of
   206  // int. Otherwise it returns n as is.
   207  func NormalizeBigInt(z *big.Int) Num {
   208  	if i, ok := getInt(z); ok {
   209  		return i
   210  	}
   211  	return z
   212  }
   213  
   214  // NormalizeBigRat converts a big.Rat to a big.Int (or an int if within the
   215  // range) if its denominator is 1.
   216  func NormalizeBigRat(z *big.Rat) Num {
   217  	if z.IsInt() {
   218  		n := z.Num()
   219  		if i, ok := getInt(n); ok {
   220  			return i
   221  		}
   222  		return n
   223  	}
   224  	return z
   225  }
   226  
   227  func getInt(z *big.Int) (int, bool) {
   228  	// TODO: Use a more efficient implementation by examining z.Bits
   229  	if z.IsInt64() {
   230  		i64 := z.Int64()
   231  		i := int(i64)
   232  		if int64(i) == i64 {
   233  			return i, true
   234  		}
   235  	}
   236  	return -1, false
   237  }