1// Copyright 2020 CUE Authors
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//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package adt
16
17import (
18	"math/big"
19
20	"github.com/cockroachdb/apd/v2"
21)
22
23var apdCtx apd.Context
24
25func init() {
26	apdCtx = apd.BaseContext
27	apdCtx.Precision = 24
28}
29
30func (n *Num) Impl() *apd.Decimal {
31	return &n.X
32}
33
34func (n *Num) Negative() bool {
35	return n.X.Negative
36}
37
38func (a *Num) Cmp(b *Num) int {
39	return a.X.Cmp(&b.X)
40}
41
42func (c *OpContext) Add(a, b *Num) Value {
43	return numOp(c, apdCtx.Add, a, b)
44}
45
46func (c *OpContext) Sub(a, b *Num) Value {
47	return numOp(c, apdCtx.Sub, a, b)
48}
49
50func (c *OpContext) Mul(a, b *Num) Value {
51	return numOp(c, apdCtx.Mul, a, b)
52}
53
54func (c *OpContext) Quo(a, b *Num) Value {
55	v := numOp(c, apdCtx.Quo, a, b)
56	if n, ok := v.(*Num); ok {
57		n.K = FloatKind
58	}
59	return v
60}
61
62func (c *OpContext) Pow(a, b *Num) Value {
63	return numOp(c, apdCtx.Pow, a, b)
64}
65
66type numFunc func(z, x, y *apd.Decimal) (apd.Condition, error)
67
68func numOp(c *OpContext, fn numFunc, x, y *Num) Value {
69	var d apd.Decimal
70
71	cond, err := fn(&d, &x.X, &y.X)
72
73	if err != nil {
74		return c.NewErrf("failed arithmetic: %v", err)
75	}
76
77	if cond.DivisionByZero() {
78		return c.NewErrf("division by zero")
79	}
80
81	k := x.Kind() & y.Kind()
82	if k == 0 {
83		k = FloatKind
84	}
85	return c.newNum(&d, k)
86}
87
88func (c *OpContext) IntDiv(a, b *Num) Value {
89	return intDivOp(c, (*big.Int).Div, a, b)
90}
91
92func (c *OpContext) IntMod(a, b *Num) Value {
93	return intDivOp(c, (*big.Int).Mod, a, b)
94}
95
96func (c *OpContext) IntQuo(a, b *Num) Value {
97	return intDivOp(c, (*big.Int).Quo, a, b)
98}
99
100func (c *OpContext) IntRem(a, b *Num) Value {
101	return intDivOp(c, (*big.Int).Rem, a, b)
102}
103
104type intFunc func(z, x, y *big.Int) *big.Int
105
106func intDivOp(c *OpContext, fn intFunc, a, b *Num) Value {
107	if b.X.IsZero() {
108		return c.NewErrf("division by zero")
109	}
110
111	var x, y apd.Decimal
112	_, _ = apdCtx.RoundToIntegralValue(&x, &a.X)
113	if x.Negative {
114		x.Coeff.Neg(&x.Coeff)
115	}
116	_, _ = apdCtx.RoundToIntegralValue(&y, &b.X)
117	if y.Negative {
118		y.Coeff.Neg(&y.Coeff)
119	}
120
121	var d apd.Decimal
122
123	fn(&d.Coeff, &x.Coeff, &y.Coeff)
124
125	if d.Coeff.Sign() < 0 {
126		d.Coeff.Neg(&d.Coeff)
127		d.Negative = true
128	}
129
130	return c.newNum(&d, IntKind)
131}
132