pkg/math: added MultipleOf

Also:
- add support for Decimal type (internal)
- add correspondng support in openapi package

Change-Id: Idc4b829423bba63380a8edfc1109c345f4248355
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2640
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/builtin.go b/cue/builtin.go
index dccb441..08fdcfc 100644
--- a/cue/builtin.go
+++ b/cue/builtin.go
@@ -28,6 +28,7 @@
 	"cuelang.org/go/cue/errors"
 	"cuelang.org/go/cue/parser"
 	"cuelang.org/go/internal"
+	"github.com/cockroachdb/apd/v2"
 )
 
 // A builtin is a builtin function or constant.
@@ -393,6 +394,15 @@
 	return res
 }
 
+func (c *callCtxt) decimal(i int) *apd.Decimal {
+	x := newValueRoot(c.ctx, c.args[i])
+	if _, err := x.MantExp(nil); err != nil {
+		c.invalidArgType(c.args[i], i, "Decimal", err)
+		return nil
+	}
+	return &c.args[i].(*numLit).v
+}
+
 func (c *callCtxt) float64(i int) float64 {
 	x := newValueRoot(c.ctx, c.args[i])
 	res, err := x.Float64()
diff --git a/cue/builtin_test.go b/cue/builtin_test.go
index d93bd05..b710f73 100644
--- a/cue/builtin_test.go
+++ b/cue/builtin_test.go
@@ -90,6 +90,21 @@
 		test("math", `math.Asin(2.0e400)`),
 		`_|_(cannot use 2.0e+400 (type float) as float64 in argument 0 to math.Asin: value was rounded up)`,
 	}, {
+		test("math", `math.MultipleOf(4, 2)`), `true`,
+	}, {
+		test("math", `math.MultipleOf(5, 2)`), `false`,
+	}, {
+		test("math", `math.MultipleOf(5, 0)`),
+		`_|_(error in call to math.MultipleOf: division by zero)`,
+	}, {
+		test("math", `math.MultipleOf(100, 1.00001)`), `false`,
+	}, {
+		test("math", `math.MultipleOf(1, 1)`), `true`,
+	}, {
+		test("math", `math.MultipleOf(5, 2.5)`), `true`,
+	}, {
+		test("math", `math.MultipleOf(100e100, 10)`), `true`,
+	}, {
 		test("encoding/csv", `csv.Decode("1,2,3\n4,5,6")`),
 		`[["1","2","3"],["4","5","6"]]`,
 	}, {
diff --git a/cue/builtins.go b/cue/builtins.go
index 1fd88a3..1a73490 100644
--- a/cue/builtins.go
+++ b/cue/builtins.go
@@ -28,6 +28,7 @@
 	"cuelang.org/go/cue/literal"
 	"cuelang.org/go/cue/parser"
 	"cuelang.org/go/internal/third_party/yaml"
+	"github.com/cockroachdb/apd/v2"
 	goyaml "github.com/ghodss/yaml"
 )
 
@@ -37,6 +38,8 @@
 
 var _ io.Reader
 
+var mulContext = apd.BaseContext.WithPrecision(1)
+
 var split = path.Split
 
 var pathClean = path.Clean
@@ -564,6 +567,18 @@
 				}()
 			},
 		}, {
+			Name:   "MultipleOf",
+			Params: []kind{numKind, numKind},
+			Result: boolKind,
+			Func: func(c *callCtxt) {
+				x, y := c.decimal(0), c.decimal(1)
+				c.ret, c.err = func() (interface{}, error) {
+					var d apd.Decimal
+					cond, err := mulContext.Quo(&d, x, y)
+					return !cond.Inexact(), err
+				}()
+			},
+		}, {
 			Name:   "Abs",
 			Params: []kind{numKind},
 			Result: numKind,
@@ -1506,6 +1521,7 @@
 			Func: func(c *callCtxt) {
 				s, max := c.string(0), c.int(1)
 				c.ret = func() interface{} {
+
 					return len([]rune(s)) <= max
 				}()
 			},
diff --git a/cue/gen.go b/cue/gen.go
index 6e91f03..3fd9e80 100644
--- a/cue/gen.go
+++ b/cue/gen.go
@@ -388,6 +388,8 @@
 		return "bigFloat"
 	case "big.Rat":
 		return "bigRat"
+	case "internal.Decimal":
+		return "decimal"
 	case "cue.Value":
 		return "value"
 	case "cue.List":
@@ -419,7 +421,7 @@
 		"uint", "byte", "uint8", "uint16", "uint32", "uint64",
 		"bigInt":
 		cueKind += "intKind"
-	case "float64", "bigRat", "bigFloat":
+	case "float64", "bigRat", "bigFloat", "decimal":
 		cueKind += "numKind"
 	case "list":
 		cueKind += "listKind"
diff --git a/encoding/openapi/build.go b/encoding/openapi/build.go
index 8387c72..d2315fa 100644
--- a/encoding/openapi/build.go
+++ b/encoding/openapi/build.go
@@ -435,7 +435,7 @@
 
 // object supports the following
 // - maxProperties: maximum allowed fields in this struct.
-// - minProperties: minimum required fields in this struct.a
+// - minProperties: minimum required fields in this struct.
 // - patternProperties: [regexp]: schema
 //   TODO: we can support this once .kv(key, value) allow
 //      foo [=~"pattern"]: type
@@ -599,6 +599,18 @@
 			b.kv("maxItems", i),
 		})
 
