github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/eval/builtin_fn_num.go (about)

     1  package eval
     2  
     3  import (
     4  	"math"
     5  	"math/big"
     6  	"math/rand"
     7  
     8  	"src.elv.sh/pkg/eval/errs"
     9  	"src.elv.sh/pkg/eval/vals"
    10  )
    11  
    12  // Numerical operations.
    13  
    14  //elvdoc:fn rand
    15  //
    16  // ```elvish
    17  // rand
    18  // ```
    19  //
    20  // Output a pseudo-random number in the interval [0, 1). Example:
    21  //
    22  // ```elvish-transcript
    23  // ~> rand
    24  // ▶ 0.17843564133528436
    25  // ```
    26  
    27  func init() {
    28  	addBuiltinFns(map[string]interface{}{
    29  		// Constructor
    30  		"float64":   toFloat64,
    31  		"num":       num,
    32  		"exact-num": exactNum,
    33  
    34  		// Comparison
    35  		"<":  lt,
    36  		"<=": le,
    37  		"==": eqNum,
    38  		"!=": ne,
    39  		">":  gt,
    40  		">=": ge,
    41  
    42  		// Arithmetic
    43  		"+": add,
    44  		"-": sub,
    45  		"*": mul,
    46  		// Also handles cd /
    47  		"/": slash,
    48  		"%": rem,
    49  
    50  		// Random
    51  		"rand":    rand.Float64,
    52  		"randint": randint,
    53  	})
    54  }
    55  
    56  //elvdoc:fn num
    57  //
    58  // ```elvish
    59  // num $string-or-number
    60  // ```
    61  //
    62  // Constructs a [typed number](./language.html#number).
    63  //
    64  // If the argument is a string, this command outputs the typed number the
    65  // argument represents, or raises an exception if the argument is not a valid
    66  // representation of a number. If the argument is already a typed number, this
    67  // command outputs it as is.
    68  //
    69  // This command is usually not needed for working with numbers; see the
    70  // discussion of [numerical commands](#numerical-commands).
    71  //
    72  // Examples:
    73  //
    74  // ```elvish-transcript
    75  // ~> num 10
    76  // ▶ (num 10)
    77  // ~> num 0x10
    78  // ▶ (num 16)
    79  // ~> num 1/12
    80  // ▶ (num 1/12)
    81  // ~> num 3.14
    82  // ▶ (num 3.14)
    83  // ~> num (num 10)
    84  // ▶ (num 10)
    85  // ```
    86  
    87  func num(n vals.Num) vals.Num {
    88  	// Conversion is actually handled in vals/conversion.go.
    89  	return n
    90  }
    91  
    92  //elvdoc:fn exact-num
    93  //
    94  // ```elvish
    95  // exact-num $string-or-number
    96  // ```
    97  //
    98  // Coerces the argument to an exact number. If the argument is infinity or NaN,
    99  // an exception is thrown.
   100  //
   101  // If the argument is a string, it is converted to a typed number first. If the
   102  // argument is already an exact number, it is returned as is.
   103  //
   104  // Examples:
   105  //
   106  // ```elvish-transcript
   107  // ~> exact-num (num 0.125)
   108  // ▶ (num 1/8)
   109  // ~> exact-num 0.125
   110  // ▶ (num 1/8)
   111  // ~> exact-num (num 1)
   112  // ▶ (num 1)
   113  // ```
   114  //
   115  // Beware that seemingly simple fractions that can't be represented precisely in
   116  // binary can result in the denominator being a very large power of 2:
   117  //
   118  // ```elvish-transcript
   119  // ~> exact-num 0.1
   120  // ▶ (num 3602879701896397/36028797018963968)
   121  // ```
   122  
   123  func exactNum(n vals.Num) (vals.Num, error) {
   124  	if f, ok := n.(float64); ok {
   125  		r := new(big.Rat).SetFloat64(f)
   126  		if r == nil {
   127  			return nil, errs.BadValue{What: "argument here",
   128  				Valid: "finite float", Actual: vals.ToString(f)}
   129  		}
   130  		return r, nil
   131  	}
   132  	return n, nil
   133  }
   134  
   135  //elvdoc:fn float64
   136  //
   137  // ```elvish
   138  // float64 $string-or-number
   139  // ```
   140  //
   141  // Constructs a floating-point number.
   142  //
   143  // This command is deprecated; use [`num`](#num) instead.
   144  
   145  func toFloat64(f float64) float64 {
   146  	return f
   147  }
   148  
   149  //elvdoc:fn &lt; &lt;= == != &gt; &gt;= {#num-cmp}
   150  //
   151  // ```elvish
   152  // <  $number... # less
   153  // <= $number... # less or equal
   154  // == $number... # equal
   155  // != $number... # not equal
   156  // >  $number... # greater
   157  // >= $number... # greater or equal
   158  // ```
   159  //
   160  // Number comparisons. All of them accept an arbitrary number of arguments:
   161  //
   162  // 1.  When given fewer than two arguments, all output `$true`.
   163  //
   164  // 2.  When given two arguments, output whether the two arguments satisfy the named
   165  // relationship.
   166  //
   167  // 3.  When given more than two arguments, output whether every adjacent pair of
   168  // numbers satisfy the named relationship.
   169  //
   170  // Examples:
   171  //
   172  // ```elvish-transcript
   173  // ~> == 3 3.0
   174  // ▶ $true
   175  // ~> < 3 4
   176  // ▶ $true
   177  // ~> < 3 4 10
   178  // ▶ $true
   179  // ~> < 6 9 1
   180  // ▶ $false
   181  // ```
   182  //
   183  // As a consequence of rule 3, the `!=` command outputs `$true` as long as any
   184  // _adjacent_ pair of numbers are not equal, even if some numbers that are not
   185  // adjacent are equal:
   186  //
   187  // ```elvish-transcript
   188  // ~> != 5 5 4
   189  // ▶ $false
   190  // ~> != 5 6 5
   191  // ▶ $true
   192  // ```
   193  
   194  func lt(nums ...vals.Num) bool {
   195  	return chainCompare(nums,
   196  		func(a, b int) bool { return a < b },
   197  		func(a, b *big.Int) bool { return a.Cmp(b) < 0 },
   198  		func(a, b *big.Rat) bool { return a.Cmp(b) < 0 },
   199  		func(a, b float64) bool { return a < b })
   200  
   201  }
   202  
   203  func le(nums ...vals.Num) bool {
   204  	return chainCompare(nums,
   205  		func(a, b int) bool { return a <= b },
   206  		func(a, b *big.Int) bool { return a.Cmp(b) <= 0 },
   207  		func(a, b *big.Rat) bool { return a.Cmp(b) <= 0 },
   208  		func(a, b float64) bool { return a <= b })
   209  }
   210  
   211  func eqNum(nums ...vals.Num) bool {
   212  	return chainCompare(nums,
   213  		func(a, b int) bool { return a == b },
   214  		func(a, b *big.Int) bool { return a.Cmp(b) == 0 },
   215  		func(a, b *big.Rat) bool { return a.Cmp(b) == 0 },
   216  		func(a, b float64) bool { return a == b })
   217  }
   218  
   219  func ne(nums ...vals.Num) bool {
   220  	return chainCompare(nums,
   221  		func(a, b int) bool { return a != b },
   222  		func(a, b *big.Int) bool { return a.Cmp(b) != 0 },
   223  		func(a, b *big.Rat) bool { return a.Cmp(b) != 0 },
   224  		func(a, b float64) bool { return a != b })
   225  }
   226  
   227  func gt(nums ...vals.Num) bool {
   228  	return chainCompare(nums,
   229  		func(a, b int) bool { return a > b },
   230  		func(a, b *big.Int) bool { return a.Cmp(b) > 0 },
   231  		func(a, b *big.Rat) bool { return a.Cmp(b) > 0 },
   232  		func(a, b float64) bool { return a > b })
   233  }
   234  
   235  func ge(nums ...vals.Num) bool {
   236  	return chainCompare(nums,
   237  		func(a, b int) bool { return a >= b },
   238  		func(a, b *big.Int) bool { return a.Cmp(b) >= 0 },
   239  		func(a, b *big.Rat) bool { return a.Cmp(b) >= 0 },
   240  		func(a, b float64) bool { return a >= b })
   241  }
   242  
   243  func chainCompare(nums []vals.Num,
   244  	p1 func(a, b int) bool, p2 func(a, b *big.Int) bool,
   245  	p3 func(a, b *big.Rat) bool, p4 func(a, b float64) bool) bool {
   246  
   247  	for i := 0; i < len(nums)-1; i++ {
   248  		var r bool
   249  		a, b := vals.UnifyNums2(nums[i], nums[i+1], 0)
   250  		switch a := a.(type) {
   251  		case int:
   252  			r = p1(a, b.(int))
   253  		case *big.Int:
   254  			r = p2(a, b.(*big.Int))
   255  		case *big.Rat:
   256  			r = p3(a, b.(*big.Rat))
   257  		case float64:
   258  			r = p4(a, b.(float64))
   259  		}
   260  		if !r {
   261  			return false
   262  		}
   263  	}
   264  	return true
   265  }
   266  
   267  //elvdoc:fn + {#add}
   268  //
   269  // ```elvish
   270  // + $num...
   271  // ```
   272  //
   273  // Outputs the sum of all arguments, or 0 when there are no arguments.
   274  //
   275  // This command is [exactness-preserving](#exactness-preserving).
   276  //
   277  // Examples:
   278  //
   279  // ```elvish-transcript
   280  // ~> + 5 2 7
   281  // ▶ (num 14)
   282  // ~> + 1/2 1/3 1/4
   283  // ▶ (num 13/12)
   284  // ~> + 1/2 0.5
   285  // ▶ (num 1.0)
   286  // ```
   287  
   288  func add(rawNums ...vals.Num) vals.Num {
   289  	nums := vals.UnifyNums(rawNums, vals.BigInt)
   290  	switch nums := nums.(type) {
   291  	case []*big.Int:
   292  		acc := big.NewInt(0)
   293  		for _, num := range nums {
   294  			acc.Add(acc, num)
   295  		}
   296  		return vals.NormalizeBigInt(acc)
   297  	case []*big.Rat:
   298  		acc := big.NewRat(0, 1)
   299  		for _, num := range nums {
   300  			acc.Add(acc, num)
   301  		}
   302  		return vals.NormalizeBigRat(acc)
   303  	case []float64:
   304  		acc := float64(0)
   305  		for _, num := range nums {
   306  			acc += num
   307  		}
   308  		return acc
   309  	default:
   310  		panic("unreachable")
   311  	}
   312  }
   313  
   314  //elvdoc:fn - {#sub}
   315  //
   316  // ```elvish
   317  // - $x-num $y-num...
   318  // ```
   319  //
   320  // Outputs the result of substracting from `$x-num` all the `$y-num`s, working
   321  // from left to right. When no `$y-num` is given, outputs the negation of
   322  // `$x-num` instead (in other words, `- $x-num` is equivalent to `- 0 $x-num`).
   323  //
   324  // This command is [exactness-preserving](#exactness-preserving).
   325  //
   326  // Examples:
   327  //
   328  // ```elvish-transcript
   329  // ~> - 5
   330  // ▶ (num -5)
   331  // ~> - 5 2
   332  // ▶ (num 3)
   333  // ~> - 5 2 7
   334  // ▶ (num -4)
   335  // ~> - 1/2 1/3
   336  // ▶ (num 1/6)
   337  // ~> - 1/2 0.3
   338  // ▶ (num 0.2)
   339  // ~> - 10
   340  // ▶ (num -10)
   341  // ```
   342  
   343  func sub(rawNums ...vals.Num) (vals.Num, error) {
   344  	if len(rawNums) == 0 {
   345  		return nil, errs.ArityMismatch{
   346  			What:     "arguments here",
   347  			ValidLow: 1, ValidHigh: -1, Actual: 0,
   348  		}
   349  	}
   350  
   351  	nums := vals.UnifyNums(rawNums, vals.BigInt)
   352  	switch nums := nums.(type) {
   353  	case []*big.Int:
   354  		acc := &big.Int{}
   355  		if len(nums) == 1 {
   356  			acc.Neg(nums[0])
   357  			return acc, nil
   358  		}
   359  		acc.Set(nums[0])
   360  		for _, num := range nums[1:] {
   361  			acc.Sub(acc, num)
   362  		}
   363  		return acc, nil
   364  	case []*big.Rat:
   365  		acc := &big.Rat{}
   366  		if len(nums) == 1 {
   367  			acc.Neg(nums[0])
   368  			return acc, nil
   369  		}
   370  		acc.Set(nums[0])
   371  		for _, num := range nums[1:] {
   372  			acc.Sub(acc, num)
   373  		}
   374  		return acc, nil
   375  	case []float64:
   376  		if len(nums) == 1 {
   377  			return -nums[0], nil
   378  		}
   379  		acc := nums[0]
   380  		for _, num := range nums[1:] {
   381  			acc -= num
   382  		}
   383  		return acc, nil
   384  	default:
   385  		panic("unreachable")
   386  	}
   387  }
   388  
   389  //elvdoc:fn * {#mul}
   390  //
   391  // ```elvish
   392  // * $num...
   393  // ```
   394  //
   395  // Outputs the product of all arguments, or 1 when there are no arguments.
   396  //
   397  // This command is [exactness-preserving](#exactness-preserving). Additionally,
   398  // when any argument is exact 0 and no other argument is a floating-point
   399  // infinity, the result is exact 0.
   400  //
   401  // Examples:
   402  //
   403  // ```elvish-transcript
   404  // ~> * 2 5 7
   405  // ▶ (num 70)
   406  // ~> * 1/2 0.5
   407  // ▶ (num 0.25)
   408  // ~> * 0 0.5
   409  // ▶ (num 0)
   410  // ```
   411  
   412  func mul(rawNums ...vals.Num) vals.Num {
   413  	hasExact0 := false
   414  	hasInf := false
   415  	for _, num := range rawNums {
   416  		if num == 0 {
   417  			hasExact0 = true
   418  		}
   419  		if f, ok := num.(float64); ok && math.IsInf(f, 0) {
   420  			hasInf = true
   421  			break
   422  		}
   423  	}
   424  	if hasExact0 && !hasInf {
   425  		return 0
   426  	}
   427  
   428  	nums := vals.UnifyNums(rawNums, vals.BigInt)
   429  	switch nums := nums.(type) {
   430  	case []*big.Int:
   431  		acc := big.NewInt(1)
   432  		for _, num := range nums {
   433  			acc.Mul(acc, num)
   434  		}
   435  		return vals.NormalizeBigInt(acc)
   436  	case []*big.Rat:
   437  		acc := big.NewRat(1, 1)
   438  		for _, num := range nums {
   439  			acc.Mul(acc, num)
   440  		}
   441  		return vals.NormalizeBigRat(acc)
   442  	case []float64:
   443  		acc := float64(1)
   444  		for _, num := range nums {
   445  			acc *= num
   446  		}
   447  		return acc
   448  	default:
   449  		panic("unreachable")
   450  	}
   451  }
   452  
   453  //elvdoc:fn / {#div}
   454  //
   455  // ```elvish
   456  // / $x-num $y-num...
   457  // ```
   458  //
   459  // Outputs the result of dividing `$x-num` with all the `$y-num`s, working from
   460  // left to right. When no `$y-num` is given, outputs the reciprocal of `$x-num`
   461  // instead (in other words, `/ $y-num` is equivalent to `/ 1 $y-num`).
   462  //
   463  // Dividing by exact 0 raises an exception. Dividing by inexact 0 results with
   464  // either infinity or NaN according to floating-point semantics.
   465  //
   466  // This command is [exactness-preserving](#exactness-preserving). Additionally,
   467  // when `$x-num` is exact 0 and no `$y-num` is exact 0, the result is exact 0.
   468  //
   469  // Examples:
   470  //
   471  // ```elvish-transcript
   472  // ~> / 2
   473  // ▶ (num 1/2)
   474  // ~> / 2.0
   475  // ▶ (num 0.5)
   476  // ~> / 10 5
   477  // ▶ (num 2)
   478  // ~> / 2 5
   479  // ▶ (num 2/5)
   480  // ~> / 2 5 7
   481  // ▶ (num 2/35)
   482  // ~> / 0 1.0
   483  // ▶ (num 0)
   484  // ~> / 2 0
   485  // Exception: bad value: divisor must be number other than exact 0, but is exact 0
   486  // [tty 6], line 1: / 2 0
   487  // ~> / 2 0.0
   488  // ▶ (num +Inf)
   489  // ```
   490  //
   491  // When given no argument, this command is equivalent to `cd /`, due to the
   492  // implicit cd feature. (The implicit cd feature will probably change to avoid
   493  // this oddity).
   494  
   495  func slash(fm *Frame, args ...vals.Num) error {
   496  	if len(args) == 0 {
   497  		// cd /
   498  		return fm.Evaler.Chdir("/")
   499  	}
   500  	// Division
   501  	result, err := div(args...)
   502  	if err == nil {
   503  		fm.OutputChan() <- vals.FromGo(result)
   504  	}
   505  	return err
   506  }
   507  
   508  // ErrDivideByZero is thrown when attempting to divide by zero.
   509  var ErrDivideByZero = errs.BadValue{
   510  	What: "divisor", Valid: "number other than exact 0", Actual: "exact 0"}
   511  
   512  func div(rawNums ...vals.Num) (vals.Num, error) {
   513  	for _, num := range rawNums[1:] {
   514  		if num == 0 {
   515  			return nil, ErrDivideByZero
   516  		}
   517  	}
   518  	if rawNums[0] == 0 {
   519  		return 0, nil
   520  	}
   521  	nums := vals.UnifyNums(rawNums, vals.BigRat)
   522  	switch nums := nums.(type) {
   523  	case []*big.Rat:
   524  		acc := &big.Rat{}
   525  		acc.Set(nums[0])
   526  		if len(nums) == 1 {
   527  			acc.Inv(acc)
   528  			return acc, nil
   529  		}
   530  		for _, num := range nums[1:] {
   531  			acc.Quo(acc, num)
   532  		}
   533  		return acc, nil
   534  	case []float64:
   535  		acc := nums[0]
   536  		if len(nums) == 1 {
   537  			return 1 / acc, nil
   538  		}
   539  		for _, num := range nums[1:] {
   540  			acc /= num
   541  		}
   542  		return acc, nil
   543  	default:
   544  		panic("unreachable")
   545  	}
   546  }
   547  
   548  //elvdoc:fn % {#rem}
   549  //
   550  // ```elvish
   551  // % $x $y
   552  // ```
   553  //
   554  // Output the remainder after dividing `$x` by `$y`. The result has the same
   555  // sign as `$x`. Both must be integers that can represented in a machine word
   556  // (this limit may be lifted in future).
   557  //
   558  // Examples:
   559  //
   560  // ```elvish-transcript
   561  // ~> % 10 3
   562  // ▶ 1
   563  // ~> % -10 3
   564  // ▶ -1
   565  // ~> % 10 -3
   566  // ▶ 1
   567  // ```
   568  
   569  func rem(a, b int) (int, error) {
   570  	// TODO: Support other number types
   571  	if b == 0 {
   572  		return 0, ErrDivideByZero
   573  	}
   574  	return a % b, nil
   575  }
   576  
   577  //elvdoc:fn randint
   578  //
   579  // ```elvish
   580  // randint $low $high
   581  // ```
   582  //
   583  // Output a pseudo-random integer in the interval [$low, $high). Example:
   584  //
   585  // ```elvish-transcript
   586  // ~> # Emulate dice
   587  // randint 1 7
   588  // ▶ 6
   589  // ```
   590  
   591  func randint(low, high int) (int, error) {
   592  	if low >= high {
   593  		return 0, ErrArgs
   594  	}
   595  	return low + rand.Intn(high-low), nil
   596  }