Math Expression
Sizes, seeds and step counts without a calculator
- a
- b
- c
- INT
- FLOAT
Half of ComfyUI plumbing is one number that has to be derived from another. "Width should be 1.5× the latent's width." "Steps should be whatever I set, times two." Math Expression is the node that lets you write that as an expression instead of a chain of multiplier primitives.
What it is
A multiline expression widget, three optional inputs (a, b, c) that accept an INT, FLOAT, IMAGE or LATENT each, and two outputs: INT and FLOAT carrying the same value, truncated and as a float. The result is also drawn on the node, so you can read it without wiring anything.
The expressions people actually write:
a.width * 1.5
min(a.width, 2048)
steps * 2
iif(a > b, a, b)
randomint(1, 1000)
How it works
Here's the bit worth caring about: the expression is parsed with Python's ast module and walked with a whitelist. There is no eval() - a node that evaluates arbitrary strings from a workflow file is a node that runs arbitrary code from a downloaded workflow, and this one doesn't. Anything outside the whitelist raises a ValueError that names the offending piece, which makes the errors unusually readable for a node like this.
What's allowed:
- numbers, and
+ - * / // % **, the bitwise& | ^ << >>, unary- + ~ not, andand/or - comparisons (
== != < <= > >=, chained) which yield1or0- soa > bgives you a number, not a boolean - the names
a,b,c a.widthanda.heightfor anIMAGEorLATENTinput; latent sizes are multiplied by 8, i.e. it hands you the pixel size you were about to guess atNodeTitle.widgetto read another node's widget by node title or type- the functions
min max abs round int float pow sqrt floor ceil randomint randomchoice iif
An empty expression evaluates to 0. Referencing an input that isn't connected is an error, not a silent zero.
What it's good for
The a.width access is the reason I reach for it. Working at one resolution and upscaling by a fixed factor is a chain of guesswork otherwise - you generate at 1024, decide to go 1.5×, and now every resize widget in the graph says 1536 and drifts out of sync the moment you change the first one. Wire the latent into a, put a.width * 1.5 in the box, and the whole thing follows. Anything where the correct answer is "derived from the image I'm actually holding" belongs here.
Second use: making a randomised number that isn't a seed - randomint(1, 10) for a pick-one-of-ten style, or iif() to choose between two constants based on a checkbox.
Installing it
ComfyUI Manager → search ComfyUI-BCNodes → install → restart. Manually:
cd ComfyUI/custom_nodes
git clone https://github.com/0xBeycan/ComfyUI-BCNodes
pip install -r ComfyUI-BCNodes/requirements.txt
Reload the browser tab. It's under BCNodes/logic; search BCNodes to see the pack's nodes. No extra dependencies - requirements.txt only covers the pack's PostFx and Caption Audit nodes.
Common issues
It's an output node, so it always runs. That's deliberate: it's how the value can be displayed on the canvas with nothing wired to it. The side effect is that ComfyUI's cache can't skip it, and therefore can't skip anything upstream of it either. If you hang one of these off a big IMAGE chain for a bit of arithmetic, expect that chain to execute on every queue. Keep the expression downstream of the cheap stuff.
Renaming a node breaks NodeTitle.widget. The reference is by title or type, not by identity. Rename KSampler and the expression that read KSampler.steps stops working.
The INT output truncates. 2.9 becomes 2, not 3. If you want rounding, say so - round(x) is in the list.
No subscripts, no strings, no lambdas. a[0] is a ValueError. This is a small arithmetic language, not a scripting surface.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| expression | STRING | — | |
| aopt | * | — | |
| bopt | * | — | |
| copt | * | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| INT | INT | — |
| FLOAT | FLOAT | — |