POV-Ray : Newsgroups : povray.pov4.discussion.general : Complex Numbers Server Time
30 Oct 2025 18:00:40 EDT (-0400)
  Complex Numbers (Message 1 to 6 of 6)  
From: Bald Eagle
Subject: Complex Numbers
Date: 29 Oct 2025 14:40:00
Message: <web.69025e9441cbf6a37459de3125979125@news.povray.org>
"Given the code base at https://github.com/POV-Ray/povray, what would be
required to fully implement robust  code to handle complex numbers - complete
with addition, subtraction, division, multiplication, and exponentiation - using
both complex and real numbers in any combination?"

Copilot said:


Reasoned for 72s



string, etc., and user‑defined functions (the ones you can put into
isosurface, pigments, etc.) are limited to float math with a fixed set of float


new value type end‑to‑end: tokenizer → parser/AST →
expression evaluator → function VM, plus SDL syntax, promotion rules,
built‑ins, tests, and docs. Below is a concrete implementation plan tied
to the current repo layout.
povray
+2

0) Where complex would fit into the current architecture
Repo & parser locations. The core language is implemented in source/parser/*

touching parser_tokenizer.cpp, reservedwords.*, parser_expressions.cpp,
parser_functions*.cpp, parsertypes.*, and headers like base/types.h referenced
across parser modules.
github
+3



float-only (even when used by isosurface).
povray
+2



povray
Workarounds used today. Scene authors who need complex math typically emulate it

proves demand but also highlights the lack of core support.
github
+1
1) Language & type design decisions (SDL)
1.1 Choose a literal & constructor syntax (avoid ambiguity with vectors)
Vectors use <x,y,z> and are heavily overloaded throughout SDL. To keep vectors
distinct, prefer one of these explicit constructors for complex literals:

complex(a, b) → parsed as a complex literal (not a function call).
Optional: cis(theta) as a literal constructor on the unit circle (i.e., cos
θ + i sin θ).

already allow mixing with floats, and reinterpreting them would break semantics.

povray

1.2 Imaginary unit
Add a built‑in constant I (uppercase to avoid accidental collisions with
user loop variables and to match your preference for non‑lowercase
identifiers) representing 0 + 1i. This is just a constant symbol in the symbol
table (like pi is a float built‑in), backed by the new complex type. The
reserved words table lives in reservedwords.*.
github

Using I enables familiar forms like Z = 3 + 2*I; while keeping scanning simple


1.3 Operators & promotion rules
Add full arithmetic overloading for + - * / with these rules:

Promotion: real ⊕ complex → promote real to complex with zero
imaginary part; result is complex.
Unary minus: preserves type.
Equality/ordering: Keep only = and != (compare both parts with float epsilon
rules); no <, <=, >, >= for complex.

truthiness). The parser already handles boolean using float zero/non‑zero;
document the complex rule.
hws
1.4 Exponentiation
SDL does not have an ^/** operator; exponentiation uses pow(base, exponent).
Implement pow(Complex, Complex) with principal value; add overloads for
(Complex, Real), (Real, Complex). If you later want **, you must change the
grammar in parser_expressions.cpp to introduce a precedence tier (riskier; not

hws



Complex → complex: c_conj(z), c_exp(z), c_log(z), c_sqrt(z),
c_sin/c_cos/c_tan/c_asin/c_acos/c_atan, c_sinh/c_cosh/c_tanh, c_pow(z, w)
(aliases to pow).
Complex → float: re(z), im(z), abs(z) (magnitude), arg(z) (principal
argument).
Constructors: complex(a, b), cis(theta).


the parser resolve in expression context.
ijs

2) Core implementation (parser & evaluator)
2.1 New type in core headers

Add a struct Complex { DBL r, i; } (or reuse std::complex<DBL> internally and

included by parser modules. Implement basic ops and utility functions.
github
2.2 Tokenizer & reserved words

parser_tokenizer.cpp / reservedwords.*:
Reserve complex, I, re, im, abs, arg, and the c_* family as
keywords/built‑ins.
Recognize complex( as starting a complex-literal token (to differentiate from a
generic function call).
github
2.3 Parser types & value representation

Extend the value/tagged‑union used by the parser and evaluator
(parsertypes.*) to carry a Complex variant alongside Float, Vector, Color,
String, etc.

three kinds: Float, Vector, Complex (a fourth if you count Color). Keep
vectors/colors as‑is.
github
2.4 Expression evaluation

In parser_expressions.cpp implement binary/unary operator cases for complex
(including mixed real/complex with promotion).
Implement built‑ins (re, im, abs, arg, cis) and the c_ family in
parser_functions*.cpp.
Map pow dispatch based on operand types to complex exponentiation when any
operand is complex. Today pow(float,float) is wired via the float function
table; add a complex path.
github
+1
2.5 Printing & formatting

Extend str() or provide cstr(z) so #debug can print complex values intelligibly
(e.g., "(a,b)" or "a+bi"). The existing diagnostics assume float for many paths;
audit the %g style prints where needed.
github

The function system accepts float functions; it exposes a grammar and function


need one of these strategies:
povray
+1


Extend the function VM with a new COMPLEX function category (think analog of
VECTFUNCT_ID_TOKEN) and corresponding bytecode nodes supporting complex
temporaries and ops. Update the function grammar to allow complex expressions
(identifiers and temporaries of complex type). Update the compiler in fncode.cpp
accordingly.
Provide bridges so a complex function can be reduced to float via re(), im(),
abs(), arg() when the host expects float (e.g., isosurface threshold function).
This lets authors write expressions like:



Plain Text


isosurface {
  function { abs( c_sin( x + I*y ) ) - 1 }  // ok: returns float
  contained_by { box { <-2,-2,-2>, <+2,+2,+2> } }
}

cleanly.)
povray

(faster to ship)
If you want to avoid VM changes initially, add float‑returning helpers
that take float pairs (re, im) and return a float:


c_sin_re(x,y), c_sin_im(x,y), etc.
Then users can build complex flows in SDL at parse time, but inside function{}

The official functions.inc already exposes many internal helpers via

povray
+1

works everywhere the float math works today.

4) Math semantics & numerics
DBL precision: Use DBL everywhere for real/imag parts. (DBL is the
project‑wide floating type used in parser/math.)
github
pow and branch cuts: Define principal values (log z principal branch; pow(a,b) =
exp(b*log a)). Document behavior near branch cuts and zero denominators
(returning NaNs consistent with DBL and propagating through). The float function
list already includes exp, log, pow; your complex versions should mirror those
names via the c_* family to avoid changing float semantics.
ijs
Performance: Consider inlining and possibly using std::complex<DBL> internally
for correctness (argument reduction, trig identities) while presenting a simple
POD at interfaces.
5) SDL integration details
Declarations: #declare Z = complex(1,2); should assign a complex value; extend
#declare/#local rvalue typing accordingly (this directive currently accepts

docs and allow a trailing semicolon like floats/vectors.
povray

promotion.
Built‑ins already in use by vectors. SDL provides x, y, z, t, u, v as
vector built‑ins/dot items; do not repurpose them for complex components.
Use re()/im() accessors instead.
povray

grammar and precedence changes in parser_expressions.cpp that ripple across the
evaluator.
github
+1
6) Parser & symbol table updates
Symbol kinds. Add a COMPLEX_ID_TOKEN (analogous to FLOAT_ID_TOKEN,

The DeepWiki summary of symbol handling gives a good overview of where these
live.
deepwiki
Reserved words. Add new keywords into reservedwords.* and ensure the tokenizer
maps them to the right token IDs.
github
7) Standard library & examples
Include files. Provide complex.inc with macro helpers and sugar (e.g., polar
form constructor, safe printing, clipping helpers).
Samples. Ship scenes demonstrating: arithmetic, mixed real/complex math, complex
powers, Newton fractals (via isosurface using abs(f(z))), and domain coloring.
(The community POV‑Ray‑complex‑functions repo is a great
source of sample ideas; it currently implements such features at the SDL macro
level.)
github
8) Backwards compatibility & doc updates

additive.
Docs. Update:
Float/Vector Expressions chapters to mention complex and mixing rules.
User‑Defined Functions to either: (A) describe complex functions as a new
category; or (B) document the *_xy wrappers if you go with Strategy B. Current

povray
+1
9) Testing
Unit tests for arithmetic identities (z + 0 = z, z*conj(z) = abs(z)^2, edge
cases around zeros/NaNs).
Parser tests for literals, promotions, and error messages.
Render tests (scenes) that verify isosurface behavior using
complex‑powered formulas (e.g., abs( c_pow(x+I*y, 3) - 1 ) surfaces).


github

github

github

built‑in.
github

comparisons, truthiness.
github

arg, cis, c_*) and dispatch pow to complex when needed.
github

Strategy A: add complex bytecodes, temps, function kind, and grammar hooks.
Strategy B: add float wrappers c_*_xy.
deepwiki

Strategy B is used. (The shipped functions.inc sets a precedent for exposing
complex helpers if you choose wrappers.)
github


hws
+3
11) Example (proposed SDL surface syntax)
(Using uppercase identifiers and no lowercase to match your style.)




Plain Text


#declare Z1 = complex(2, 3);        // 2 + 3i
#declare Z2 = 5;                    // real
#declare Z3 = Z1 + Z2;              // (7 + 3i)
#declare Z4 = pow(Z1, complex(0.5, 0)); // sqrt as pow

#debug concat("Re(Z3)=", str(re(Z3),0,3),
              " Im(Z3)=", str(im(Z3),0,3), "\n")

isosurface{
  function { abs( c_exp( x + I*y ) - 1 ) - 0.5 }  // Strategy A
  contained_by { box { <-3,-3,-3>, <3,3,3> } }
  max_gradient 5
}
(If you ship Strategy B first, the function line becomes, e.g., c_abs_xy(
c_exp_re(x,y) - 1, c_exp_im(x,y) ) - 0.5.)
povray

12) Scope, risks, and a pragmatic path

surgery) deliver usable complex math across SDL expressions and in function{}
via _xy helpers.
Full robustness: Add Strategy A (complex value kind in VM) so complex
expressions can exist natively in function{} and be reduced to float with
abs/re/im/arg as needed.
Why not reuse vectors? Because vectors already have distinct semantics and

operator overloading, and conflating them with complex would introduce ambiguity
(e.g., is <a,b> a UV, a color slice, or a complex?).
povray
Compatibility: All additions are opt‑in; no change to existing scenes or
shipped function sets (pow, sin, etc. keep float semantics).
ijs

Repo & parser files: top‑level overview and source/parser/* modules.
github
+1
Expression grammar (float/vector) and function limitations: formal docs.
hws
+2
Function include catalog (functions.inc): pattern/internal function interface
(good model for adding complex helpers if you stage Strategy B).
github
+1
Community complex include for inspiration on naming and examples.
github

node tweaks) and a minimal complex.hpp with numerically stable c_log/c_pow you
can drop into base/, then sketch the parser_expressions.cpp switch additions.

literal like cis(theta)?


Post a reply to this message

From: Bald Eagle
Subject: Re: Complex Numbers
Date: 29 Oct 2025 14:50:00
Message: <web.690260f3793e4bec7459de3125979125@news.povray.org>
"Show a draft of complex.hpp
 and Sketch parser_expressions.cpp changes.  Since this will be implemented at
the source code level, lowercase i is the preferred embodiment, and should be a
reserved keyword.  The capitalized identifier is only a restriction for code
written in sdl to be scanned by the parser, to avoid lowercase reserved
keywords.  I would like both complex (a,b)+i as well as cis(theta) capability,
however the standard a+bi notation would be most preferred if not too difficult
to implement."





sketch of the parser changes to support complex numbers, including:

- `complex(a,b)` and `cis(theta)` constructors,
- a **lower‑case** reserved keyword **`i`** (imaginary unit),
- and the **`a + b i`** notation via a numeric‑literal **suffix `i`** (so
`3i` becomes a single imaginary literal, not an identifier mash‑up).



notes and citations inline.

---



Create a small, standalone header you can include from `base/types.h` or
directly from the parser. It avoids `std::complex` to keep numerics and ABI
under your control (but you *can* switch internally later).

```cpp
// File: source/base/complex.hpp
// Lightweight complex number support for POV-Ray parser & function VM.
// Depends only on <cmath> and POV-Ray's DBL typedef.

#ifndef POV_BASE_COMPLEX_HPP
#define POV_BASE_COMPLEX_HPP

#include <cmath>
#include "base/types.h" // for DBL

namespace pov {

struct Complex
{
    DBL r; // real
    DBL i; // imag

    // --- ctors ---
    constexpr Complex() : r(DBL(0)), i(DBL(0)) {}
    constexpr explicit Complex(DBL real) : r(real), i(DBL(0)) {}
    constexpr Complex(DBL real, DBL imag) : r(real), i(imag) {}

    // --- unary ---
    constexpr Complex operator+() const { return *this; }
    constexpr Complex operator-() const { return Complex(-r, -i); }

    // --- basic ops (component formulas) ---
    friend inline Complex operator+(const Complex& a, const Complex& b) { return
Complex(a.r + b.r, a.i + b.i); }
    friend inline Complex operator-(const Complex& a, const Complex& b) { return
Complex(a.r - b.r, a.i - b.i); }
    friend inline Complex operator*(const Complex& a, const Complex& b)
    {
        return Complex(a.r*b.r - a.i*b.i, a.r*b.i + a.i*b.r);
    }
    friend inline Complex operator/(const Complex& a, const Complex& b)
    {
        // Smith's algorithm for numerical stability
        DBL ar = std::fabs(b.r), ai = std::fabs(b.i);
        if (ar >= ai) {
            DBL t = b.i / b.r;
            DBL d = b.r + b.i * t;
            return Complex((a.r + a.i * t) / d, (a.i - a.r * t) / d);
        } else {
            DBL t = b.r / b.i;
            DBL d = b.r * t + b.i;
            return Complex((a.r * t + a.i) / d, (a.i * t - a.r) / d);
        }
    }

    // compound
    Complex& operator+=(const Complex& o){ r+=o.r; i+=o.i; return *this; }
    Complex& operator-=(const Complex& o){ r-=o.r; i-=o.i; return *this; }
    Complex& operator*=(const Complex& o){ DBL nr=r*o.r - i*o.i; i = r*o.i +
i*o.r; r = nr; return *this; }
    Complex& operator/=(const Complex& o){ *this = *this / o; return *this; }
};


inline Complex operator+(const Complex& z, DBL x){ return Complex(z.r + x, z.i);
}
inline Complex operator-(const Complex& z, DBL x){ return Complex(z.r - x, z.i);
}
inline Complex operator*(const Complex& z, DBL x){ return Complex(z.r * x, z.i *
x); }
inline Complex operator/(const Complex& z, DBL x){ return Complex(z.r / x, z.i /
x); }

inline Complex operator+(DBL x, const Complex& z){ return Complex(x + z.r, z.i);
}
inline Complex operator-(DBL x, const Complex& z){ return Complex(x - z.r,
-z.i); }
inline Complex operator*(DBL x, const Complex& z){ return Complex(x * z.r, x *
z.i); }
inline Complex operator/(DBL x, const Complex& z){ return Complex(x, DBL(0)) /
z; }

// comparisons: only == / != are defined (lexical epsilon-like)
inline bool operator==(const Complex& a, const Complex& b)
{
    // Use POV-Ray float equal semantics elsewhere; here a simple exact compare.
    return (a.r == b.r) && (a.i == b.i);
}
inline bool operator!=(const Complex& a, const Complex& b){ return !(a==b); }

// --- elementary functions ---

inline Complex conj(const Complex& z){ return Complex(z.r, -z.i); }
inline DBL     abs (const Complex& z){ return std::hypot(z.r, z.i); } //
magnitude
inline DBL     arg (const Complex& z){ return std::atan2(z.i, z.r); }

// exp(x+iy) = exp(x) (cos y + i sin y)
inline Complex cexp(const Complex& z)
{
    DBL ex = std::exp(z.r);
    DBL cy = std::cos(z.i), sy = std::sin(z.i);
    return Complex(ex * cy, ex * sy);
}

// log z = ln|z| + i arg(z)  (principal branch)
inline Complex clog(const Complex& z)
{
    return Complex(std::log(abs(z)), arg(z));
}

// pow(z,w) = exp(w * log z)  (principal value)
inline Complex cpow(const Complex& z, const Complex& w)
{
    return cexp( Complex(w.r * clog(z).r - w.i * clog(z).i,
                         w.r * clog(z).i + w.i * clog(z).r) );
}

inline Complex csqrt(const Complex& z)
{
    // principal square root
    DBL m = abs(z);
    DBL s = std::sqrt( (m + std::fabs(z.r)) / DBL(2) );
    DBL d = (s == 0) ? 0 : (z.i / (2*s));
    return (z.r >= 0) ? Complex(s, d) : Complex(d, (z.i >= 0 ? s : -s));
}

// trig via Euler
inline Complex csin(const Complex& z)
{
    // sin(x+iy) = sin x cosh y + i cos x sinh y
    return Complex( std::sin(z.r) * std::cosh(z.i),
                    std::cos(z.r) * std::sinh(z.i) );
}

inline Complex ccos(const Complex& z)
{
    // cos(x+iy) = cos x cosh y - i sin x sinh y
    return Complex( std::cos(z.r) * std::cosh(z.i),
                   -std::sin(z.r) * std::sinh(z.i) );
}

inline Complex ctan(const Complex& z)
{
    return csin(z) / ccos(z);
}

// hyperbolic
inline Complex csinh(const Complex& z){ return (cexp(z) -
cexp(Complex(-z.r,-z.i))) * DBL(0.5); }
inline Complex ccosh(const Complex& z){ return (cexp(z) +
cexp(Complex(-z.r,-z.i))) * DBL(0.5); }
inline Complex ctanh(const Complex& z){ return csinh(z) / ccosh(z); }

// helper constructors
inline Complex cis(DBL theta) { return Complex(std::cos(theta),
std::sin(theta)); }      // e^{i θ}
inline Complex make_complex(DBL re, DBL im){ return Complex(re, im); }

static const Complex I(0.0, 1.0); // imaginary unit; lower-case 'i' will be
reserved in the parser

} // namespace pov

#endif // POV_BASE_COMPLEX_HPP
```

**Why this way?**
- Uses `DBL` consistently with parser math.
- Implements numerically stable division and a principal‑value `pow/log`,
matching how float `pow/log` are documented to behave for scalars today.

---



Below is a *surgical* diff‑style sketch showing where to add complex
support. It assumes:

- you add a new **value kind** `VALUE_COMPLEX`,
- a **token** for `IMAGINARY_LITERAL` (produced by the tokenizer when it sees
`123i`), and
- a **built‑in identifier** `i` bound to `pov::I`.

The existing parser has centralized expression handling with helper methods that


### 2.1 Extend value representation

In (or near) `parsertypes.*`, add a complex arm to the variant; then include the
header.

```cpp
// parsertypes.h (illustrative)
enum ValueKind { VALUE_FLOAT, VALUE_VECTOR, VALUE_COLOR, VALUE_STRING, /*...*/,
VALUE_COMPLEX };

