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 }