POV-Ray : Newsgroups : povray.general : Strange syntax : Re: Strange syntax Server Time
1 Mar 2026 19:04:21 EST (-0500)
  Re: Strange syntax  
From: Bald Eagle
Date: 27 Feb 2026 10:10:00
Message: <web.69a1b372f71f0dca68a6daf225979125@news.povray.org>
I wanted to dig in a bit deeper to discover why scalar 1 gets promoted to <1, 0,
0, 0, 0, 0> instead of <1, 1, 1, 1, 1>.

Something clever that I noticed in the response was using vdot (V, V) as a way
to "count" the number of components in the vector (assuming they are all 1).
I will have to play with this idea.
Is there a way to normalize all the components of a vector without knowing how
many components there are?


--------------------------------------------------------------

AI response:

results: the equality operator = produces a scalar (0/1). If that scalar is then
required to participate in a 5‑component colour/vector context higher up
the expression tree, the parser embeds the scalar into a 5‑component



a float across all components, but it does not apply to boolean results of
comparisons. [github.com], [math.hws.edu]




Equality is scalar
In POV‑Ray SDL, A = B is a comparison with a scalar truth value (1 for

one. Internally the expression evaluator carries results in a small fixed array

count is 1. [github.com]


Type/arity is decided by surrounding context
When that scalar result is used where a vector/colour is expected, the parser
promotes it to the needed arity. There are two different promotion paths in the
language implementation:




General operator promotion (float → N‑vector), described in the
reference manual: a lone float can be promoted to a vector of 2/3/4/5 components
by replicating the same value into all components (e.g., 5 → <5,5,5> when
a 3‑vector is needed). This rule applies when a float literal/expression
itself is being used as a vector. [math.hws.edu]


Embedding of boolean/scalar results into multi‑component registers that
occurs inside the expression parser for comparison/logical operations. For
these, the parser stores the truth value in component 0 and clears the
remainder, producing <1,0,0,0,0> if the surrounding context expects a
5‑component value (as happens in colour/5‑term vector contexts).
This is why you observe <1,0,0,0,0> instead of <1,1,1,1,1>. You can see the
relevant behavior in the expression parser implementation (comparison handling
and result packing) in parser_expressions.cpp. [github.com]



Colour (5‑term) contexts are common

colours are 5‑component vectors: rgbft), the arity is 5. The documentation


non‑colour scalar promoted into a colour context naturally becomes
something shaped like <value, 0, 0, 0, 0> when it comes from a boolean
comparison result. [povray.org], [museum.state.il.us]




applies when a numeric float is directly promoted to an N‑vector (e.g.,
scale 5 → scale <5,5,5>). That rule does not override how
boolean/comparison results are packaged internally by the parser. So:

1 used as a colour literal where replication is needed: <1,1,1,1,1> (general
promotion). [math.hws.edu]
( <1,1,1> = 1 ) used where a colour is required: comparison yields scalar 1
first → then embedded as <1,0,0,0,0> (boolean embedding). [github.com]



If you need a vector/colour mask of ones after a comparison, build it
explicitly, e.g.:

Text.#declare V = <1,1,1>;#declare M = (vdot(V, V) = 3 ? <1,1,1,1,1> :
<0,0,0,0,0>); // example of explicit replicationShow more lines
or use arithmetic to replicate the scalar result across components before the
colour context forces 5D:
Chart.js#declare B = (<1,1,1> = 1);     // scalar 0/1#declare M = B *
<1,1,1,1,1>;   // replicate into all 5 lanesShow more lines
(Here the multiplication forces general operator promotion on the float B to a
5‑vector, giving <B,B,B,B,B>.) [math.hws.edu]


handles = and point to the code paths that set only the first component on

3.8 alpha, or master). [github.com]


The relevant code in the parser



defines an epsilon‑based predicate to turn floats into boolean truth
values. You can see both patterns in the current file and its 3.7 predecessor:


// parser_expressions.cpp
#define FTRUE (f) ( (int) (fabs (f)>EPSILON))



This is used throughout the numeric/logic code paths whenever a boolean is
needed. [github.com]
Internally, results are stored as an EXPRESS (a fixed array of DBL) with a

float-parameter helper rejects multi‑term results by checking Terms>1:


DBL Parser::Parse_Float_Param ()
{
    DBL Local;
    EXPRESS Express;
    int Terms;
    bool old_allow_id = Allow_Identifier_In_Call;
    Allow_Identifier_In_Call = false;

    Parse_Paren_Begin ();
    Parse_Express (Express,&Terms);
    if (Terms>1)
    {
        Error ("Float expected but vector or color expression found.");
    }
    Local = Express [0];
    Parse_Paren_End ();
    Allow_Identifier_In_Call = old_allow_id;
    return (Local);
}



