numeric.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. package sprig
  2. import (
  3. "fmt"
  4. "math"
  5. "math/rand"
  6. "reflect"
  7. "strconv"
  8. "strings"
  9. )
  10. // toFloat64 converts a value to a 64-bit float.
  11. // It handles various input types:
  12. // - string: parsed as a float, returns 0 if parsing fails
  13. // - integer types: converted to float64
  14. // - unsigned integer types: converted to float64
  15. // - float types: returned as is
  16. // - bool: true becomes 1.0, false becomes 0.0
  17. // - other types: returns 0.0
  18. //
  19. // Parameters:
  20. // - v: The value to convert to float64
  21. //
  22. // Returns:
  23. // - float64: The converted value
  24. func toFloat64(v any) float64 {
  25. if str, ok := v.(string); ok {
  26. iv, err := strconv.ParseFloat(str, 64)
  27. if err != nil {
  28. return 0
  29. }
  30. return iv
  31. }
  32. val := reflect.Indirect(reflect.ValueOf(v))
  33. switch val.Kind() {
  34. case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
  35. return float64(val.Int())
  36. case reflect.Uint8, reflect.Uint16, reflect.Uint32:
  37. return float64(val.Uint())
  38. case reflect.Uint, reflect.Uint64:
  39. return float64(val.Uint())
  40. case reflect.Float32, reflect.Float64:
  41. return val.Float()
  42. case reflect.Bool:
  43. if val.Bool() {
  44. return 1
  45. }
  46. return 0
  47. default:
  48. return 0
  49. }
  50. }
  51. // toInt converts a value to a 32-bit integer.
  52. // This is a wrapper around toInt64 that casts the result to int.
  53. //
  54. // Parameters:
  55. // - v: The value to convert to int
  56. //
  57. // Returns:
  58. // - int: The converted value
  59. func toInt(v any) int {
  60. // It's not optimal. But I don't want duplicate toInt64 code.
  61. return int(toInt64(v))
  62. }
  63. // toInt64 converts a value to a 64-bit integer.
  64. // It handles various input types:
  65. // - string: parsed as an integer, returns 0 if parsing fails
  66. // - integer types: converted to int64
  67. // - unsigned integer types: converted to int64 (values > MaxInt64 become MaxInt64)
  68. // - float types: truncated to int64
  69. // - bool: true becomes 1, false becomes 0
  70. // - other types: returns 0
  71. func toInt64(v any) int64 {
  72. if str, ok := v.(string); ok {
  73. iv, err := strconv.ParseInt(str, 10, 64)
  74. if err != nil {
  75. return 0
  76. }
  77. return iv
  78. }
  79. val := reflect.Indirect(reflect.ValueOf(v))
  80. switch val.Kind() {
  81. case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
  82. return val.Int()
  83. case reflect.Uint8, reflect.Uint16, reflect.Uint32:
  84. return int64(val.Uint())
  85. case reflect.Uint, reflect.Uint64:
  86. tv := val.Uint()
  87. if tv <= math.MaxInt64 {
  88. return int64(tv)
  89. }
  90. // TODO: What is the sensible thing to do here?
  91. return math.MaxInt64
  92. case reflect.Float32, reflect.Float64:
  93. return int64(val.Float())
  94. case reflect.Bool:
  95. if val.Bool() {
  96. return 1
  97. }
  98. return 0
  99. default:
  100. return 0
  101. }
  102. }
  103. // add1 increments a value by 1.
  104. // The input is first converted to int64 using toInt64.
  105. //
  106. // Parameters:
  107. // - i: The value to increment
  108. //
  109. // Returns:
  110. // - int64: The incremented value
  111. func add1(i any) int64 {
  112. return toInt64(i) + 1
  113. }
  114. // add sums all the provided values.
  115. // All inputs are converted to int64 using toInt64 before addition.
  116. //
  117. // Parameters:
  118. // - i: A variadic list of values to sum
  119. //
  120. // Returns:
  121. // - int64: The sum of all values
  122. func add(i ...any) int64 {
  123. var a int64
  124. for _, b := range i {
  125. a += toInt64(b)
  126. }
  127. return a
  128. }
  129. // sub subtracts the second value from the first.
  130. // Both inputs are converted to int64 using toInt64 before subtraction.
  131. //
  132. // Parameters:
  133. // - a: The value to subtract from
  134. // - b: The value to subtract
  135. //
  136. // Returns:
  137. // - int64: The result of a - b
  138. func sub(a, b any) int64 {
  139. return toInt64(a) - toInt64(b)
  140. }
  141. // div divides the first value by the second.
  142. // Both inputs are converted to int64 using toInt64 before division.
  143. // Note: This performs integer division, so the result is truncated.
  144. //
  145. // Parameters:
  146. // - a: The dividend
  147. // - b: The divisor
  148. //
  149. // Returns:
  150. // - int64: The result of a / b
  151. //
  152. // Panics:
  153. // - If b evaluates to 0 (division by zero)
  154. func div(a, b any) int64 {
  155. return toInt64(a) / toInt64(b)
  156. }
  157. // mod returns the remainder of dividing the first value by the second.
  158. // Both inputs are converted to int64 using toInt64 before the modulo operation.
  159. //
  160. // Parameters:
  161. // - a: The dividend
  162. // - b: The divisor
  163. //
  164. // Returns:
  165. // - int64: The remainder of a / b
  166. //
  167. // Panics:
  168. // - If b evaluates to 0 (modulo by zero)
  169. func mod(a, b any) int64 {
  170. return toInt64(a) % toInt64(b)
  171. }
  172. // mul multiplies all the provided values.
  173. // All inputs are converted to int64 using toInt64 before multiplication.
  174. //
  175. // Parameters:
  176. // - a: The first value to multiply
  177. // - v: Additional values to multiply with a
  178. //
  179. // Returns:
  180. // - int64: The product of all values
  181. func mul(a any, v ...any) int64 {
  182. val := toInt64(a)
  183. for _, b := range v {
  184. val = val * toInt64(b)
  185. }
  186. return val
  187. }
  188. // randInt generates a random integer between min (inclusive) and max (exclusive).
  189. //
  190. // Parameters:
  191. // - min: The lower bound (inclusive)
  192. // - max: The upper bound (exclusive)
  193. //
  194. // Returns:
  195. // - int: A random integer in the range [min, max)
  196. //
  197. // Panics:
  198. // - If max <= min (via rand.Intn)
  199. func randInt(min, max int) int {
  200. return rand.Intn(max-min) + min
  201. }
  202. // maxAsInt64 returns the maximum value from a list of values as an int64.
  203. // All inputs are converted to int64 using toInt64 before comparison.
  204. //
  205. // Parameters:
  206. // - a: The first value to compare
  207. // - i: Additional values to compare
  208. //
  209. // Returns:
  210. // - int64: The maximum value from all inputs
  211. func maxAsInt64(a any, i ...any) int64 {
  212. aa := toInt64(a)
  213. for _, b := range i {
  214. bb := toInt64(b)
  215. if bb > aa {
  216. aa = bb
  217. }
  218. }
  219. return aa
  220. }
  221. // maxAsFloat64 returns the maximum value from a list of values as a float64.
  222. // All inputs are converted to float64 using toFloat64 before comparison.
  223. //
  224. // Parameters:
  225. // - a: The first value to compare
  226. // - i: Additional values to compare
  227. //
  228. // Returns:
  229. // - float64: The maximum value from all inputs
  230. func maxAsFloat64(a any, i ...any) float64 {
  231. m := toFloat64(a)
  232. for _, b := range i {
  233. m = math.Max(m, toFloat64(b))
  234. }
  235. return m
  236. }
  237. // minAsInt64 returns the minimum value from a list of values as an int64.
  238. // All inputs are converted to int64 using toInt64 before comparison.
  239. //
  240. // Parameters:
  241. // - a: The first value to compare
  242. // - i: Additional values to compare
  243. //
  244. // Returns:
  245. // - int64: The minimum value from all inputs
  246. func minAsInt64(a any, i ...any) int64 {
  247. aa := toInt64(a)
  248. for _, b := range i {
  249. bb := toInt64(b)
  250. if bb < aa {
  251. aa = bb
  252. }
  253. }
  254. return aa
  255. }
  256. // minAsFloat64 returns the minimum value from a list of values as a float64.
  257. // All inputs are converted to float64 using toFloat64 before comparison.
  258. //
  259. // Parameters:
  260. // - a: The first value to compare
  261. // - i: Additional values to compare
  262. //
  263. // Returns:
  264. // - float64: The minimum value from all inputs
  265. func minAsFloat64(a any, i ...any) float64 {
  266. m := toFloat64(a)
  267. for _, b := range i {
  268. m = math.Min(m, toFloat64(b))
  269. }
  270. return m
  271. }
  272. // until generates a sequence of integers from 0 to count (exclusive).
  273. // If count is negative, it generates a sequence from 0 to count (inclusive) with step -1.
  274. //
  275. // Parameters:
  276. // - count: The end value (exclusive if positive, inclusive if negative)
  277. //
  278. // Returns:
  279. // - []int: A slice containing the generated sequence
  280. func until(count int) []int {
  281. step := 1
  282. if count < 0 {
  283. step = -1
  284. }
  285. return untilStep(0, count, step)
  286. }
  287. // untilStep generates a sequence of integers from start to stop with the specified step.
  288. // The sequence is generated as follows:
  289. // - If step is 0, returns an empty slice
  290. // - If stop < start and step < 0, generates a decreasing sequence from start to stop (exclusive)
  291. // - If stop > start and step > 0, generates an increasing sequence from start to stop (exclusive)
  292. // - Otherwise, returns an empty slice
  293. //
  294. // Parameters:
  295. // - start: The starting value (inclusive)
  296. // - stop: The ending value (exclusive)
  297. // - step: The increment between values
  298. //
  299. // Returns:
  300. // - []int: A slice containing the generated sequence
  301. //
  302. // Panics:
  303. // - If the number of iterations would exceed loopExecutionLimit
  304. func untilStep(start, stop, step int) []int {
  305. var v []int
  306. if step == 0 {
  307. return v
  308. }
  309. iterations := math.Abs(float64(stop)-float64(start)) / float64(step)
  310. if iterations > loopExecutionLimit {
  311. panic(fmt.Sprintf("too many iterations in untilStep; max allowed is %d, got %f", loopExecutionLimit, iterations))
  312. }
  313. if stop < start {
  314. if step >= 0 {
  315. return v
  316. }
  317. for i := start; i > stop; i += step {
  318. v = append(v, i)
  319. }
  320. return v
  321. }
  322. if step <= 0 {
  323. return v
  324. }
  325. for i := start; i < stop; i += step {
  326. v = append(v, i)
  327. }
  328. return v
  329. }
  330. // floor returns the greatest integer value less than or equal to the input.
  331. // The input is first converted to float64 using toFloat64.
  332. //
  333. // Parameters:
  334. // - a: The value to floor
  335. //
  336. // Returns:
  337. // - float64: The greatest integer value less than or equal to a
  338. func floor(a any) float64 {
  339. return math.Floor(toFloat64(a))
  340. }
  341. // ceil returns the least integer value greater than or equal to the input.
  342. // The input is first converted to float64 using toFloat64.
  343. //
  344. // Parameters:
  345. // - a: The value to ceil
  346. //
  347. // Returns:
  348. // - float64: The least integer value greater than or equal to a
  349. func ceil(a any) float64 {
  350. return math.Ceil(toFloat64(a))
  351. }
  352. // round rounds a number to a specified number of decimal places.
  353. // The input is first converted to float64 using toFloat64.
  354. //
  355. // Parameters:
  356. // - a: The value to round
  357. // - p: The number of decimal places to round to
  358. // - rOpt: Optional rounding threshold (default is 0.5)
  359. //
  360. // Returns:
  361. // - float64: The rounded value
  362. //
  363. // Examples:
  364. // - round(3.14159, 2) returns 3.14
  365. // - round(3.14159, 2, 0.6) returns 3.14 (only rounds up if fraction ≥ 0.6)
  366. func round(a any, p int, rOpt ...float64) float64 {
  367. roundOn := .5
  368. if len(rOpt) > 0 {
  369. roundOn = rOpt[0]
  370. }
  371. val := toFloat64(a)
  372. places := toFloat64(p)
  373. var round float64
  374. pow := math.Pow(10, places)
  375. digit := pow * val
  376. _, div := math.Modf(digit)
  377. if div >= roundOn {
  378. round = math.Ceil(digit)
  379. } else {
  380. round = math.Floor(digit)
  381. }
  382. return round / pow
  383. }
  384. // toDecimal converts a value from octal to decimal.
  385. // The input is first converted to a string using fmt.Sprint, then parsed as an octal number.
  386. // If the parsing fails, it returns 0.
  387. //
  388. // Parameters:
  389. // - v: The octal value to convert
  390. //
  391. // Returns:
  392. // - int64: The decimal representation of the octal value
  393. func toDecimal(v any) int64 {
  394. result, err := strconv.ParseInt(fmt.Sprint(v), 8, 64)
  395. if err != nil {
  396. return 0
  397. }
  398. return result
  399. }
  400. // atoi converts a string to an integer.
  401. // If the conversion fails, it returns 0.
  402. //
  403. // Parameters:
  404. // - a: The string to convert
  405. //
  406. // Returns:
  407. // - int: The integer value of the string
  408. func atoi(a string) int {
  409. i, _ := strconv.Atoi(a)
  410. return i
  411. }
  412. // seq generates a sequence of integers and returns them as a space-delimited string.
  413. // The behavior depends on the number of parameters:
  414. // - 0 params: Returns an empty string
  415. // - 1 param: Generates sequence from 1 to param[0]
  416. // - 2 params: Generates sequence from param[0] to param[1]
  417. // - 3 params: Generates sequence from param[0] to param[2] with step param[1]
  418. //
  419. // If the end is less than the start, the sequence will be decreasing unless
  420. // a positive step is explicitly provided (which would result in an empty string).
  421. //
  422. // Parameters:
  423. // - params: Variable number of integers defining the sequence
  424. //
  425. // Returns:
  426. // - string: A space-delimited string of the generated sequence
  427. func seq(params ...int) string {
  428. increment := 1
  429. switch len(params) {
  430. case 0:
  431. return ""
  432. case 1:
  433. start := 1
  434. end := params[0]
  435. if end < start {
  436. increment = -1
  437. }
  438. return intArrayToString(untilStep(start, end+increment, increment), " ")
  439. case 3:
  440. start := params[0]
  441. end := params[2]
  442. step := params[1]
  443. if end < start {
  444. increment = -1
  445. if step > 0 {
  446. return ""
  447. }
  448. }
  449. return intArrayToString(untilStep(start, end+increment, step), " ")
  450. case 2:
  451. start := params[0]
  452. end := params[1]
  453. step := 1
  454. if end < start {
  455. step = -1
  456. }
  457. return intArrayToString(untilStep(start, end+step, step), " ")
  458. default:
  459. return ""
  460. }
  461. }
  462. // intArrayToString converts a slice of integers to a space-delimited string.
  463. // The function removes the square brackets that would normally appear when
  464. // converting a slice to a string.
  465. //
  466. // Parameters:
  467. // - slice: The slice of integers to convert
  468. // - delimiter: The delimiter to use between elements
  469. //
  470. // Returns:
  471. // - string: A delimited string representation of the integer slice
  472. func intArrayToString(slice []int, delimiter string) string {
  473. return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(slice)), delimiter), "[]")
  474. }