struct Value
{
    ValueKind kind;
    union {
        DBL    f;
        Vector v;   // existing
        // ...
        pov::Complex c;
    } u;
    // helpers...
    static Value FromComplex(const pov::Complex& z) {
        Value v; v.kind = VALUE_COMPLEX; v.u.c = z; return v;
    }
};
```



### 2.2 Recognize complex literals and the `i` keyword


Parser‑side:

```cpp
// parser_expressions.cpp : inside primary/atom expression parsing
switch (token.Type)
{
    case FLOAT_LITERAL_TOKEN:
        return Value::FromFloat(token.float_value);

    case IMAGINARY_LITERAL_TOKEN: // e.g. "3.5i" produced by tokenizer
        return Value::FromComplex(pov::Complex(0.0, token.float_value));

    case IDENT_TOKEN:
        if (token.is_builtin_i) { // lowercase 'i'
            return Value::FromComplex(pov::I);
        }
        // ... existing identifier handling ...
        break;

    case COMPLEX_KW_TOKEN: // "complex(" ... ")" treated as literal ctor
        // parse '(' expr ',' expr ')', both must be float-like
        {
            DBL re = ParseFloatExpression();
            Expect(COMMA_TOKEN);
            DBL im = ParseFloatExpression();
            Expect(RPAREN_TOKEN);
            return Value::FromComplex(pov::Complex(re, im));
        }

    case CIS_KW_TOKEN: // "cis("
        {
            DBL theta = ParseFloatExpression();
            Expect(RPAREN_TOKEN);
            return Value::FromComplex(pov::cis(theta));
        }
    // ...
}
```

*(Token names are illustrative; wire them to your actual enums. The important
part is: the parser sees both constructors as **literal** expressions, not as
general function calls.)*

### 2.3 Promotion helpers

Add small helpers to unify arithmetic:

```cpp
inline Value PromoteToComplex(const Value& v)
{
    if (v.kind == VALUE_COMPLEX) return v;
    if (v.kind == VALUE_FLOAT)  return Value::FromComplex( pov::Complex(v.u.f,
0.0) );
    // vectors/colors/strings are not implicitly promotable; throw parse error.
    SyntaxError("Complex arithmetic requires float or complex operands.");
    return v; // unreachable
}
```

### 2.4 Binary operators `+ - * /`

Extend the binary‑op reducer:

```cpp
Value Parser::ReduceBinaryOp(TokenType op, const Value& lhs, const Value& rhs)
{
    // Complex cases first (covers mixed real/complex via promotion)
    if (lhs.kind == VALUE_COMPLEX || rhs.kind == VALUE_COMPLEX)
    {
        pov::Complex a = (lhs.kind == VALUE_COMPLEX) ? lhs.u.c :
pov::Complex(lhs.u.f, 0.0);
        pov::Complex b = (rhs.kind == VALUE_COMPLEX) ? rhs.u.c :
pov::Complex(rhs.u.f, 0.0);

        switch (op) {
            case PLUS_TOKEN:  return Value::FromComplex(a + b);
            case MINUS_TOKEN: return Value::FromComplex(a - b);
            case MULT_TOKEN:  return Value::FromComplex(a * b);
            case DIV_TOKEN:   return Value::FromComplex(a / b);
            default: break; // fall through
        }
    }

    // ... existing float/vector operator logic ...
}
```

### 2.5 Unary minus

```cpp
Value Parser::ReduceUnaryMinus(const Value& x)
{
    if (x.kind == VALUE_COMPLEX) return Value::FromComplex( -x.u.c );
    if (x.kind == VALUE_FLOAT)  return Value::FromFloat( -x.u.f );
    // vectors/colors: existing behavior
    // ...
}
```

### 2.6 Function call dispatch (`pow`, `abs`, `re`, `im`, `arg`, `cis`, etc.)

Where scalar functions are resolved (typically in `parser_functions*.cpp`), add
the complex variants:

```cpp
// abs(z) : float magnitude when argument is complex; preserve float abs for
float
Value Parser::CallAbs(const Value& v)
{
    if (v.kind == VALUE_COMPLEX) return Value::FromFloat( pov::abs(v.u.c) );
    // existing float abs
}