This shows the (values, arity) representation: Express[0..Terms-1] is the
payload; the scalar case is Terms == 1. [github.com]
You can see the same scheme in the 3.7 code (express.cpp) as well.
[bugs.povray.org]
2) Places that set the arity
Whenever a vector-returning construct is parsed (e.g., a vector function call),
the parser explicitly writes multiple lanes into Express[...] and sets the
arity:


void Parser::Parse_Vector_Function_Call(EXPRESS Express, int *Terms)
{
    // ... call into VM ...
    for (param = 0; param < f->return_size; param++)
        Express[param] = fnVMContext->GetLocal(param);
    *Terms = f->return_size;  // <-- explicit arity for vector returns
}




the expression parser. [bugs.povray.org]
3) Where comparisons land
The actual comparison operators (e.g., =, !=, <, <=, >=, >) are handled in the
numeric/relational part of the expression parser; the code keeps temporary
scalars like greater_val, less_val, equal_val for those results, which are then
written back to the expression register as a single component (arity 1). You can
see these locals declared in the numeric parser routine:


// inside Parser::Parse_Num_Factor(...) in 3.7
DBL greater_val, less_val, equal_val;   // temps for relational/equality tests


Those comparisons convert to 0/1 using epsilon semantics (FTRUE), and then store
the result as a scalar in Express[0] with *Terms = 1. That is the key to the


Why we care: Once a comparison has yielded a scalar Express[0] ∈ {0,1}
with Terms=1, any later need for a vector/colour will trigger a promotion. Which
promotion path it takes determines whether you get <1,0,0,0,0> or <1,1,1,1,1>.




the end result depends on which one the parser chooses from context:


Float → N‑vector replication (operator promotion rule).

expects an N‑component vector and you give it a float, it replicates the
float into all components:
9 → <9,9,9,9> (for 4‑vectors), 5 → <5,5,5> (for
3‑vectors), etc. [math.hws.edu]


Lower‑arity vector → higher‑arity vector zero‑fill.
The same section also says that when promoting a vector of lower dimension to a
higher one, the remaining components are set to zero:
<7,6> → <7,6,0,0> if a 4‑vector is required. [math.hws.edu]


The comparison result takes path (2), not (1).
The equality (<1,1,1> = 1) is parsed/evaluated in the numeric/relational
pipeline. The boolean truth value is put into the expression register as a

1 and Express[0] = 1.0. When that value is later needed in a 5‑component
colour context (e.g., inside pigment { ... } where colours are rgbft
5‑vectors), POV‑Ray promotes a 1‑component vector to a
5‑component vector, which fills lanes 2..5 with zeros, giving <1,0,0,0,0>.
This matches exactly what you observed. [povray.org], [math.hws.edu]
By contrast, a literal 1 (a float) appearing directly in a colour context

5‑vector is expected and it starts from a true float, not from a

had in mind. [povray.org], [math.hws.edu]

Why does a comparison yield a 1‑lane vector instead of a float?


All numeric/logic sub‑expressions are computed into the EXPRESS register
(DBL Express[5]) plus an integer Terms.
Pure scalars and booleans both occupy Terms = 1 lane at that point.

the parser treats a Terms = 1 result as a vector of arity 1 if vector/colour
typing already dominates the surrounding context. From there, the
vector→vector arity promotion (zero‑fill) applies, not the
float→vector replication. [github.com], [bugs.povray.org]

So for (<1,1,1> = 1) you end up with:

Relational evaluation → Express[0] = 1.0, Terms = 1 (boolean true).
[bugs.povray.org]
Used in a colour (5‑vector) position → 1‑lane vector upcast to
5‑lane → <1,0,0,0,0>. [povray.org], [math.hws.edu]


How to get <1,1,1,1,1> from a boolean on purpose
If your intent is a mask of ones across all five colour lanes, force the
float→vector promotion (replication) explicitly by multiplying with a full
vector constant:


#declare B = (<1,1,1> = 1);        // scalar 0/1 in Express[0]
#declare M = B * <1,1,1,1,1>;      // forces float→vector replication
→ <B,B,B,B,B>


This uses general operator promotion (a float on the left in a vector arithmetic
expression) so POV‑Ray replicates B to each lane. [math.hws.edu]


#declare M = select(B, <0,0,0,0,0>, <1,1,1,1,1>);



Post a reply to this message

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.