I'd like to do "basic math" on large (64-bits) signed integers in a "DSL" (jsonnet) that only has support for "numbers" (64-bit double). My "strategy" is converting strings, representing "integer" user-input, that can be correctly represented as a number into one, and on every "operation" just check which parameters are "number" and "strings".
Ideally, I'd have support for add, sub, mult, div, modulo and comparisons. But I can build modulo using div, and sub using add (assuming add support signed integers). Comparisons should be easy too.
So, at the very least, I'd need add, mult, and ideally div implementation examples.
There are a bunch of example out there, but all the ones I found either assume that there is an integer type that can contain the biggest integer, or that the language is procedural. Unfortunately, jsonnet is also functional, and so procedural solutions cannot simply be used either.
So, what I'm looking for is an example (or link to one), that shows me how to add, multiply, and ideally divide, signed integers, represented as strings, using the functional paradigm, without using some "built-in" library function to do the hard work (unless it's also available in jsonnet). Or, most likely, the OSS implementation of that built-in library function.
I'm not overly concerned with performance, but I'm hoping for an O(m + n) rather than O(m * n) solution ...
I think (although not authoritative on this) that you'll need to add jsonnet native functions to handle int64 and wanted operators. Jsonnet golang libs do provide an interface to add these, I've crafted an (incomplete) example that should help:
example.jsonnet
// file: example.jsonnet
// "Register" added native functions
local parseInt64 = std.native('parseInt64');
local addInt64 = std.native('addInt64');
local multInt64 = std.native('multInt64');
{
const: {
power2_63_minus2: parseInt64('9223372036854775806'), // 2^63 - 2
power2_61: parseInt64('2305843009213693952'),
minusOne: parseInt64('-1'),
one: parseInt64('1'),
two: parseInt64('2'),
},
// Adding 1 to get max positive int64 value
max64Positive: addInt64(self.const.power2_63_minus2, self.const.one),
// Adding 2 to 'wrap' its sign to get max int64 negative value (abs)
max64Negative: addInt64(self.const.power2_63_minus2, self.const.two),
// Substract 1 to the above max negative int64 value to get back to max positive
max64PosFromNeg: addInt64(self.max64Negative, self.const.minusOne),
// Multiplying 2^61 by 2
multRes: multInt64(self.const.power2_61, self.const.two),
}
main.go
// file: main.go
package main
import (
"fmt"
"io/ioutil"
"strconv"
"github.com/google/go-jsonnet"
"github.com/google/go-jsonnet/ast"
)
// Implement multInt64() native function, from passed string arguments
// returns string
func multInt64(s []interface{}) (res interface{}, err error) {
num1 := []byte(s[0].(string))
num2 := []byte(s[1].(string))
// NB: ParseInt returns int64
// Convert each string arg to int64 via ParseInt()
res1, err := strconv.ParseInt(string(num1), 10, 64)
if err != nil {
return nil, fmt.Errorf("multInt64: %s: %s", num1, err)
}
res2, err := strconv.ParseInt(string(num2), 10, 64)
if err != nil {
return nil, fmt.Errorf("multInt64: %s: %s", num2, err)
}
// Return the mult of both int64 numbers, as string
return fmt.Sprintf("%d", res1*res2), nil
}
// Implement addInt64() native function, from passed string arguments,
// returns string
func addInt64(s []interface{}) (res interface{}, err error) {
num1 := []byte(s[0].(string))
num2 := []byte(s[1].(string))
// NB: ParseInt returns int64
// Convert each string arg to int64 via ParseInt()
res1, err := strconv.ParseInt(string(num1), 10, 64)
if err != nil {
return nil, fmt.Errorf("addInt64: %s: %s", num1, err)
}
res2, err := strconv.ParseInt(string(num2), 10, 64)
if err != nil {
return nil, fmt.Errorf("addInt64: %s: %s", num2, err)
}
// Return the sum of both int64 numbers, as string
return fmt.Sprintf("%d", res1+res2), nil
}
// Implement parseInt64() native function, note it _only_ validates the
// passed string as an int64, returns string
func parseInt64(s []interface{}) (res interface{}, err error) {
data := []byte(s[0].(string))
res, err = strconv.ParseInt(string(data), 10, 64)
// Just check for ParseInt() errors
if err != nil {
return nil, fmt.Errorf("ParsetInt64: %s: %s", data, err)
}
return string(data), nil
}
// Register the above (native) functions
func registerNativeFuncs(vm *jsonnet.VM) {
vm.NativeFunction(
&jsonnet.NativeFunction{
Name: "parseInt64",
Params: ast.Identifiers{"num"},
Func: parseInt64,
},
)
vm.NativeFunction(
&jsonnet.NativeFunction{
Name: "addInt64",
Params: ast.Identifiers{"num1", "num2"},
Func: addInt64,
},
)
vm.NativeFunction(
&jsonnet.NativeFunction{
Name: "multInt64",
Params: ast.Identifiers{"num1", "num2"},
Func: multInt64,
},
)
}
// Exercise above native functions, invoked in `example.jsonnet`
func main() {
// create a Jsonnet VM
vm := jsonnet.MakeVM()
// vm.ExtVar("fooName", "fooValue")
registerNativeFuncs(vm)
// load a Jsonnet file
data, err := ioutil.ReadFile("example.jsonnet")
if err != nil {
panic(err)
}
// evaluate the Jsonnet file with an external variable
result, err := vm.EvaluateAnonymousSnippet("example.jsonnet", string(data))
if err != nil {
panic(err)
}
// print the result
fmt.Println(result)
}
output
$ go mod init int64ex
$ go mod tidy
go run main.go
{
"const": {
"minusOne": "-1",
"one": "1",
"power2_61": "2305843009213693952",
"power2_63_minus2": "9223372036854775806",
"two": "2"
},
"max64Negative": "-9223372036854775808",
"max64PosFromNeg": "9223372036854775807",
"max64Positive": "9223372036854775807",
"multRes": "4611686018427387904"
}