// re(z) / im(z) / arg(z)
Value Parser::CallRe(const Value& v)
{
    if (v.kind == VALUE_COMPLEX) return Value::FromFloat( v.u.c.r );
    SyntaxError("re() expects a complex argument.");
}
Value Parser::CallIm(const Value& v)
{
    if (v.kind == VALUE_COMPLEX) return Value::FromFloat( v.u.c.i );
    SyntaxError("im() expects a complex argument.");
}
Value Parser::CallArg(const Value& v)
{
    if (v.kind == VALUE_COMPLEX) return Value::FromFloat( pov::arg(v.u.c) );
    SyntaxError("arg() expects a complex argument.");
}

// pow(a,b)
Value Parser::CallPow(const Value& a, const Value& b)
{
    if (a.kind == VALUE_COMPLEX || b.kind == VALUE_COMPLEX)
    {
        pov::Complex ac = (a.kind == VALUE_COMPLEX) ? a.u.c :
pov::Complex(a.u.f,0);
        pov::Complex bc = (b.kind == VALUE_COMPLEX) ? b.u.c :
pov::Complex(b.u.f,0);
        return Value::FromComplex( pov::cpow(ac, bc) );
    }
    // existing float pow
}
```

> The function catalog already enumerates float functions that are legal in
expressions and in `function{}` blocks; you

ts.

---

## 3) Tokenizer / reserved word notes (for `a + b i` and lowercase `i`)

While you asked for parser changes, **the `3i` case must be handled by the
tokenizer**; otherwise `3i` is a single (invalid) identifier token. Add:

- **A numeric‑literal suffix `i`** to produce `IMAGINARY_LITERAL_TOKEN`
with payload `float_value = 3.0` → parser converts to `0 + 3i`.
- **A reserved built‑in identifier `i`** mapped to the value `pov::I`
(0+1i) in the symbol table. This lives in `reservedwords.*` and
symbol‑table bootstrap.

Sketch in `parser_tokenizer.cpp` (where numeric literals are finalized):

```cpp
// After scanning a FLOAT_LITERAL: check for immediate 'i' suffix without
whitespace.
if (CurrentChar() == 'i' || CurrentChar() == 'I') {
    Advance(); // consume 'i'
    token.Type = IMAGINARY_LITERAL_TOKEN;
    token.float_value = scanned_number; // imag part only
    return token;
}
```

Add `i` to reserved words:

```cpp
// reservedwords.cpp
{ "i", I_BUILTIN_TOKEN }, // lowercase preferred embodiment (user request)
{ "complex", COMPLEX_KW_TOKEN },
{ "cis",     CIS_KW_TOKEN },
{ "re", RE_KW_TOKEN }, { "im", IM_KW_TOKEN }, { "arg", ARG_KW_TOKEN },
```


`Value::FromComplex(pov::I)` during parser init. (Compare how `pi`, `version`,
and other built‑ins are installed.)

---

## 4) SDL surface: how it looks after these changes

```pov
#declare Z1 = 2 + 3i;                 // suffix 'i' → imaginary literal
(parser builds 0+3i)
#declare Z2 = complex(1, -2) + cis(pi/6);
#declare R  = abs(Z1);                // float magnitude
#declare TH = arg(Z1);                // float angle

