<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Apps on Blog do Daquisu</title>
    <link>https://daquisu.com/apps/</link>
    <description>Experimentos interativos que rodam direto no navegador, sem instalar nada.</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>pt</language>
    <lastBuildDate>Fri, 17 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://daquisu.com/apps/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>Mandelbrot infinito</title>
      <link>https://daquisu.com/apps/mandelbrot/</link>
      <pubDate>Fri, 17 Jul 2026 00:00:00 +0000</pubDate>
      <author>Akira Daquisu</author>
      <guid>https://daquisu.com/apps/mandelbrot/</guid>
      <description>Explorador do conjunto de Mandelbrot com zoom sem limite: a GPU faz o trabalho pesado via WebGL e a precisão arbitrária vem da teoria de perturbação.</description><content:encoded><![CDATA[<h2 id="o-que-é-isso" class="heading-anchor">O que é isso?<a href="#o-que-%c3%a9-isso" class="heading-anchor__link" aria-label="Link para esta seção" title="Copiar link">
    <svg class="heading-anchor__icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
      <path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-.5 9.45a.75.75 0 01-1.06-1.06l-1.25 1.25a2 2 0 01-2.83-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25z"/>
    </svg>
  </a>
</h2>
<p>O conjunto de Mandelbrot é o exemplo mais famoso de fractal: um objeto com detalhe
infinito. Cada ponto <code>c</code> do plano complexo entra ou não no conjunto conforme a
iteração <code>z → z² + c</code> (começando com <code>z = 0</code>) fica limitada ou escapa para o
infinito. As cores mostram quantas iterações cada pixel levou para escapar.</p>
<p>A graça é dar zoom: não importa o quanto você aproxime, sempre há estrutura nova.
Este app foi feito para aguentar esse &ldquo;sempre&rdquo; — o zoom não para em 100.000×,
vai até onde sua paciência aguentar (o limite configurado é 10^1200).</p>
<h2 id="como-funciona" class="heading-anchor">Como funciona<a href="#como-funciona" class="heading-anchor__link" aria-label="Link para esta seção" title="Copiar link">
    <svg class="heading-anchor__icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
      <path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-.5 9.45a.75.75 0 01-1.06-1.06l-1.25 1.25a2 2 0 01-2.83-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25z"/>
    </svg>
  </a>
</h2>
<h3 id="a-gpu-faz-o-trabalho-pesado-webgl" class="heading-anchor">A GPU faz o trabalho pesado (WebGL)<a href="#a-gpu-faz-o-trabalho-pesado-webgl" class="heading-anchor__link" aria-label="Link para esta seção" title="Copiar link">
    <svg class="heading-anchor__icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
      <path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-.5 9.45a.75.75 0 01-1.06-1.06l-1.25 1.25a2 2 0 01-2.83-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25z"/>
    </svg>
  </a>
</h3>
<p>Cada pixel é independente dos demais: é o cenário perfeito para a GPU. A conta
<code>z → z² + c</code> roda num <em>fragment shader</em> WebGL2, milhões de pixels em paralelo.
Para não travar o navegador em cenas pesadas, as iterações rodam em blocos: o
estado de cada pixel (<code>z</code>, contagem, status) fica guardado em texturas float
que fazem ping-pong entre passadas, e a imagem vai se completando
progressivamente na tela.</p>
<h3 id="o-problema-float32-acaba-rápido" class="heading-anchor">O problema: float32 acaba rápido<a href="#o-problema-float32-acaba-r%c3%a1pido" class="heading-anchor__link" aria-label="Link para esta seção" title="Copiar link">
    <svg class="heading-anchor__icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
      <path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-.5 9.45a.75.75 0 01-1.06-1.06l-1.25 1.25a2 2 0 01-2.83-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25z"/>
    </svg>
  </a>
</h3>
<p>A GPU só calcula nativamente com float32, que tem ~7 dígitos de precisão. Por
volta de zoom 10⁵, pixels vizinhos passam a ter o mesmo valor de <code>c</code> e a imagem
vira blocos. Precisão dupla no shader não existe em WebGL, e emular precisão
arbitrária por pixel na GPU seria lento demais.</p>
<h3 id="a-saída-teoria-de-perturbação" class="heading-anchor">A saída: teoria de perturbação<a href="#a-sa%c3%adda-teoria-de-perturba%c3%a7%c3%a3o" class="heading-anchor__link" aria-label="Link para esta seção" title="Copiar link">
    <svg class="heading-anchor__icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
      <path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-.5 9.45a.75.75 0 01-1.06-1.06l-1.25 1.25a2 2 0 01-2.83-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25z"/>
    </svg>
  </a>
</h3>
<p>O truque (K. I. Martin, 2013) é separar o problema em duas partes:</p>
<ol>
<li><strong>Uma órbita de referência</strong> <code>Z₀, Z₁, Z₂, …</code> é calculada com precisão
arbitrária na CPU — aqui, aritmética de ponto fixo com <code>BigInt</code>, num web
worker para não travar a página. São os únicos números &ldquo;caros&rdquo; de todo o
processo.</li>
<li><strong>Cada pixel só acompanha a diferença</strong> <code>δ = z − Z</code> para a referência. A
álgebra fecha bonito: <code>δ′ = 2·Z·δ + δ² + δc</code>. Essa recorrência só usa
números pequenos e roda inteira em float na GPU.</li>
</ol>
<p>Ou seja: a precisão arbitrária entra uma vez por imagem (na referência), e a
GPU segue trabalhando com números baratos. É o mesmo esquema dos renderizadores
sérios de deep zoom, como Kalles Fraktaler e fraktaler-3.</p>
<p>Quando a órbita do pixel se aproxima demais de zero (perto de um minibrot) ou
sobrevive além do fim da referência, o pixel sofre <em>rebase</em> (técnica do usuário
Zhuoran, 2021): o δ vira o valor completo e volta a acompanhar a referência do
início. Isso elimina os &ldquo;glitches&rdquo; clássicos da perturbação sem precisar de
múltiplas referências.</p>
<h3 id="e-quando-nem-o-expoente-do-float32-aguenta" class="heading-anchor">E quando nem o expoente do float32 aguenta?<a href="#e-quando-nem-o-expoente-do-float32-aguenta" class="heading-anchor__link" aria-label="Link para esta seção" title="Copiar link">
    <svg class="heading-anchor__icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
      <path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-.5 9.45a.75.75 0 01-1.06-1.06l-1.25 1.25a2 2 0 01-2.83-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25z"/>
    </svg>
  </a>
</h3>
<p>Em zoom além de ~10²⁷, o δ de um pixel é menor que o menor float32 positivo
(~10⁻³⁸) — o número vira zero antes de perder precisão. A solução é o formato
<strong>floatexp</strong>: mantissa float32 + expoente inteiro separado, com soma e
multiplicação implementadas à mão no shader. É mais lento, então o app escolhe
o modo conforme a profundidade:</p>
<table>
  <thead>
      <tr>
          <th>zoom</th>
          <th>modo</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>até ~10³</td>
          <td>float32 direto</td>
      </tr>
      <tr>
          <td>até ~10²⁶</td>
          <td>perturbação com δ em float32</td>
      </tr>
      <tr>
          <td>além</td>
          <td>perturbação com δ em floatexp</td>
      </tr>
  </tbody>
</table>
<h3 id="detalhes-de-engenharia" class="heading-anchor">Detalhes de engenharia<a href="#detalhes-de-engenharia" class="heading-anchor__link" aria-label="Link para esta seção" title="Copiar link">
    <svg class="heading-anchor__icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
      <path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-.5 9.45a.75.75 0 01-1.06-1.06l-1.25 1.25a2 2 0 01-2.83-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25z"/>
    </svg>
  </a>
</h3>
<ul>
<li>O centro da imagem vive em <code>BigInt</code> de ponto fixo (a precisão cresce com o
zoom), então arrastar a 10^500 continua exato.</li>
<li>Enquanto você arrasta ou dá zoom, a última imagem pronta é reprojetada
(transformada afim) como preview; o render de verdade recomeça quando o
gesto assenta.</li>
<li>A referência é reaproveitada entre movimentos próximos: dar zoom perto do
mesmo ponto não recomputa a órbita.</li>
<li>A órbita de referência também é guardada em floatexp na textura — perto de
minibrots ela passa tão perto de zero que float32 puro descartaria
exatamente a informação de que o rebase precisa.</li>
<li>O link do botão &ldquo;compartilhar&rdquo; carrega as coordenadas completas em
hexadecimal, com quantos dígitos forem necessários.</li>
</ul>
<h3 id="limites-honestos" class="heading-anchor">Limites honestos<a href="#limites-honestos" class="heading-anchor__link" aria-label="Link para esta seção" title="Copiar link">
    <svg class="heading-anchor__icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
      <path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-.5 9.45a.75.75 0 01-1.06-1.06l-1.25 1.25a2 2 0 01-2.83-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25z"/>
    </svg>
  </a>
</h3>
<p>Perto da fronteira do conjunto a dinâmica é caótica: com milhares de
iterações, a contagem exata de escape de um pixel depende da precisão usada —
em qualquer renderizador em ponto flutuante. A estrutura da imagem é a mesma;
faixas de cor individuais podem diferir de renders feitos com outra
aritmética. Em profundidade extrema com muitas iterações, o render leva alguns
segundos (a imagem vai aparecendo aos poucos). Aproximação por séries (BLA) e
antialiasing ficam para uma próxima versão.</p>
<h2 id="referências" class="heading-anchor">Referências<a href="#refer%c3%aancias" class="heading-anchor__link" aria-label="Link para esta seção" title="Copiar link">
    <svg class="heading-anchor__icon" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
      <path d="M7.775 3.275a.75.75 0 001.06 1.06l1.25-1.25a2 2 0 112.83 2.83l-2.5 2.5a2 2 0 01-2.83 0 .75.75 0 00-1.06 1.06 3.5 3.5 0 004.95 0l2.5-2.5a3.5 3.5 0 00-4.95-4.95l-1.25 1.25zm-.5 9.45a.75.75 0 01-1.06-1.06l-1.25 1.25a2 2 0 01-2.83-2.83l2.5-2.5a2 2 0 012.83 0 .75.75 0 001.06-1.06 3.5 3.5 0 00-4.95 0l-2.5 2.5a3.5 3.5 0 004.95 4.95l1.25-1.25z"/>
    </svg>
  </a>
</h2>
<ul>
<li>K. I. Martin, <em>SuperFractalThing</em> (2013) — a formulação original da perturbação.</li>
<li>Zhuoran, <em>Another solution to perturbation glitches</em> (fractalforums, 2021) — o rebasing.</li>
<li>Claude Heiland-Allen, <em>fraktaler-3</em> e o blog mathr.co.uk — floatexp e muito do estado da arte.</li>
<li>Inigo Quilez — as paletas por cosseno.</li>
</ul>
]]></content:encoded><enclosure url="https://daquisu.com/images/apps/mandelbrot.png" type="image/png" /></item>
  </channel>
</rss>
