fp64 catastrophic cancellation

fp64 catastrophic cancellation

The numerics-textbook plot on a GPU.

fp64 catastrophic cancellation shader, written in TypeScript and compiled by TypeShade, rendered at build time.
// ═══ typeshade example — fp64 catastrophic cancellation ═══
//
// The numerics-textbook figure, live on the GPU: (x−1)⁷ EVALUATED IN EXPANDED
// FORM (x⁷ − 7x⁶ + 21x⁵ − 35x⁴ + 35x³ − 21x² + 7x − 1) near x = 1. The terms
// are all ~1 while the true value is ~w⁷ ≈ 10⁻⁹ — eight ~1-sized numbers must
// cancel to nine digits, which f32 (7 digits) cannot do AT ALL: its result is
// ±5×10⁻⁶ NOISE, thousands of times the whole plot range (CPU-verified:
// 7300× signal). The df64 side (~14 digits) hugs the true curve. The thin
// reference line is the FACTORED form ((x−1)⁷ — subtract first, no
// cancellation), which even f32 evaluates cleanly: the fix is always to
// restructure the math, and fp64 is for when you can't.
//
// Narrow the half-width slider and even df64 starts to fray (w⁷ sinks toward
// its ~14-digit floor) — the same budget wall, two decades further out.
//
// The `_fp64` guard uniform is auto-injected by the lowering; the render
// harnesses bind it to 1.0f by probing the program for the Fp64Guard block.
import {
fn,
module,
vec3,
vec4,
f32,
f64,
pow,
fract,
abs,
min,
mix,
step,
smoothstep,
toF32,
toF64,
f32T,
vec2fT,
Let,
uniformStruct,
} from '../src/index.js';
import { VsOut, vs } from './_fullscreen.js';
import type { ShaderExample } from './_shared.js';
const U = uniformStruct(
'Uniforms',
{ group: 0, binding: 0, as: 'u' },
{
resolution: vec2fT,
half_width: f32T, // plot spans x ∈ [1−w, 1+w]
fp64: f32T, // toggle: 1 = split-screen f32 | f64 (canonical), 0 = all-f32
},
);
const fsCancel = fn(
'fs_cancel',
{ vo: VsOut },
(p) => {
const w = Let(U.field.half_width);
const halfUv = Let(p.vo.uv.x.mul(2.0));
const sx = Let(halfUv.sub(p.vo.uv.x.lt(0.5).select(0.0, 1.0)));
const u = Let(sx.sub(0.5).mul(w.mul(2.0))); // x − 1 ∈ [−w, w]
const isF32 = Let(p.vo.uv.x.lt(0.5).or(U.field.fp64.lt(0.5)));
// f64 — the expanded polynomial, every term in extended precision; only
// the ~w⁷-sized RESULT narrows (small values narrow harmlessly: f32
// precision is relative).
const xd = Let(f64(1).add(toF64(u)));
const x2 = Let(xd.mul(xd));
const x3 = Let(x2.mul(xd));
const x4 = Let(x2.mul(x2));
const x5 = Let(x4.mul(xd));
const x6 = Let(x3.mul(x3));
const x7 = Let(x6.mul(xd));
const p64 = Let(
toF32(
x7
.sub(x6.mul(7.0))
.add(x5.mul(21.0))
.sub(x4.mul(35.0))
.add(x3.mul(35.0))
.sub(x2.mul(21.0))
.add(xd.mul(7.0))
.sub(1.0),
),
);
// f32 twin — SAME expanded polynomial: eight ~1-sized terms, seven digits.
const xf = Let(f32(1).add(u));
const f2 = Let(xf.mul(xf));
const f3 = Let(f2.mul(xf));
const f4 = Let(f2.mul(f2));
const f5 = Let(f4.mul(xf));
const f6 = Let(f3.mul(f3));
const f7 = Let(f6.mul(xf));
const p32 = Let(
f7
.sub(f6.mul(7.0))
.add(f5.mul(21.0))
.sub(f4.mul(35.0))
.add(f3.mul(35.0))
.sub(f2.mul(21.0))
.add(xf.mul(7.0))
.sub(1.0),
);
const pv = Let(isF32.select(p32, p64));
// Plot in units of the true amplitude w⁷ (the picture is w-invariant).
const yscale = Let(pow(w, f32(7)).mul(1.3));
const v = Let(pv.div(yscale)); // computed curve, ±0.77 at the edges
const u2 = Let(u.mul(u));
const truth = Let(u2.mul(u2).mul(u2).mul(u).div(yscale)); // factored (x−1)⁷ — no cancellation
const py = Let(p.vo.uv.y.sub(0.5).mul(2.0));
const px = Let(f32(2).div(U.field.resolution.y)); // plot-units per pixel
// Graph-paper styling: parchment with a pale grid (10 columns per half,
// 0.2-plot-unit rows), ink axes.
const gxf = Let(fract(sx.mul(10.0)));
const gyf = Let(fract(py.add(1.0).mul(5.0)));
const dgx = Let(min(gxf, f32(1).sub(gxf))); // distance to column line, in cells
const dgy = Let(min(gyf, f32(1).sub(gyf)));
const aaCx = Let(f32(30).div(U.field.resolution.x)); // ~1.5 px in cell units
const aaCy = Let(f32(15).div(U.field.resolution.y));
const grid = Let(
f32(1)
.sub(smoothstep(f32(0), aaCx, dgx))
.add(f32(1).sub(smoothstep(f32(0), aaCy, dgy))),
);
const paper = vec3(0.96, 0.94, 0.88);
const rgb0 = Let(mix(paper, vec3(0.72, 0.78, 0.86), min(grid, f32(1)).mul(0.45)));
// Fill below the computed curve — its BOUNDARY is the visible verdict:
// a smooth odd curve on the f64 side, a full-height noise barcode on f32.
const fill = Let(step(py, v));
const rgb1 = Let(mix(rgb0, vec3(0.62, 0.74, 0.9), fill.mul(0.5)));
// Ink the computed curve where it is on-screen and locally flat enough.
const ink = Let(f32(1).sub(smoothstep(px.mul(1.2), px.mul(3.0), abs(v.sub(py)))));
const rgb2 = Let(mix(rgb1, vec3(0.13, 0.16, 0.3), ink.mul(0.85)));
// The factored-form reference in warm red — clean on BOTH halves.
const ref = Let(f32(1).sub(smoothstep(px.mul(0.8), px.mul(2.2), abs(truth.sub(py)))));
const rgb3 = Let(mix(rgb2, vec3(0.8, 0.25, 0.2), ref.mul(0.65)));
// Axes: y = 0 and x = 1 (the cancellation point).
const axis = Let(
min(
smoothstep(f32(0), px.mul(1.5), abs(py)),
smoothstep(f32(0), px.mul(1.5), abs(sx.sub(0.5)).mul(2.0)),
),
);
const rgb = Let(mix(vec3(0.35, 0.33, 0.3), rgb3, axis));
return vec4(rgb, f32(1));
},
{ stage: 'fragment', retAttr: '@location(0)' },
);
// `_fp64` guard lands at (group 0, binding 1) automatically.
const fp64CancellationModule = module({
funcs: [vs, fsCancel],
uses: [U, VsOut],
});
export const fp64Cancellation: ShaderExample = {
id: 'fp64-cancellation',
title: 'fp64 catastrophic cancellation',
blurb:
'The numerics-textbook plot on a GPU: (x−1)⁷ evaluated in EXPANDED form near x = 1 — eight ~1-sized terms must cancel to nine digits. f32 (left) returns pure noise thousands of times the plot range; emulated f64 (right) hugs the true curve. The thin red reference is the FACTORED form, which even f32 nails — restructure the math when you can, reach for fp64 when you can’t. Narrow the width slider and watch df64 hit its own wall two decades later.',
category: 'generic',
file: 'fp64-cancellation.ts',
module: fp64CancellationModule,
renderable: true,
splitLabels: ['f32 expanded', 'f64 expanded'],
controls: {
resolution: { kind: 'resolution' },
half_width: {
kind: 'slider',
label: 'Half-width w',
min: 0.012,
max: 0.12,
step: 0.002,
value: 0.05,
wheel: true,
},
fp64: { kind: 'toggle', label: 'fp64 emulation', value: true },
},
};
struct Uniforms {
resolution: vec2<f32>,
half_width: f32,
fp64: f32,
}
struct VsOut {
@builtin(position) pos: vec4<f32>,
@location(0) uv: vec2<f32>,
}
@group(0) @binding(0) var<uniform> u: Uniforms;
@group(0) @binding(1) var _fp64: texture_2d<f32>;
@vertex
fn vs(@builtin(vertex_index) vi: u32) -> VsOut {
let _cse0 = ((f32((vi & 1u)) * 4.0) - 1.0);
let _cse1 = ((f32((vi >> 1u)) * 4.0) - 1.0);
return VsOut(vec4<f32>(_cse0, _cse1, 0.0, 1.0), vec2<f32>(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5)));
}
@fragment
fn fs_cancel(vo: VsOut) -> @location(0) vec4<f32> {
let _fp64_g = textureLoad(_fp64, vec2<i32>(0, 0), 0).x;
let _v0 = u.half_width;
let _v1 = (vo.uv.x * 2.0);
let _cse0 = (vo.uv.x < 0.5);
let _v2 = (_v1 - select(1.0, 0.0, _cse0));
let _gv0 = (_v2 - 0.5);
let _v3 = (_gv0 * (_v0 * 2.0));
let _v4 = (_cse0 || (u.fp64 < 0.5));
let _cse1 = vec2<f32>(1.0, 0.0);
let _v5 = df64_add(_cse1, vec2<f32>(_v3, 0.0), _fp64_g);
let _v6 = df64_sqr(_v5, _fp64_g);
let _v7 = df64_mul(_v6, _v5, _fp64_g);
let _v8 = df64_sqr(_v6, _fp64_g);
let _v9 = df64_mul(_v8, _v5, _fp64_g);
let _v10 = df64_sqr(_v7, _fp64_g);
let _v11 = df64_mul(_v10, _v5, _fp64_g);
let _cse6 = bitcast<f32>(bitcast<u32>(0.0));
let _cse2 = vec2<f32>(_cse6, _cse6);
let _cse3 = vec2<f32>(7.0, 0.0);
let _cse4 = vec2<f32>(21.0, 0.0);
let _cse5 = vec2<f32>(35.0, 0.0);
let _v12 = df64_narrow(df64_sub(df64_add(df64_sub(df64_add(df64_sub(df64_add(df64_sub(df64_add(_v11, _cse2, _fp64_g), df64_mul(_v10, _cse3, _fp64_g), _fp64_g), df64_mul(_v9, _cse4, _fp64_g), _fp64_g), df64_mul(_v8, _cse5, _fp64_g), _fp64_g), df64_mul(_v7, _cse5, _fp64_g), _fp64_g), df64_mul(_v6, _cse4, _fp64_g), _fp64_g), df64_mul(_v5, _cse3, _fp64_g), _fp64_g), df64_add(_cse1, _cse2, _fp64_g), _fp64_g));
let _v13 = (1.0 + _v3);
let _v14 = (_v13 * _v13);
let _v15 = (_v14 * _v13);
let _v16 = (_v14 * _v14);
let _v17 = (_v16 * _v13);
let _v18 = (_v15 * _v15);
let _v19 = (_v18 * _v13);
let _v20 = (((((((_v19 - (_v18 * 7.0)) + (_v17 * 21.0)) - (_v16 * 35.0)) + (_v15 * 35.0)) - (_v14 * 21.0)) + (_v13 * 7.0)) - 1.0);
let _v21 = select(_v12, _v20, _v4);
let _v22 = (pow(_v0, 7.0) * 1.3);
let _v23 = (_v21 / _v22);
let _v24 = (_v3 * _v3);
let _v25 = ((((_v24 * _v24) * _v24) * _v3) / _v22);
let _v26 = ((vo.uv.y - 0.5) * 2.0);
let _v27 = (2.0 / u.resolution.y);
let _v28 = fract((_v2 * 10.0));
let _v29 = fract(((_v26 + 1.0) * 5.0));
let _v30 = min(_v28, (1.0 - _v28));
let _v31 = min(_v29, (1.0 - _v29));
let _v32 = (30.0 / u.resolution.x);
let _v33 = (15.0 / u.resolution.y);
let _v34 = ((1.0 - smoothstep(0.0, _v32, _v30)) + (1.0 - smoothstep(0.0, _v33, _v31)));
let _v35 = mix(vec3<f32>(0.96, 0.94, 0.88), vec3<f32>(0.72, 0.78, 0.86), (min(_v34, 1.0) * 0.45));
let _v36 = step(_v26, _v23);
let _v37 = mix(_v35, vec3<f32>(0.62, 0.74, 0.9), (_v36 * 0.5));
let _v38 = (1.0 - smoothstep((_v27 * 1.2), (_v27 * 3.0), abs((_v23 - _v26))));
let _v39 = mix(_v37, vec3<f32>(0.13, 0.16, 0.3), (_v38 * 0.85));
let _v40 = (1.0 - smoothstep((_v27 * 0.8), (_v27 * 2.2), abs((_v25 - _v26))));
let _v41 = mix(_v39, vec3<f32>(0.8, 0.25, 0.2), (_v40 * 0.65));
let _lc0 = (_v27 * 1.5);
let _v42 = min(smoothstep(0.0, _lc0, abs(_v26)), smoothstep(0.0, _lc0, (abs(_gv0) * 2.0)));
let _v43 = mix(vec3<f32>(0.35, 0.33, 0.3), _v41, _v42);
return vec4<f32>(_v43, 1.0);
}
fn df64_twoSum(a: f32, b: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = (a + b);
let _v1 = (((_v0 * _fp64_g) - a) * _fp64_g);
let _v2 = (((a - ((_v0 - _v1) * _fp64_g)) * _fp64_g) + (b - _v1));
return vec2<f32>(_v0, _v2);
}
fn df64_quickTwoSum(a: f32, b: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = ((a + b) * _fp64_g);
let _v1 = (b - ((_v0 - a) * _fp64_g));
return vec2<f32>(_v0, _v1);
}
fn df64_split(a: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = (a * (_fp64_g * 4097.0));
let _v1 = ((_v0 * _fp64_g) - (_v0 - a));
let _v2 = ((a * _fp64_g) - _v1);
return vec2<f32>(_v1, _v2);
}
fn df64_twoProd(a: f32, b: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = (a * b);
let _v1 = df64_split(a, _fp64_g);
let _v2 = df64_split(b, _fp64_g);
let _v3 = (((((_v1.x * _v2.x) - _v0) + (_v1.x * _v2.y)) + (_v1.y * _v2.x)) + (_v1.y * _v2.y));
return vec2<f32>(_v0, _v3);
}
fn df64_twoSqr(a: f32, _fp64_g: f32) -> vec2<f32> {
let _v0 = (a * a);
let _v1 = df64_split(a, _fp64_g);
let _v2 = (((((_v1.x * _v1.x) - _v0) * _fp64_g) + (((_v1.x * _v1.y) * 2.0) * _fp64_g)) + ((_v1.y * _v1.y) * _fp64_g));
return vec2<f32>(_v0, _v2);
}
fn df64_add(a: vec2<f32>, b: vec2<f32>, _fp64_g: f32) -> vec2<f32> {
var _v0: vec2<f32> = df64_twoSum(a.x, b.x, _fp64_g);
let _v1 = df64_twoSum(a.y, b.y, _fp64_g);
_v0.y = (_v0.y + _v1.x);
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
_v0.y = (_v0.y + _v1.y);
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
return _v0;
}
fn df64_sub(a: vec2<f32>, b: vec2<f32>, _fp64_g: f32) -> vec2<f32> {
return df64_add(a, (-b), _fp64_g);
}
fn df64_mul(a: vec2<f32>, b: vec2<f32>, _fp64_g: f32) -> vec2<f32> {
var _v0: vec2<f32> = df64_twoProd(a.x, b.x, _fp64_g);
_v0.y = (_v0.y + (a.x * b.y));
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
_v0.y = (_v0.y + (a.y * b.x));
return df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
}
fn df64_sqr(a: vec2<f32>, _fp64_g: f32) -> vec2<f32> {
var _v0: vec2<f32> = df64_twoSqr(a.x, _fp64_g);
_v0.y = (_v0.y + ((a.x * a.y) * 2.0));
return df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
}
fn df64_narrow(a: vec2<f32>) -> f32 {
return (a.x + a.y);
}
#version 300 es
precision highp float;
precision highp int;
out vec2 uv;
void main() {
uint vi = uint(gl_VertexID);
float _cse0 = ((float((vi & 1u)) * 4.0) - 1.0);
float _cse1 = ((float((vi >> 1u)) * 4.0) - 1.0);
gl_Position = vec4(_cse0, _cse1, 0.0, 1.0);
uv = vec2(((_cse0 * 0.5) + 0.5), ((_cse1 * 0.5) + 0.5));
}
#version 300 es
precision highp float;
precision highp int;
layout(std140) uniform Uniforms {
vec2 resolution;
float half_width;
float fp64;
} u;
uniform highp sampler2D _fp64;
vec2 df64_twoSum(float a, float b, float _fp64_g) {
float _v0 = (a + b);
float _v1 = (((_v0 * _fp64_g) - a) * _fp64_g);
float _v2 = (((a - ((_v0 - _v1) * _fp64_g)) * _fp64_g) + (b - _v1));
return vec2(_v0, _v2);
}
vec2 df64_quickTwoSum(float a, float b, float _fp64_g) {
float _v0 = ((a + b) * _fp64_g);
float _v1 = (b - ((_v0 - a) * _fp64_g));
return vec2(_v0, _v1);
}
vec2 df64_split(float a, float _fp64_g) {
float _v0 = (a * (_fp64_g * 4097.0));
float _v1 = ((_v0 * _fp64_g) - (_v0 - a));
float _v2 = ((a * _fp64_g) - _v1);
return vec2(_v1, _v2);
}
vec2 df64_twoProd(float a, float b, float _fp64_g) {
float _v0 = (a * b);
vec2 _v1 = df64_split(a, _fp64_g);
vec2 _v2 = df64_split(b, _fp64_g);
float _v3 = (((((_v1.x * _v2.x) - _v0) + (_v1.x * _v2.y)) + (_v1.y * _v2.x)) + (_v1.y * _v2.y));
return vec2(_v0, _v3);
}
vec2 df64_twoSqr(float a, float _fp64_g) {
float _v0 = (a * a);
vec2 _v1 = df64_split(a, _fp64_g);
float _v2 = (((((_v1.x * _v1.x) - _v0) * _fp64_g) + (((_v1.x * _v1.y) * 2.0) * _fp64_g)) + ((_v1.y * _v1.y) * _fp64_g));
return vec2(_v0, _v2);
}
vec2 df64_add(vec2 a, vec2 b, float _fp64_g) {
vec2 _v0 = df64_twoSum(a.x, b.x, _fp64_g);
vec2 _v1 = df64_twoSum(a.y, b.y, _fp64_g);
_v0.y = (_v0.y + _v1.x);
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
_v0.y = (_v0.y + _v1.y);
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
return _v0;
}
vec2 df64_sub(vec2 a, vec2 b, float _fp64_g) {
return df64_add(a, (-b), _fp64_g);
}
vec2 df64_mul(vec2 a, vec2 b, float _fp64_g) {
vec2 _v0 = df64_twoProd(a.x, b.x, _fp64_g);
_v0.y = (_v0.y + (a.x * b.y));
_v0 = df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
_v0.y = (_v0.y + (a.y * b.x));
return df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
}
vec2 df64_sqr(vec2 a, float _fp64_g) {
vec2 _v0 = df64_twoSqr(a.x, _fp64_g);
_v0.y = (_v0.y + ((a.x * a.y) * 2.0));
return df64_quickTwoSum(_v0.x, _v0.y, _fp64_g);
}
float df64_narrow(vec2 a) {
return (a.x + a.y);
}
in vec2 uv;
layout(location = 0) out vec4 _ret;
void main() {
float _fp64_g = texelFetch(_fp64, ivec2(0, 0), 0).x;
float _v0 = u.half_width;
float _v1 = (uv.x * 2.0);
bool _cse0 = (uv.x < 0.5);
float _v2 = (_v1 - (_cse0 ? 0.0 : 1.0));
float _gv0 = (_v2 - 0.5);
float _v3 = (_gv0 * (_v0 * 2.0));
bool _v4 = (_cse0 || (u.fp64 < 0.5));
vec2 _cse1 = vec2(1.0, 0.0);
vec2 _v5 = df64_add(_cse1, vec2(_v3, 0.0), _fp64_g);
vec2 _v6 = df64_sqr(_v5, _fp64_g);
vec2 _v7 = df64_mul(_v6, _v5, _fp64_g);
vec2 _v8 = df64_sqr(_v6, _fp64_g);
vec2 _v9 = df64_mul(_v8, _v5, _fp64_g);
vec2 _v10 = df64_sqr(_v7, _fp64_g);
vec2 _v11 = df64_mul(_v10, _v5, _fp64_g);
float _cse6 = uintBitsToFloat(floatBitsToUint(0.0));
vec2 _cse2 = vec2(_cse6, _cse6);
vec2 _cse3 = vec2(7.0, 0.0);
vec2 _cse4 = vec2(21.0, 0.0);
vec2 _cse5 = vec2(35.0, 0.0);
float _v12 = df64_narrow(df64_sub(df64_add(df64_sub(df64_add(df64_sub(df64_add(df64_sub(df64_add(_v11, _cse2, _fp64_g), df64_mul(_v10, _cse3, _fp64_g), _fp64_g), df64_mul(_v9, _cse4, _fp64_g), _fp64_g), df64_mul(_v8, _cse5, _fp64_g), _fp64_g), df64_mul(_v7, _cse5, _fp64_g), _fp64_g), df64_mul(_v6, _cse4, _fp64_g), _fp64_g), df64_mul(_v5, _cse3, _fp64_g), _fp64_g), df64_add(_cse1, _cse2, _fp64_g), _fp64_g));
float _v13 = (1.0 + _v3);
float _v14 = (_v13 * _v13);
float _v15 = (_v14 * _v13);
float _v16 = (_v14 * _v14);
float _v17 = (_v16 * _v13);
float _v18 = (_v15 * _v15);
float _v19 = (_v18 * _v13);
float _v20 = (((((((_v19 - (_v18 * 7.0)) + (_v17 * 21.0)) - (_v16 * 35.0)) + (_v15 * 35.0)) - (_v14 * 21.0)) + (_v13 * 7.0)) - 1.0);
float _v21 = (_v4 ? _v20 : _v12);
float _v22 = (pow(_v0, 7.0) * 1.3);
float _v23 = (_v21 / _v22);
float _v24 = (_v3 * _v3);
float _v25 = ((((_v24 * _v24) * _v24) * _v3) / _v22);
float _v26 = ((uv.y - 0.5) * 2.0);
float _v27 = (2.0 / u.resolution.y);
float _v28 = fract((_v2 * 10.0));
float _v29 = fract(((_v26 + 1.0) * 5.0));
float _v30 = min(_v28, (1.0 - _v28));
float _v31 = min(_v29, (1.0 - _v29));
float _v32 = (30.0 / u.resolution.x);
float _v33 = (15.0 / u.resolution.y);
float _v34 = ((1.0 - smoothstep(0.0, _v32, _v30)) + (1.0 - smoothstep(0.0, _v33, _v31)));
vec3 _v35 = mix(vec3(0.96, 0.94, 0.88), vec3(0.72, 0.78, 0.86), (min(_v34, 1.0) * 0.45));
float _v36 = step(_v26, _v23);
vec3 _v37 = mix(_v35, vec3(0.62, 0.74, 0.9), (_v36 * 0.5));
float _v38 = (1.0 - smoothstep((_v27 * 1.2), (_v27 * 3.0), abs((_v23 - _v26))));
vec3 _v39 = mix(_v37, vec3(0.13, 0.16, 0.3), (_v38 * 0.85));
float _v40 = (1.0 - smoothstep((_v27 * 0.8), (_v27 * 2.2), abs((_v25 - _v26))));
vec3 _v41 = mix(_v39, vec3(0.8, 0.25, 0.2), (_v40 * 0.65));
float _lc0 = (_v27 * 1.5);
float _v42 = min(smoothstep(0.0, _lc0, abs(_v26)), smoothstep(0.0, _lc0, (abs(_gv0) * 2.0)));
vec3 _v43 = mix(vec3(0.35, 0.33, 0.3), _v41, _v42);
_ret = vec4(_v43, 1.0);
}

fp64 catastrophic cancellation, drawn at build time.

The WGSL and GLSL tabs are the compiler's own output at commit 26de7be8, read from the goldens its emit suite bakes (emit-goldens.test.ts).

This example is written against the fn() builder API, which the editor in the Playground does not take.

File on GitHub

Edit this page Report a problem