// Works in general expressions:
#debug concat("Z1=", str(re(Z1),0,3), "+", str(im(Z1),0,3), "i\n")

// Works inside function{} (if you also add VM support or wrappers; see earlier
plan)
isosurface {
  function { abs( (x + i*y) * (x - i*y) ) - 1 }
  contained_by { box { <-2,-2,-2>, <2,2,2> } }
  max_gradient 5
}
```


ther (A) add complex to the VM (best), or (B) expose float‑returning wrappers
(e.g., `re(c_sin(x,y))`) until you 
wire complex through. See the earlier plan I sent for both paths.

---

## 5) Why this fits the codebase

- **Files to modify**: `source/parser/parser_expressions.cpp`,
`parser_functions*.cpp`, `parser_tokenizer.cpp`, `reservedwords.*`,
`parsertypes.*`, and `base/types.h` include path to `base/complex.hpp`. These
are the same modules that own expression parsing today.


type‑overloads and two new literal forms (`complex()`/`cis()` and the `i`
suffix).

---

## 6) Next steps (I can help draft these too)

1. **Token/enum plumbing**: add `IMAGINARY_LITERAL_TOKEN`, `COMPLEX_KW_TOKEN`,
`CIS_KW_TOKEN`, and function tokens for `re/im/arg`. Wire into
`reservedwords.*`.
2. **Symbol bootstrap**: insert built‑in `i` into the global table at
parser init.
3. **Function VM**: choose Strategy A (native complex in VM) vs Strategy B


identities; `pow` branch‑cut sanity.



---


`reservedwords` lines, tokenizer suffix detection, plus a
minimally‑invasive `parser_expressions.cpp` change set (guarded by a
`#define POV_ENABLE_COMPLEX` flag) so you can try `2+3i`, `complex()`, and
`cis()` in the SDL right away.

