oficina / sheets Public

Clone
git clone https://amadan.net/oficina/sheets

Plain git — no account needed to clone.

Download

Download this file

1package calc
2
3import "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
11func 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!.
25func 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.
51func 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
77func 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
85func 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
93func 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
101func 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
115func 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.
140func 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.
155func 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