+	case cue.CallOp:
+		name := fmt.Sprint(a[0])
+		switch name {
+		case "math.MultipleOf":
+			if len(a) != 2 {
+				b.failf(v, "builtin %v may only be used with single argument", name)
+			}
+			b.setFilter("Schema", "multipleOf", b.int(a[1]))
+		default:
+			b.failf(v, "builtin %v not supported in OpenAPI", name)
+		}
+
 	case cue.NoOp:
 		// TODO: extract format from specific type.
 
diff --git a/encoding/openapi/openapi_test.go b/encoding/openapi/openapi_test.go
index b085f75..430b5d4 100644
--- a/encoding/openapi/openapi_test.go
+++ b/encoding/openapi/openapi_test.go
@@ -55,6 +55,10 @@
 		"strings.json",
 		defaultConfig,
 	}, {
+		"nums.cue",
+		"nums.json",
+		defaultConfig,
+	}, {
 		"oneof.cue",
 		"oneof.json",
 		defaultConfig,
diff --git a/encoding/openapi/testdata/nums.cue b/encoding/openapi/testdata/nums.cue
new file mode 100644
index 0000000..a20b36b
--- /dev/null
+++ b/encoding/openapi/testdata/nums.cue
@@ -0,0 +1,3 @@
+import "math"
+
+mul: math.MultipleOf(5)
diff --git a/encoding/openapi/testdata/nums.json b/encoding/openapi/testdata/nums.json
new file mode 100644
index 0000000..819d5e8
--- /dev/null
+++ b/encoding/openapi/testdata/nums.json
@@ -0,0 +1,12 @@
+{
+   "openapi": "3.0.0",
+   "info": {},
+   "components": {
+      "schemas": {
+         "mul": {
+            "type": "number",
+            "multipleOf": 5
+         }
+      }
+   }
+}
\ No newline at end of file
diff --git a/internal/internal.go b/internal/internal.go
index 6133bac..261b9d6 100644
--- a/internal/internal.go
+++ b/internal/internal.go
@@ -16,6 +16,13 @@
 
 // TODO: refactor packages as to make this package unnecessary.
 
+import "github.com/cockroachdb/apd/v2"
+
+// A Decimal is an arbitrary-precision binary-coded decimal number.
+//
+// Right now Decimal is aliased to apd.Decimal. This may change in the future.
+type Decimal = apd.Decimal
+
 // DebugStr prints a syntax node.
 var DebugStr func(x interface{}) string
 
diff --git a/pkg/math/manual.go b/pkg/math/manual.go
index 493f8c5..361a6c1 100644
--- a/pkg/math/manual.go
+++ b/pkg/math/manual.go
@@ -14,7 +14,12 @@
 
 package math
 
-import "math"
+import (
+	"math"
+
+	"cuelang.org/go/internal"
+	"github.com/cockroachdb/apd/v2"
+)
 
 // TODO: use apd
 
@@ -67,3 +72,12 @@
 func RoundToEven(x float64) float64 {
 	return math.RoundToEven(x)
 }
+
+var mulContext = apd.BaseContext.WithPrecision(1)
+
+// MultipleOf reports whether x is a multiple of y.
+func MultipleOf(x, y *internal.Decimal) (bool, error) {
+	var d apd.Decimal
+	cond, err := mulContext.Quo(&d, x, y)
+	return !cond.Inexact(), err
+}