| 1 | package calc |
| 2 | |
| 3 | import "math" |
| 4 | |
| 5 | // fn_math_round.go: ROUND, ROUNDUP, ROUNDDOWN, ABS, SQRT, POWER, MOD — |
| 6 | // split from fn_math.go (SUM/AVERAGE/COUNT/COUNTA/MIN/MAX) to stay under |
| 7 | // the 300-line cap; still the same "math family" the plan groups |
| 8 | // together, just none of these need the literal-vs-reference machinery |
| 9 | // fn_math.go carries. |
| 10 | |
| 11 | func init() { |
| 12 | Register("ROUND", fnRound) |
| 13 | Register("ROUNDUP", fnRoundUp) |
| 14 | Register("ROUNDDOWN", fnRoundDown) |
| 15 | Register("ABS", fnAbs) |
| 16 | Register("SQRT", fnSqrt) |
| 17 | Register("POWER", fnPower) |
| 18 | Register("MOD", fnMod) |
| 19 | } |
| 20 | |
| 21 | // twoNumArgs evaluates the common "exactly two numeric arguments" shape |
| 22 | // shared by ROUND/ROUNDUP/ROUNDDOWN/POWER/MOD: an error argument |
| 23 | // propagates left-most-wins, a wrong argument count or a non-coercible |
| 24 | // argument is #VALUE!. |
| 25 | func twoNumArgs(args []Value) (a, b float64, errv Value, ok bool) { |
| 26 | if len(args) != 2 { |
| 27 | return 0, 0, errValue(ErrValue), false |
| 28 | } |
| 29 | if args[0].Kind == KindErr { |
| 30 | return 0, 0, args[0], false |
| 31 | } |
| 32 | if args[1].Kind == KindErr { |
| 33 | return 0, 0, args[1], false |
| 34 | } |
| 35 | x, ek := toNumber(args[0]) |
| 36 | if ek != ErrNone { |
| 37 | return 0, 0, errValue(ek), false |
| 38 | } |
| 39 | y, ek := toNumber(args[1]) |
| 40 | if ek != ErrNone { |
| 41 | return 0, 0, errValue(ek), false |
| 42 | } |
| 43 | return x, y, Value{}, true |
| 44 | } |
| 45 | |
| 46 | // roundAt implements the shared shape behind ROUND (mode 0, half away |
| 47 | // from zero — Excel's own rounding rule, not banker's rounding), |
| 48 | // ROUNDUP (mode 1, always away from zero regardless of the dropped |
| 49 | // digits) and ROUNDDOWN (mode 2, always toward zero — truncation). |
| 50 | // digits may be negative (round to tens, hundreds, ...), matching Excel. |
| 51 | func roundAt(x float64, digits int, mode int) float64 { |
| 52 | mult := math.Pow(10, float64(digits)) |
| 53 | v := x * mult |
| 54 | switch mode { |
| 55 | case 0: |
| 56 | if v >= 0 { |
| 57 | v = math.Floor(v + 0.5) |
| 58 | } else { |
| 59 | v = math.Ceil(v - 0.5) |
| 60 | } |
| 61 | case 1: |
| 62 | if v >= 0 { |
| 63 | v = math.Ceil(v) |
| 64 | } else { |
| 65 | v = math.Floor(v) |
| 66 | } |
| 67 | default: |
| 68 | if v >= 0 { |
| 69 | v = math.Floor(v) |
| 70 | } else { |
| 71 | v = math.Ceil(v) |
| 72 | } |
| 73 | } |
| 74 | return v / mult |
| 75 | } |
| 76 | |
| 77 | func fnRound(args []Value) Value { |
| 78 | x, d, errv, ok := twoNumArgs(args) |
| 79 | if !ok { |
| 80 | return errv |
| 81 | } |
| 82 | return Value{Kind: KindNum, Num: roundAt(x, int(d), 0)} |
| 83 | } |
| 84 | |
| 85 | func fnRoundUp(args []Value) Value { |
| 86 | x, d, errv, ok := twoNumArgs(args) |
| 87 | if !ok { |
| 88 | return errv |
| 89 | } |
| 90 | return Value{Kind: KindNum, Num: roundAt(x, int(d), 1)} |
| 91 | } |
| 92 | |
| 93 | func fnRoundDown(args []Value) Value { |
| 94 | x, d, errv, ok := twoNumArgs(args) |
| 95 | if !ok { |
| 96 | return errv |
| 97 | } |
| 98 | return Value{Kind: KindNum, Num: roundAt(x, int(d), 2)} |
| 99 | } |
| 100 | |
| 101 | func fnAbs(args []Value) Value { |
| 102 | if len(args) != 1 { |
| 103 | return errValue(ErrValue) |
| 104 | } |
| 105 | if args[0].Kind == KindErr { |
| 106 | return args[0] |
| 107 | } |
| 108 | x, ek := toNumber(args[0]) |
| 109 | if ek != ErrNone { |
| 110 | return errValue(ek) |
| 111 | } |
| 112 | return Value{Kind: KindNum, Num: math.Abs(x)} |
| 113 | } |
| 114 | |
| 115 | func fnSqrt(args []Value) Value { |
| 116 | if len(args) != 1 { |
| 117 | return errValue(ErrValue) |
| 118 | } |
| 119 | if args[0].Kind == KindErr { |
| 120 | return args[0] |
| 121 | } |
| 122 | x, ek := toNumber(args[0]) |
| 123 | if ek != ErrNone { |
| 124 | return errValue(ek) |
| 125 | } |
| 126 | if x < 0 { |
| 127 | return errValue(ErrNum) |
| 128 | } |
| 129 | return Value{Kind: KindNum, Num: math.Sqrt(x)} |
| 130 | } |
| 131 | |
| 132 | // fnPower guards its result, not just its inputs, unlike fnSqrt: a |
| 133 | // negative base with a non-integer exponent (POWER(-8, 1.0/3) — a cube |
| 134 | // root written as a fractional power) makes math.Pow return NaN for |
| 135 | // inputs that are each individually fine on their own, and an |
| 136 | // overflowing result (POWER(10, 400)) returns +Inf. Both are #NUM! in |
| 137 | // Excel, never a value a cell should hold — Value has no way to |
| 138 | // represent "not a number" other than the error kind that means exactly |
| 139 | // that. |
| 140 | func fnPower(args []Value) Value { |
| 141 | x, y, errv, ok := twoNumArgs(args) |
| 142 | if !ok { |
| 143 | return errv |
| 144 | } |
| 145 | r := math.Pow(x, y) |
| 146 | if math.IsNaN(r) || math.IsInf(r, 0) { |
| 147 | return errValue(ErrNum) |
| 148 | } |
| 149 | return Value{Kind: KindNum, Num: r} |
| 150 | } |
| 151 | |
| 152 | // fnMod implements MOD the way Excel does: the result takes the sign of |
| 153 | // the divisor (n - d*floor(n/d)), not the sign of the dividend the way |
| 154 | // Go's own % operator would give — MOD(-10,3) is 2, not -1. |
| 155 | func fnMod(args []Value) Value { |
| 156 | x, y, errv, ok := twoNumArgs(args) |
| 157 | if !ok { |
| 158 | return errv |
| 159 | } |
| 160 | if y == 0 { |
| 161 | return errValue(ErrDiv0) |
| 162 | } |
| 163 | return Value{Kind: KindNum, Num: x - y*math.Floor(x/y)} |
| 164 | } |
| 165 | |