A graph that checks "time - late == 6" after On timer (#224) failed, because the difference comes out as 5.999999999999972. Both values are sums of measured frame times, and they round along different paths. The only workaround was a Round node in front of every comparison. - New helper nearlyEqual in values.ts. Two numbers count as equal when |a - b| <= 1e-9 * max(1, |a|, |b|). The tolerance grows with the size of the values, so it holds for hours of summed dt. - Compare uses the rule for all six operators. "<" and ">" only hold when the values are not nearly equal, "<=" and ">=" also hold when they are. So "==" and ">" are never true at the same time. - Equals uses the same rule when both inputs are real numbers. Objects still compare by identity, everything else as text. The node description says so. - The tolerance is fixed, there is no parameter on the node. Physics values come from float32 in Rapier and are not covered on purpose. - The log display is unchanged and still prints the raw number. Tests: - ./check.sh: 156 files, 2311 tests, CHECK EXIT 0 (before 2300). - 11 new cases in node-catalog.test.ts, including the value from the log against 6, growth with magnitude, 6.001 != 6 and 1e-6 > 0. - Counter-check exact compare: exactly the 8 noise cases fail. - Counter-check "<"/">" without the exclusion: exactly 2 cases fail. - No e2e change, the check sum stays at 1374. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
39 lines
1.3 KiB
TypeScript
39 lines
1.3 KiB
TypeScript
import type { NodeDef } from '../../NodeRegistry';
|
|
import { asNumber, nearlyEqual } from '../../values';
|
|
import { bool, num } from '../shared';
|
|
|
|
import { textOf } from '../../../model/Properties';
|
|
// All six share one notion of equal, so `==` and `>` are never true at once.
|
|
const OPERATORS: Record<string, (a: number, b: number) => boolean> = {
|
|
'==': (a, b) => nearlyEqual(a, b),
|
|
'!=': (a, b) => !nearlyEqual(a, b),
|
|
'<': (a, b) => a < b && !nearlyEqual(a, b),
|
|
'<=': (a, b) => a < b || nearlyEqual(a, b),
|
|
'>': (a, b) => a > b && !nearlyEqual(a, b),
|
|
'>=': (a, b) => a > b || nearlyEqual(a, b),
|
|
};
|
|
|
|
export const Compare: NodeDef = {
|
|
type: 'Compare',
|
|
label: 'Compare',
|
|
category: 'data',
|
|
description: 'Compares two numbers.',
|
|
inputs: [num('a'), num('b')],
|
|
outputs: [bool('result')],
|
|
params: [
|
|
{
|
|
key: 'op',
|
|
label: 'Operator',
|
|
kind: 'enum',
|
|
default: '<',
|
|
options: Object.keys(OPERATORS).map((op) => ({ value: op, label: op })),
|
|
},
|
|
],
|
|
run: (_ctx, inputs, params) => {
|
|
// The fallback matches the param's declared default, so a node with no setting behaves
|
|
// like a freshly created one.
|
|
const compare = OPERATORS[textOf(params.op)] ?? OPERATORS['<']!;
|
|
return { outputs: { result: compare(asNumber(inputs.a), asNumber(inputs.b)) } };
|
|
},
|
|
};
|