github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/tpl/math/math.go (about) 1 // Copyright 2017 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 // Package math provides template functions for mathematical operations. 15 package math 16 17 import ( 18 "errors" 19 "fmt" 20 "math" 21 "reflect" 22 "sync/atomic" 23 24 _math "github.com/gohugoio/hugo/common/math" 25 "github.com/spf13/cast" 26 ) 27 28 var ( 29 errMustTwoNumbersError = errors.New("must provide at least two numbers") 30 errMustOneNumberError = errors.New("must provide at least one number") 31 ) 32 33 // New returns a new instance of the math-namespaced template functions. 34 func New() *Namespace { 35 return &Namespace{} 36 } 37 38 // Namespace provides template functions for the "math" namespace. 39 type Namespace struct{} 40 41 // Abs returns the absolute value of n. 42 func (ns *Namespace) Abs(n any) (float64, error) { 43 af, err := cast.ToFloat64E(n) 44 if err != nil { 45 return 0, errors.New("the math.Abs function requires a numeric argument") 46 } 47 48 return math.Abs(af), nil 49 } 50 51 // Add adds the multivalued addends n1 and n2 or more values. 52 func (ns *Namespace) Add(inputs ...any) (any, error) { 53 return ns.doArithmetic(inputs, '+') 54 } 55 56 // Ceil returns the least integer value greater than or equal to n. 57 func (ns *Namespace) Ceil(n any) (float64, error) { 58 xf, err := cast.ToFloat64E(n) 59 if err != nil { 60 return 0, errors.New("Ceil operator can't be used with non-float value") 61 } 62 63 return math.Ceil(xf), nil 64 } 65 66 // Div divides n1 by n2. 67 func (ns *Namespace) Div(inputs ...any) (any, error) { 68 return ns.doArithmetic(inputs, '/') 69 } 70 71 // Floor returns the greatest integer value less than or equal to n. 72 func (ns *Namespace) Floor(n any) (float64, error) { 73 xf, err := cast.ToFloat64E(n) 74 if err != nil { 75 return 0, errors.New("Floor operator can't be used with non-float value") 76 } 77 78 return math.Floor(xf), nil 79 } 80 81 // Log returns the natural logarithm of the number n. 82 func (ns *Namespace) Log(n any) (float64, error) { 83 af, err := cast.ToFloat64E(n) 84 if err != nil { 85 return 0, errors.New("Log operator can't be used with non integer or float value") 86 } 87 88 return math.Log(af), nil 89 } 90 91 // Max returns the greater of all numbers in inputs. Any slices in inputs are flattened. 92 func (ns *Namespace) Max(inputs ...any) (maximum float64, err error) { 93 return ns.applyOpToScalarsOrSlices("Max", math.Max, inputs...) 94 } 95 96 // Min returns the smaller of all numbers in inputs. Any slices in inputs are flattened. 97 func (ns *Namespace) Min(inputs ...any) (minimum float64, err error) { 98 return ns.applyOpToScalarsOrSlices("Min", math.Min, inputs...) 99 } 100 101 // Sum returns the sum of all numbers in inputs. Any slices in inputs are flattened. 102 func (ns *Namespace) Sum(inputs ...any) (sum float64, err error) { 103 fn := func(x, y float64) float64 { 104 return x + y 105 } 106 return ns.applyOpToScalarsOrSlices("Sum", fn, inputs...) 107 } 108 109 // Product returns the product of all numbers in inputs. Any slices in inputs are flattened. 110 func (ns *Namespace) Product(inputs ...any) (product float64, err error) { 111 fn := func(x, y float64) float64 { 112 return x * y 113 } 114 return ns.applyOpToScalarsOrSlices("Product", fn, inputs...) 115 } 116 117 // Mod returns n1 % n2. 118 func (ns *Namespace) Mod(n1, n2 any) (int64, error) { 119 ai, erra := cast.ToInt64E(n1) 120 bi, errb := cast.ToInt64E(n2) 121 122 if erra != nil || errb != nil { 123 return 0, errors.New("modulo operator can't be used with non integer value") 124 } 125 126 if bi == 0 { 127 return 0, errors.New("the number can't be divided by zero at modulo operation") 128 } 129 130 return ai % bi, nil 131 } 132 133 // ModBool returns the boolean of n1 % n2. If n1 % n2 == 0, return true. 134 func (ns *Namespace) ModBool(n1, n2 any) (bool, error) { 135 res, err := ns.Mod(n1, n2) 136 if err != nil { 137 return false, err 138 } 139 140 return res == int64(0), nil 141 } 142 143 // Mul multiplies the multivalued numbers n1 and n2 or more values. 144 func (ns *Namespace) Mul(inputs ...any) (any, error) { 145 return ns.doArithmetic(inputs, '*') 146 } 147 148 // Pow returns n1 raised to the power of n2. 149 func (ns *Namespace) Pow(n1, n2 any) (float64, error) { 150 af, erra := cast.ToFloat64E(n1) 151 bf, errb := cast.ToFloat64E(n2) 152 153 if erra != nil || errb != nil { 154 return 0, errors.New("Pow operator can't be used with non-float value") 155 } 156 157 return math.Pow(af, bf), nil 158 } 159 160 // Round returns the integer nearest to n, rounding half away from zero. 161 func (ns *Namespace) Round(n any) (float64, error) { 162 xf, err := cast.ToFloat64E(n) 163 if err != nil { 164 return 0, errors.New("Round operator can't be used with non-float value") 165 } 166 167 return _round(xf), nil 168 } 169 170 // Sqrt returns the square root of the number n. 171 func (ns *Namespace) Sqrt(n any) (float64, error) { 172 af, err := cast.ToFloat64E(n) 173 if err != nil { 174 return 0, errors.New("Sqrt operator can't be used with non integer or float value") 175 } 176 177 return math.Sqrt(af), nil 178 } 179 180 // Sub subtracts multivalued. 181 func (ns *Namespace) Sub(inputs ...any) (any, error) { 182 return ns.doArithmetic(inputs, '-') 183 } 184 185 func (ns *Namespace) applyOpToScalarsOrSlices(opName string, op func(x, y float64) float64, inputs ...any) (result float64, err error) { 186 var i int 187 var hasValue bool 188 for _, input := range inputs { 189 var values []float64 190 var isSlice bool 191 values, isSlice, err = ns.toFloatsE(input) 192 if err != nil { 193 err = fmt.Errorf("%s operator can't be used with non-float values", opName) 194 return 195 } 196 hasValue = hasValue || len(values) > 0 || isSlice 197 for _, value := range values { 198 i++ 199 if i == 1 { 200 result = value 201 continue 202 } 203 result = op(result, value) 204 } 205 } 206 207 if !hasValue { 208 err = errMustOneNumberError 209 return 210 } 211 return 212 213 } 214 215 func (ns *Namespace) toFloatsE(v any) ([]float64, bool, error) { 216 vv := reflect.ValueOf(v) 217 switch vv.Kind() { 218 case reflect.Slice, reflect.Array: 219 var floats []float64 220 for i := 0; i < vv.Len(); i++ { 221 f, err := cast.ToFloat64E(vv.Index(i).Interface()) 222 if err != nil { 223 return nil, true, err 224 } 225 floats = append(floats, f) 226 } 227 return floats, true, nil 228 default: 229 f, err := cast.ToFloat64E(v) 230 if err != nil { 231 return nil, false, err 232 } 233 return []float64{f}, false, nil 234 } 235 } 236 237 func (ns *Namespace) doArithmetic(inputs []any, operation rune) (value any, err error) { 238 if len(inputs) < 2 { 239 return nil, errMustTwoNumbersError 240 } 241 value = inputs[0] 242 for i := 1; i < len(inputs); i++ { 243 value, err = _math.DoArithmetic(value, inputs[i], operation) 244 if err != nil { 245 return 246 } 247 } 248 return 249 } 250 251 var counter uint64 252 253 // Counter increments and returns a global counter. 254 // This was originally added to be used in tests where now.UnixNano did not 255 // have the needed precision (especially on Windows). 256 // Note that given the parallel nature of Hugo, you cannot use this to get sequences of numbers, 257 // and the counter will reset on new builds. 258 // <docsmeta>{"identifiers": ["now.UnixNano"] }</docsmeta> 259 func (ns *Namespace) Counter() uint64 { 260 return atomic.AddUint64(&counter, uint64(1)) 261 }