From 5bf1ee7a4e9161e1dbb727031eb8b852425c16c5 Mon Sep 17 00:00:00 2001 From: MelekTaus Date: Thu, 24 Sep 2026 08:44:25 +0200 Subject: [PATCH] Let Equals match a number against a typed number like 130 (#224) The float tolerance from the last commit never reached the case it was built for. The inputs of Equals are of type any, and their inline field is a text field. A typed 130 therefore arrives as the string "130", and the tolerance only applied when both sides were real numbers. The node fell back to comparing "129.99999999999997" with "130" as text. - A number against a text that parses as a number now uses the same 1e-9 rule as two numbers. - An empty text does not count as 0. - Two texts are still compared as text, so "1.0" and "1" stay different. - Compare was not affected, its inputs are real number fields. Tests: - 3 new cases in node-catalog.test.ts: 129.99999999999997 vs "130" is equal, 0 vs "" is not, "1.0" vs "1" is not. - Counter-check: with the old rule (two real numbers only) exactly the "130" case fails. Original restored and compared with cmp. - ./check.sh: 156 files, 2314 tests, CHECK EXIT 0 (before 2311). Co-Authored-By: Claude Opus 5.5 --- src/core/graph/nodes/data/Equals.ts | 18 +++++++++++++++--- tests/core/node-catalog.test.ts | 8 ++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/core/graph/nodes/data/Equals.ts b/src/core/graph/nodes/data/Equals.ts index a69acf6f..76a5bd41 100644 --- a/src/core/graph/nodes/data/Equals.ts +++ b/src/core/graph/nodes/data/Equals.ts @@ -1,5 +1,5 @@ import type { NodeDef } from '../../NodeRegistry'; -import { asEntity, asString, nearlyEqual } from '../../values'; +import { asEntity, asString, nearlyEqual, type Value } from '../../values'; import { any, bool } from '../shared'; export const Equals: NodeDef = { @@ -15,9 +15,21 @@ export const Equals: NodeDef = { const a = asEntity(inputs.a); const b = asEntity(inputs.b); if (a || b) return { outputs: { result: a === b } }; - if (typeof inputs.a === 'number' && typeof inputs.b === 'number') { - return { outputs: { result: nearlyEqual(inputs.a, inputs.b) } }; + // An inline field on an `any` pin is text, so a typed 130 arrives as "130": with a number on + // the other side it still counts as that number (#224). + const x = numberOf(inputs.a); + const y = numberOf(inputs.b); + if (x !== null && y !== null && (typeof inputs.a === 'number' || typeof inputs.b === 'number')) { + return { outputs: { result: nearlyEqual(x, y) } }; } return { outputs: { result: asString(inputs.a) === asString(inputs.b) } }; }, }; + +/** A number, or text that is one ("130", " 1.5 "); empty text is not 0 here. */ +function numberOf(value: Value | undefined): number | null { + if (typeof value === 'number') return value; + if (typeof value !== 'string' || value.trim() === '') return null; + const n = Number(value); + return Number.isFinite(n) ? n : null; +} diff --git a/tests/core/node-catalog.test.ts b/tests/core/node-catalog.test.ts index 736f564a..d3d183e5 100644 --- a/tests/core/node-catalog.test.ts +++ b/tests/core/node-catalog.test.ts @@ -691,6 +691,14 @@ const CASES: Record = { outputs: { result: true }, }, { name: 'two numbers still tell 6.001 from 6', inputs: { a: 6.001, b: 6 }, outputs: { result: false } }, + // The inline field of an `any` pin is text: a typed 130 against time - late at 130 s. + { + name: 'a noisy number equals the typed text 130', + inputs: { a: 129.99999999999997, b: '130' }, + outputs: { result: true }, + }, + { name: 'empty text is not 0', inputs: { a: 0, b: '' }, outputs: { result: false } }, + { name: 'two texts still compare as text', inputs: { a: '1.0', b: '1' }, outputs: { result: false } }, ], Compare: [ { name: 'less than', inputs: { a: 1, b: 2 }, params: { op: '<' }, outputs: { result: true } },