Skip to content

Commit 9303e8c

Browse files
committed
Implement precision mechanism for JSON Logic; I'll probably release this soon.
1 parent 5d42405 commit 9303e8c

File tree

7 files changed

+143
-7
lines changed

7 files changed

+143
-7
lines changed

defaultMethods.js

+3
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,9 @@ defaultMethods.var.compile = function (data, buildState) {
947947
// @ts-ignore Allowing a optimizeUnary attribute that can be used for performance optimizations
948948
defaultMethods['+'].optimizeUnary = defaultMethods['-'].optimizeUnary = defaultMethods.var.optimizeUnary = defaultMethods['!'].optimizeUnary = defaultMethods['!!'].optimizeUnary = defaultMethods.cat.optimizeUnary = true
949949

950+
// @ts-ignore Allowing a optimizeUnary attribute that can be used for performance optimizations
951+
defaultMethods['<'].precise = defaultMethods['<='].precise = defaultMethods['>'].precise = defaultMethods['>='].precise = defaultMethods['=='].precise = defaultMethods['==='].precise = defaultMethods['!='].precise = defaultMethods['!=='].precise = true
952+
950953
export default {
951954
...defaultMethods
952955
}

logic.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class LogicEngine {
3232
this.disableInline = options.disableInline
3333
this.disableInterpretedOptimization = options.disableInterpretedOptimization
3434
this.methods = { ...methods }
35+
this.precision = null
3536

3637
this.optimizedMap = new WeakMap()
3738
this.missesSinceSeen = 0
@@ -67,7 +68,7 @@ class LogicEngine {
6768
const [func] = Object.keys(logic)
6869
const data = logic[func]
6970

70-
if (this.isData(logic, func)) return logic
71+
if (this.isData(logic, func) || (this.precision && logic instanceof this.precision)) return logic
7172

7273
if (!this.methods[func]) throw new Error(`Method '${func}' was not found in the Logic Engine.`)
7374

@@ -77,9 +78,9 @@ class LogicEngine {
7778
}
7879

7980
if (typeof this.methods[func] === 'object') {
80-
const { method, traverse } = this.methods[func]
81+
const { method, traverse, precise } = this.methods[func]
8182
const shouldTraverse = typeof traverse === 'undefined' ? true : traverse
82-
const parsedData = shouldTraverse ? ((!data || typeof data !== 'object') ? [data] : coerceArray(this.run(data, context, { above }))) : data
83+
const parsedData = shouldTraverse ? ((!data || typeof data !== 'object') ? [data] : coerceArray(this.run(data, context, { above, precise }))) : data
8384
return method(parsedData, context, above, this)
8485
}
8586

@@ -123,7 +124,7 @@ class LogicEngine {
123124
*
124125
* @param {*} logic The logic to be executed
125126
* @param {*} data The data being passed in to the logic to be executed against.
126-
* @param {{ above?: any }} options Options for the invocation
127+
* @param {{ above?: any, precise?: boolean }} options Options for the invocation
127128
* @returns {*}
128129
*/
129130
run (logic, data = {}, options = {}) {
@@ -149,11 +150,14 @@ class LogicEngine {
149150

150151
if (Array.isArray(logic)) {
151152
const res = []
152-
for (let i = 0; i < logic.length; i++) res.push(this.run(logic[i], data, { above }))
153+
for (let i = 0; i < logic.length; i++) res.push(this.run(logic[i], data, options))
153154
return res
154155
}
155156

156-
if (logic && typeof logic === 'object' && Object.keys(logic).length > 0) return this._parse(logic, data, above)
157+
if (logic && typeof logic === 'object') {
158+
if (this.precision && !options.precise && logic.toNumber) return Number(logic)
159+
if (Object.keys(logic).length > 0) return this._parse(logic, data, above)
160+
}
157161

158162
return logic
159163
}

optimizer.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export function optimize (logic, engine, above = []) {
5353
const keys = Object.keys(logic)
5454
const methodName = keys[0]
5555

56-
const isData = engine.isData(logic, methodName)
56+
const isData = engine.isData(logic, methodName) || (engine.precision && logic instanceof engine.precision)
5757
if (isData) return () => logic
5858

5959
// If we have a deterministic function, we can just return the result of the evaluation,

precision/index.js

+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
export function configurePrecision (engine, constructor) {
2+
engine.precision = constructor
3+
engine.addMethod('+', {
4+
method: (data) => {
5+
if (typeof data === 'string') return constructor(data)
6+
if (typeof data === 'number') return constructor(data)
7+
let res = constructor(data[0])
8+
for (let i = 1; i < data.length; i++) res = res.plus(data[i])
9+
return res
10+
},
11+
compile: (args, buildState) => {
12+
if (Array.isArray(args)) {
13+
let res = buildState.compile`(engine.precision(${args[0]}))`
14+
for (let i = 1; i < args.length; i++) res = buildState.compile`(${res}.plus(${args[i]}))`
15+
return res
16+
}
17+
return false
18+
},
19+
optimizeUnary: true,
20+
deterministic: true,
21+
precise: true
22+
})
23+
24+
engine.addMethod('-', {
25+
method: (data) => {
26+
if (typeof data === 'string') return constructor(data).mul(-1)
27+
if (typeof data === 'number') return constructor(data).mul(-1)
28+
let res = constructor(data[0])
29+
for (let i = 1; i < data.length; i++) res = res.minus(data[i])
30+
return res
31+
},
32+
compile: (args, buildState) => {
33+
if (Array.isArray(args)) {
34+
let res = buildState.compile`(engine.precision(${args[0]}))`
35+
for (let i = 1; i < args.length; i++) res = buildState.compile`(${res}.minus(${args[i]}))`
36+
return res
37+
}
38+
return false
39+
},
40+
optimizeUnary: true,
41+
deterministic: true,
42+
precise: true
43+
})
44+
45+
engine.addMethod('*', {
46+
method: (data) => {
47+
let res = constructor(data[0])
48+
for (let i = 1; i < data.length; i++) res = res.mul(data[i])
49+
return res
50+
},
51+
compile: (args, buildState) => {
52+
if (Array.isArray(args)) {
53+
let res = buildState.compile`(engine.precision(${args[0]}))`
54+
for (let i = 1; i < args.length; i++) res = buildState.compile`(${res}.mul(${args[i]}))`
55+
return res
56+
}
57+
return false
58+
},
59+
deterministic: true,
60+
precise: true
61+
})
62+
63+
engine.addMethod('/', {
64+
method: (data) => {
65+
let res = constructor(data[0])
66+
for (let i = 1; i < data.length; i++) res = res.div(data[i])
67+
return res
68+
},
69+
compile: (args, buildState) => {
70+
if (Array.isArray(args)) {
71+
let res = buildState.compile`(engine.precision(${args[0]}))`
72+
for (let i = 1; i < args.length; i++) res = buildState.compile`(${res}.div(${args[i]}))`
73+
return res
74+
}
75+
return false
76+
},
77+
deterministic: true,
78+
precise: true
79+
})
80+
81+
engine.addMethod('%', {
82+
method: (data) => {
83+
let res = constructor(data[0])
84+
for (let i = 1; i < data.length; i++) res = res.mod(data[i])
85+
return res
86+
},
87+
compile: (args, buildState) => {
88+
if (Array.isArray(args)) {
89+
let res = buildState.compile`(engine.precision(${args[0]}))`
90+
for (let i = 1; i < args.length; i++) res = buildState.compile`(${res}.mod(${args[i]}))`
91+
return res
92+
}
93+
return false
94+
},
95+
deterministic: true,
96+
precise: true
97+
})
98+
}

precision/package.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "precision",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"license": "MIT",
6+
"dependencies": {
7+
"decimal.js": "^10.4.3"
8+
},
9+
"type": "module"
10+
}

precision/scratch.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { LogicEngine } from '../index.js'
2+
import { Decimal } from 'decimal.js'
3+
import { configurePrecision } from './index.js'
4+
5+
const ieee754Engine = new LogicEngine()
6+
const decimalEngine = new LogicEngine()
7+
configurePrecision(decimalEngine, Decimal.clone({ precision: 100 }))
8+
9+
console.log(decimalEngine.build({ '+': ['85070591730234615847396907784232501249', 100] })().toFixed()) // 85070591730234615847396907784232501349
10+
console.log(decimalEngine.run({ '+': [0.1, 0.2] })) // 0.3
11+
12+
console.log(ieee754Engine.run({ '>': [{ '+': [0.1, 0.2] }, 0.3] })) // true, because 0.1 + 0.2 = 0.30000000000000004
13+
console.log(decimalEngine.run({ '>': [{ '+': [0.1, 0.2] }, 0.3] })) // false, because 0.1 + 0.2 = 0.3

precision/yarn.lock

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
decimal.js@^10.4.3:
6+
version "10.4.3"
7+
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
8+
integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==

0 commit comments

Comments
 (0)