mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-05 03:17:50 +08:00
Change math:pow to produce exact results in certain cases; deprecate math:pow10.
This commit is contained in:
parent
59e4a7302a
commit
af3292418f
|
@ -36,6 +36,8 @@ or compiled, even if it is not executed:
|
|||
- The `prclose` and `pwclose` commands are deprecated. Use `file:close`
|
||||
instead.
|
||||
|
||||
- The `math:pow10` command is deprecated. Use `math:pow 10 $exponent` instead.
|
||||
|
||||
# Notable bugfixes
|
||||
|
||||
- Iterating over certain list slices no longer crash Elvish
|
||||
|
|
|
@ -37,8 +37,8 @@ var fns = map[string]interface{}{
|
|||
"log2": math.Log2,
|
||||
"max": max,
|
||||
"min": min,
|
||||
"pow": math.Pow, // TODO: Make exactness-preserving for integer exponents
|
||||
"pow10": math.Pow10, // TODO: Make exactness-preserving for integer exponents
|
||||
"pow": pow,
|
||||
"pow10": pow10,
|
||||
"round": round,
|
||||
"round-to-even": roundToEven,
|
||||
"sin": math.Sin,
|
||||
|
@ -565,16 +565,77 @@ func min(rawNums ...vals.Num) (vals.Num, error) {
|
|||
// math:pow $base $exponent
|
||||
// ```
|
||||
//
|
||||
// Output the result of raising `$base` to the power of `$exponent`. Examples:
|
||||
// Outputs the result of raising `$base` to the power of `$exponent`.
|
||||
//
|
||||
// This function produces an exact result when `$base` is exact and `$exponent`
|
||||
// is an exact integer. Otherwise it produces an inexact result.
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// ```elvish-transcript
|
||||
// ~> math:pow 3 2
|
||||
// ▶ (float64 9)
|
||||
// ▶ (num 9)
|
||||
// ~> math:pow -2 2
|
||||
// ▶ (float64 4)
|
||||
// ▶ (num 4)
|
||||
// ~> math:pow 1/2 3
|
||||
// ▶ (num 1/8)
|
||||
// ~> math:pow 1/2 -3
|
||||
// ▶ (num 8)
|
||||
// ~> math:pow 9 1/2
|
||||
// ▶ (num 3.0)
|
||||
// ~> math:pow 12 1.1
|
||||
// ▶ (num 15.38506624784179)
|
||||
// ```
|
||||
//
|
||||
// @cf math:pow10
|
||||
|
||||
func pow(base, exp vals.Num) vals.Num {
|
||||
if isExact(base) && isExactInt(exp) {
|
||||
// Produce exact result
|
||||
switch exp {
|
||||
case 0:
|
||||
return 1
|
||||
case 1:
|
||||
return base
|
||||
case -1:
|
||||
return new(big.Rat).Inv(vals.PromoteToBigRat(base))
|
||||
}
|
||||
exp := vals.PromoteToBigInt(exp)
|
||||
if isExactInt(base) && exp.Sign() > 0 {
|
||||
base := vals.PromoteToBigInt(base)
|
||||
return new(big.Int).Exp(base, exp, nil)
|
||||
}
|
||||
base := vals.PromoteToBigRat(base)
|
||||
if exp.Sign() < 0 {
|
||||
base = new(big.Rat).Inv(base)
|
||||
exp = new(big.Int).Neg(exp)
|
||||
}
|
||||
return new(big.Rat).SetFrac(
|
||||
new(big.Int).Exp(base.Num(), exp, nil),
|
||||
new(big.Int).Exp(base.Denom(), exp, nil))
|
||||
}
|
||||
|
||||
// Produce inexact result
|
||||
basef := vals.ConvertToFloat64(base)
|
||||
expf := vals.ConvertToFloat64(exp)
|
||||
return math.Pow(basef, expf)
|
||||
}
|
||||
|
||||
func isExact(n vals.Num) bool {
|
||||
switch n.(type) {
|
||||
case int, *big.Int, *big.Rat:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isExactInt(n vals.Num) bool {
|
||||
switch n.(type) {
|
||||
case int, *big.Int:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//elvdoc:fn pow10
|
||||
//
|
||||
|
@ -582,19 +643,17 @@ func min(rawNums ...vals.Num) (vals.Num, error) {
|
|||
// math:pow10 $exponent
|
||||
// ```
|
||||
//
|
||||
// Output the result of raising ten to the power of `$exponent` which must be
|
||||
// an integer. Note that `$exponent > 308` results in +Inf and `$exponent <
|
||||
// -323` results in zero. Examples:
|
||||
// Equivalent to `math:pow 10 $exponent`.
|
||||
//
|
||||
// ```elvish-transcript
|
||||
// ~> math:pow10 2
|
||||
// ▶ (float64 100)
|
||||
// ~> math:pow10 -3
|
||||
// ▶ (float64 0.001)
|
||||
// ```
|
||||
// This function is deprecated; use `math:pow 10 $exponent` instead.
|
||||
//
|
||||
// @cf math:pow
|
||||
|
||||
func pow10(fm *eval.Frame, exp vals.Num) vals.Num {
|
||||
fm.Deprecate(`the "math:pow10" command is deprecated; use "math:pow 10 $exponent" instead`, nil, 16)
|
||||
return pow(10, exp)
|
||||
}
|
||||
|
||||
//elvdoc:fn round
|
||||
//
|
||||
// ```elvish
|
||||
|
@ -816,7 +875,7 @@ func integerize(n vals.Num, fnFloat func(float64) float64, fnRat func(*big.Rat)
|
|||
// *big.Int, but we still try to be defensive here.
|
||||
return n.Num()
|
||||
}
|
||||
return vals.NormalizeBigInt(fnRat(n))
|
||||
return fnRat(n)
|
||||
case float64:
|
||||
return fnFloat(n)
|
||||
default:
|
||||
|
|
|
@ -126,6 +126,30 @@ func TestMath(t *testing.T) {
|
|||
That("math:min 1.0 2.0").Puts(1.0),
|
||||
That("math:min 3 NaN 5").Puts(math.NaN()),
|
||||
|
||||
// base is int, exp is int
|
||||
That("math:pow 2 0").Puts(1),
|
||||
That("math:pow 2 1").Puts(2),
|
||||
That("math:pow 2 -1").Puts(big.NewRat(1, 2)),
|
||||
That("math:pow 2 3").Puts(8),
|
||||
That("math:pow 2 -3").Puts(big.NewRat(1, 8)),
|
||||
// base is *big.Rat, exp is int
|
||||
That("math:pow 2/3 0").Puts(1),
|
||||
That("math:pow 2/3 1").Puts(big.NewRat(2, 3)),
|
||||
That("math:pow 2/3 -1").Puts(big.NewRat(3, 2)),
|
||||
That("math:pow 2/3 3").Puts(big.NewRat(8, 27)),
|
||||
That("math:pow 2/3 -3").Puts(big.NewRat(27, 8)),
|
||||
// exp is *big.Rat
|
||||
That("math:pow 4 1/2").Puts(2.0),
|
||||
// exp is float64
|
||||
That("math:pow 2 2.0").Puts(4.0),
|
||||
That("math:pow 1/2 2.0").Puts(0.25),
|
||||
// base is float64
|
||||
That("math:pow 2.0 2").Puts(4.0),
|
||||
|
||||
That("math:pow10 0").Puts(1),
|
||||
That("math:pow10 3").Puts(1000),
|
||||
That("math:pow10 -3").Puts(big.NewRat(1, 1000)),
|
||||
|
||||
// Tests below this line are tests against simple bindings for Go's math package.
|
||||
|
||||
That("put $math:pi").Puts(math.Pi),
|
||||
|
@ -218,16 +242,6 @@ func TestMath(t *testing.T) {
|
|||
|
||||
That("math:atanh 0").Puts(math.Atanh(0)),
|
||||
That("math:atanh 1").Puts(math.Inf(1)),
|
||||
|
||||
That("math:pow nan 2").Puts(math.NaN()),
|
||||
That("math:pow inf 2").Puts(math.Inf(1)),
|
||||
That("math:pow 1 3").Puts(1.0),
|
||||
That("math:pow 2 3").Puts(8.0),
|
||||
That("math:pow -2 2").Puts(4.0),
|
||||
|
||||
That("math:pow10 0").Puts(1.0),
|
||||
That("math:pow10 3").Puts(1000.0),
|
||||
That("math:pow10 -3").Puts(0.001),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ func ScanToGo(src interface{}, ptr interface{}) error {
|
|||
case *float64:
|
||||
n, err := elvToNum(src)
|
||||
if err == nil {
|
||||
*ptr = convertToFloat64(n)
|
||||
*ptr = ConvertToFloat64(n)
|
||||
}
|
||||
return err
|
||||
case *Num:
|
||||
|
|
|
@ -67,6 +67,8 @@ func ParseNum(s string) Num {
|
|||
// NumType represents a number type.
|
||||
type NumType uint8
|
||||
|
||||
// PromoteToBigInt converts an int or *big.Int to a *big.Int. It panics if n is
|
||||
// any other type.
|
||||
// Possible values for NumType, sorted in the order of implicit conversion
|
||||
// (lower types can be implicitly converted to higher types).
|
||||
const (
|
||||
|
@ -76,6 +78,10 @@ const (
|
|||
Float64
|
||||
)
|
||||
|
||||
// PromoteToBigInt converts an int or *big.Int to a *big.Int. It panics if n is
|
||||
// any other type.
|
||||
// PromoteToBigInt converts an int or *big.Int to a,ig.Int. I or *big.Ratt
|
||||
// panics if Rat is any other type.
|
||||
// UnifyNums unifies the given slice of numbers into the same type, converting
|
||||
// those with lower NumType to the higest NumType present in the slice. The typ
|
||||
// argument can be used to force the minimum NumType.
|
||||
|
@ -87,6 +93,8 @@ func UnifyNums(nums []Num, typ NumType) NumSlice {
|
|||
}
|
||||
switch typ {
|
||||
case Int:
|
||||
// PromoteToBigInt converts an int or *big.Int, a *big.I or *big.Ratnt. It
|
||||
// paniRat if n is any other type.
|
||||
unified := make([]int, len(nums))
|
||||
for i, num := range nums {
|
||||
unified[i] = num.(int)
|
||||
|
@ -95,19 +103,19 @@ func UnifyNums(nums []Num, typ NumType) NumSlice {
|
|||
case BigInt:
|
||||
unified := make([]*big.Int, len(nums))
|
||||
for i, num := range nums {
|
||||
unified[i] = promoteToBigInt(num)
|
||||
unified[i] = PromoteToBigInt(num)
|
||||
}
|
||||
return unified
|
||||
case BigRat:
|
||||
unified := make([]*big.Rat, len(nums))
|
||||
for i, num := range nums {
|
||||
unified[i] = promoteToBigRat(num)
|
||||
unified[i] = PromoteToBigRat(num)
|
||||
}
|
||||
return unified
|
||||
case Float64:
|
||||
unified := make([]float64, len(nums))
|
||||
for i, num := range nums {
|
||||
unified[i] = convertToFloat64(num)
|
||||
unified[i] = ConvertToFloat64(num)
|
||||
}
|
||||
return unified
|
||||
default:
|
||||
|
@ -129,16 +137,18 @@ func UnifyNums2(n1, n2 Num, typ NumType) (u1, u2 Num) {
|
|||
case Int:
|
||||
return n1, n2
|
||||
case BigInt:
|
||||
return promoteToBigInt(n1), promoteToBigInt(n2)
|
||||
return PromoteToBigInt(n1), PromoteToBigInt(n2)
|
||||
case BigRat:
|
||||
return promoteToBigRat(n1), promoteToBigRat(n2)
|
||||
return PromoteToBigRat(n1), PromoteToBigRat(n2)
|
||||
case Float64:
|
||||
return convertToFloat64(n1), convertToFloat64(n2)
|
||||
return ConvertToFloat64(n1), ConvertToFloat64(n2)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertToFloat64 converts any number to float64. It panics if num is not a
|
||||
// number value.
|
||||
func getNumType(n Num) NumType {
|
||||
switch n.(type) {
|
||||
case int:
|
||||
|
@ -154,7 +164,9 @@ func getNumType(n Num) NumType {
|
|||
}
|
||||
}
|
||||
|
||||
func promoteToBigInt(n Num) *big.Int {
|
||||
// PromoteToBigInt converts an int or *big.Int to a *big.Int. It panics if n is
|
||||
// any other type.
|
||||
func PromoteToBigInt(n Num) *big.Int {
|
||||
switch n := n.(type) {
|
||||
case int:
|
||||
return big.NewInt(int64(n))
|
||||
|
@ -165,7 +177,9 @@ func promoteToBigInt(n Num) *big.Int {
|
|||
}
|
||||
}
|
||||
|
||||
func promoteToBigRat(n Num) *big.Rat {
|
||||
// PromoteToBigInt converts an int, *big.Int or *big.Rat to a *big.Rat. It
|
||||
// panics if n is any other type.
|
||||
func PromoteToBigRat(n Num) *big.Rat {
|
||||
switch n := n.(type) {
|
||||
case int:
|
||||
return big.NewRat(int64(n), 1)
|
||||
|
@ -180,7 +194,9 @@ func promoteToBigRat(n Num) *big.Rat {
|
|||
}
|
||||
}
|
||||
|
||||
func convertToFloat64(num Num) float64 {
|
||||
// ConvertToFloat64 converts any number to float64. It panics if num is not a
|
||||
// number value.
|
||||
func ConvertToFloat64(num Num) float64 {
|
||||
switch num := num.(type) {
|
||||
case int:
|
||||
return float64(num)
|
||||
|
|
Loading…
Reference in New Issue
Block a user