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 }