**References**

`parser_tokenizer.cpp`, `reservedwords.*`, `parsertypes.*` (POV‑Ray
GitHub).
- Expression grammar & function catalog (float/vector) to be extended for
complex: POV‑Ray docs & wiki.
- Function include pattern (`functions.inc`) as a precedent for adding wrappers
if you stage VM work.


Post a reply to this message

From: Bald Eagle
Subject: Re: Complex Numbers
Date: 29 Oct 2025 14:55:00
Message: <web.690262f5793e4bec7459de3125979125@news.povray.org>

**`i` suffix**, all bundled as a zip you can download.

**Download:**
[complex_povray_patch.zip](blob:https://m365.cloud.microsoft/a301547a-bdae-4511-b0ac-75d3eafa5175)

The zip contains:


and arithmetic in `parser_expressions.cpp`.

  - add a new token `IMAGINARY_LITERAL_TOKEN` (e.g., `3i`, `2.5i`),
  - recognize `complex(`, `cis(`, `re`, `im`, `arg` tokens,
  - and (optionally) install the lower-case built-in constant `i` (0+1i).

referenced by the parser.



> The patches are written to be **fuzzy‑applicable** against the current
`master` layout. Exact line numbers may 

sed on the public repo and current parser structure.

---



The patch:

- Adds `#include "base/complex.hpp"` so the parser can construct and operate on
complex values.
- Teaches the **primary expression** parser to recognize:

  - `complex(re, im)` → builds a complex literal,
  - `cis(theta)` → builds `cos θ + i sin θ`.
- Extends **binary operator** reduction to handle `+ - * /` when either side is
complex (promotes floats to complex).
- Extends **unary minus** for complex.
- Overloads **`pow()`** to return complex when any operand is complex; overloads
**`abs()`** to return magnitude for complex; implements **`re()`**, **`im()`**,
**`arg()`** returning floats.



```diff
*** a/source/parser/parser_expressions.cpp
--- b/source/parser/parser_expressions.cpp
***************
*** 1,10 ****
   // ... file prolog omitted for brevity ...
   #include "parser/parser.h"
   #include "parser/parser_expressions.h"
   #include "base/povassert.h"
   #include <cmath>
+  #include "base/complex.hpp" // [COMPLEX] draft include for Complex math

   using namespace pov;

--- 1,11 ----
   // ... file prolog omitted for brevity ...
   #include "parser/parser.h"
   #include "parser/parser_expressions.h"
   #include "base/povassert.h"
   #include <cmath>
+  #include "base/complex.hpp" // [COMPLEX] draft include for Complex math

   using namespace pov;

***************
*** 200,230 ****
-  // Existing atom/primary expression parsing here...
-  // switch (Tok.Type) { FLOAT_LITERAL_TOKEN, IDENT_TOKEN, ... }
+  // [COMPLEX] Recognize imaginary literal tokens (e.g., 3i) and complex/cis
constructors
+  Value Parser::ParsePrimary()
+  {
+      Token Tok = CurrentToken();
+      switch (Tok.Type)
+      {
+          case FLOAT_LITERAL_TOKEN:
+              NextToken();
+              return Value::FromFloat(Tok.Float);
+
+          case IMAGINARY_LITERAL_TOKEN: // new token from tokenizer suffix 'i'
+          {
+              NextToken();
+              pov::Complex z(0.0, Tok.Float);
+              return Value::FromComplex(z);
+          }
+
+          case COMPLEX_KW_TOKEN: // complex(
+          {
+              NextToken(); Expect(LPAREN_TOKEN);
+              DBL re = ParseFloatExpression();
+              Expect(COMMA_TOKEN);
+              DBL im = ParseFloatExpression();
+              Expect(RPAREN_TOKEN);
+              return Value::FromComplex( pov::Complex(re, im) );
+          }
+
+          case CIS_KW_TOKEN: // cis(
+          {
+              NextToken(); Expect(LPAREN_TOKEN);
+              DBL th = ParseFloatExpression();
+              Expect(RPAREN_TOKEN);
+              return Value::FromComplex( pov::cis(th) );
+          }
+
+          default:
+              return ParsePrimary_Existing(); // fall back to existing
implementation
+      }
+  }

***************
*** 420,490 ****
-  // Existing binary operator reduction
+  // [COMPLEX] Extend binary ops for complex + promotion from float
   Value Parser::ReduceBinaryOp(TokenType op, const Value& lhs, const Value&
rhs)
   {
-      // existing float/vector handling ...
+      if (lhs.kind == VALUE_COMPLEX || rhs.kind == VALUE_COMPLEX)
+      {
+          pov::Complex a = (lhs.kind == VALUE_COMPLEX) ? lhs.u.c :
pov::Complex(lhs.u.f, 0);
+          pov::Complex b = (rhs.kind == VALUE_COMPLEX) ? rhs.u.c :
pov::Complex(rhs.u.f, 0);
+
+          switch (op)
+          {
+              case PLUS_TOKEN:  return Value::FromComplex(a + b);
+              case MINUS_TOKEN: return Value::FromComplex(a - b);
+              case MULT_TOKEN:  return Value::FromComplex(a * b);
+              case DIV_TOKEN:   return Value::FromComplex(a / b);
+              default: break; // defer others to existing code
+          }
+      }
+      // existing float/vector handling follows ...
       return ReduceBinaryOp_Existing(op, lhs, rhs);
   }

***************
*** 500,560 ****
-  // Existing unary minus handling
+  // [COMPLEX] unary minus
   Value Parser::ReduceUnaryMinus(const Value& x)
   {
-      // existing code
+      if (x.kind == VALUE_COMPLEX)
+          return Value::FromComplex(-x.u.c);
+      return ReduceUnaryMinus_Existing(x);
   }

***************
*** 700,790 ****
-  // Existing function dispatch such as pow(), abs(), etc.
+  // [COMPLEX] overload pow/abs/re/im/arg
   Value Parser::CallPow(const Value& a, const Value& b)
   {
-      // existing float pow
+      if (a.kind == VALUE_COMPLEX || b.kind == VALUE_COMPLEX)
+      {
+          pov::Complex ac = (a.kind == VALUE_COMPLEX) ? a.u.c :
pov::Complex(a.u.f,0);
+          pov::Complex bc = (b.kind == VALUE_COMPLEX) ? b.u.c :
pov::Complex(b.u.f,0);
+          return Value::FromComplex( pov::cpow(ac, bc) );
+      }
+      return CallPow_Existing(a,b);
   }

   Value Parser::CallAbs(const Value& v)
   {
-      // existing float abs
+      if (v.kind == VALUE_COMPLEX)
+          return Value::FromFloat( pov::abs(v.u.c) );
+      return CallAbs_Existing(v);
   }

   Value Parser::CallRe(const Value& v)
   {
+      if (v.kind == VALUE_COMPLEX) return Value::FromFloat(v.u.c.r);
       SyntaxError("re() expects complex argument");
   }

   Value Parser::CallIm(const Value& v)
   {
+      if (v.kind == VALUE_COMPLEX) return Value::FromFloat(v.u.c.i);
       SyntaxError("im() expects complex argument");
   }

   Value Parser::CallArg(const Value& v)
   {
+      if (v.kind == VALUE_COMPLEX) return Value::FromFloat( pov::arg(v.u.c) );
       SyntaxError("arg() expects complex argument");
   }
```


differences (e.g., if your implementation names these helper methods differently).

---

## 2) How to implement the **`i` suffix** in the tokenizer

**Goal:** Treat `3i`, `2.5i`, `1e-3i`, etc., as **one token** of a new type

without guessing or backtracking. This change happens right after scanning a

the token type.

You also add convenient keyword tokens for `complex`, `cis`, `re`, `im`, `arg`
and (optionally) install a built‑in lower‑case `i` constant (equal
to `0+1i`) into the symbol table during parser initialization.

The included patch (`patches/tokenizer_i_suffix.patch`) makes all these changes.
Key parts:

```diff
*** a/source/parser/parser.h
--- b/source/parser/parser.h
***************
*** 150,175 ****
    enum TokenType {
        // ... existing tokens ...
        FLOAT_LITERAL_TOKEN = 1000,
+       IMAGINARY_LITERAL_TOKEN,   // [COMPLEX] e.g., 3i, 2.5i
        IDENT_TOKEN,
        // keywords ...
+       COMPLEX_KW_TOKEN,          // complex(
+       CIS_KW_TOKEN,              // cis(
+       RE_KW_TOKEN, IM_KW_TOKEN, ARG_KW_TOKEN,
    };
```

```diff
*** a/source/parser/parser_tokenizer.cpp
--- b/source/parser/parser_tokenizer.cpp
***************
*** 300,380 ****
    // After scanning a numeric literal (FLOAT_LITERAL_TOKEN), check for 'i'
suffix
    // with no intervening whitespace.
    if (token.Type == FLOAT_LITERAL_TOKEN)
    {
        int c = PeekChar();
        if (c == 'i' || c == 'I')
        {
            // verify there's no whitespace between number and 'i'
            if (!SawWhitespaceSinceLastChar())
            {
                GetChar(); // consume 'i'
                token.Type = IMAGINARY_LITERAL_TOKEN;
                // token.Float already holds the numeric part
                return token;
            }
        }
    }

    return token;
```

```diff
*** a/source/parser/reservedwords.cpp
--- b/source/parser/reservedwords.cpp
***************
*** 90,120 ****
    static const ReservedWord s_reserved[] = {
        // ... existing ...
+       { "complex", COMPLEX_KW_TOKEN },
+       { "cis",     CIS_KW_TOKEN },
+       { "re",      RE_KW_TOKEN },
+       { "im",      IM_KW_TOKEN },
+       { "arg",     ARG_KW_TOKEN },
+       { "i",       I_BUILTIN_TOKEN }, // lowercase imaginary unit constant
    };
```

```diff
*** a/source/parser/parser.cpp
--- b/source/parser/parser.cpp
***************
*** 500,560 ****
    void Parser::InstallBuiltins()
    {
        // existing built-ins (pi, version, etc.)
+       // [COMPLEX] install lowercase 'i' as a complex constant
+       Value zi; zi.kind = VALUE_COMPLEX; zi.u.c = pov::I;
+       Symbols->InsertBuiltin("i", zi);
    }
```

> The exact token/enum locations depend on your copy of `parser.h` and the tokenizer
structure. The intent is clear: (1
) add a new token for imaginary literals, (2) promote `complex`/`cis`/`re`/`im`/`arg`
to keyword tokens, (3) register `
i` as a built‑in symbol. See the upstream files for reference.

---

## 3) `complex.hpp` helper (included)

The parser and function dispatch rely on a minimal complex implementation that:


- provides robust division, principal `log`, `pow`, `sqrt`,
- and the helpers you need: `abs`, `arg`, `cis`, `re`, `im` via direct field
access.

This file sits at `source/base/complex.hpp` (path chosen to match existing base
headers, which are widely included by parser modules).

---

## 4) Apply & test quickly

1. Unzip into your working tree (anywhere).
2. Copy `src/base/complex.hpp` to `source/base/complex.hpp`.
3. Apply patches:

```bash
patch -p1 < patches/tokenizer_i_suffix.patch
patch -p1 < patches/parser_expressions.patch
```

4. Build.
5. Try a tiny SDL:

```pov
#declare Z1 = 2 + 3i;
#declare Z2 = complex(1, -2) + cis(pi/6);
#debug concat("Z1=", str(re(Z1),0,3), "+", str(im(Z1),0,3), "i\n")
```


(a) add complex to the Function VM as discussed previously, or (b) temporarily
reduce to floats with `abs/re/im/arg` until the VM is extended. The *parsing*
side is handled by these patches.

---

## Pointers to repo structure (for context)

- Parser & expression code lives under **`source/parser/`** (including
`parser_expressions.cpp`, tokenizer, and reserved words).

documented here; `pow()` is the standard way to exponentiate (no `^` operator),
which is why overloading `pow` is the right entry point.

---

If you want me to also generate a **reserved‑keyword scan test** and a


build & render as smoke tests.


Post a reply to this message


Attachments:
Download 'complex_povray_patch.zip' (5 KB)

From: ingo
Subject: Re: Complex Numbers
Date: 29 Oct 2025 15:30:00
Message: <web.69026ae2793e4bec17bac71e8ffb8ce3@news.povray.org>
"Bald Eagle" <cre### [at] netscapenet> wrote:
> "Given the code base at https://github.com/POV-Ray/povray, what would be
> required to fully implement robust  code to handle complex numbers - complete
> with addition, subtraction, division, multiplication, and exponentiation - using
> both complex and real numbers in any combination?"

#declare complexnumber = cmp<re, im>;

it's kind of vector hence the vector notation and fits with the colour vectors
rgb<1,1,1>. All the re and im <x, y> extraction code should be available in
POV-Ray, it c++ after al ...(can't read it, always get lost)

ingo


Post a reply to this message

From: Bald Eagle
Subject: Re: Complex Numbers
Date: 29 Oct 2025 15:40:00
Message: <web.69026d7b793e4bec7459de3125979125@news.povray.org>
"ingo" <nomail@nomail> wrote:

> #declare complexnumber = cmp<re, im>;
>
> it's kind of vector hence the vector notation and fits with the colour vectors
> rgb<1,1,1>. All the re and im <x, y> extraction code should be available in
> POV-Ray, it c++ after al ...(can't read it, always get lost)

I have written such SDL-side macros to use that type of formulation, however, I
was looking to see what needed to be changed / added at the source code level,
so it could be natively available, fast, useable in isosurfaces, and assignable
to POV-space cardinal axes.

(see threads with complex macros, and discussions about complex numbers with
clipka)

Just exploring options at this point, and hoping the c++ programmers and
dev-types (Jerome, WFP, etc) will look some of it over and be inspired, comment,
and perhaps even try implementing.

In the absence of anything else I'm just trying to relentlessly keep _pushing
forward_.

- BW


Post a reply to this message

From: kurtz le pirate
Subject: Re: Complex Numbers
Date: 30 Oct 2025 06:05:27
Message: <69033867$1@news.povray.org>
On 29/10/2025 20:39, Bald Eagle wrote:
> 
> Just exploring options at this point, and hoping the c++ programmers and
> dev-types (Jerome, WFP, etc) will look some of it over and be inspired, comment,
> and perhaps even try implementing.
> 



If I remember correctly, WFP has already added the complexes in a 
slightly more transparent way in version 4.0.







-- 
kurtz le pirate
compagnie de la banquise


Post a reply to this message

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