Friday, August 14, 2026

If this page is useful, please consider donating a coffee

Thursday, August 13, 2026

Clearer Build Optimization Results with GitHub CopilotWe know that slow builds are a consistent theme in feedback from C++ developers. Your feedback helped us to shape GitHub Copilot build performance for Windows, an agent that can trace your build, find bottlenecks, apply optimizations, and measure the result. When using the agent, the clearest outcome is a straightforward win. The agent changes […] The post Clearer Build Optimization Results with GitHub Copilot appeared first on C++ Team Blog .C++ Team Blog
Introducing Photometric ConditionsIntroducing Photometric Conditions A "1000 lm" LED lamp and a "1000 lm" low-pressure sodium lamp do not look equally bright at night. The number on both boxes is a photopic value: it weights the lamp's spectrum with $V(\lambda)$, the spectral sensitivity of the eye's cone cells, which drive vision in daylight. At night, rod cells take over, the sensitivity curve shifts towards blue ($V'(\lambda)$), and the same two lamps deliver roughly 2000 and 250 scotopic lumens respectively. Both figures are luminous fluxes. Both are expressed in lumens. And a program that adds or compares them computes a result that is wrong by up to a factor of eight: cpp auto led = 1000. * lm; // photopic value from the datasheet auto sodium = 250. * lm; // scotopic value from a night-visibility model auto total = led + sodium; // compiles everywhere, means nothing To the best of our knowledge, no units library catches this today, because every one of them (including mp-units until now) models a lumen as a lumen. This post introduces photometric conditions : a way to keep photopic, scotopic, mesopic, and custom quantities in separate quantity hierarchies, so that the mistake above breaks at compile time, while everything the physics does allow keeps working.mp-units

Wednesday, August 12, 2026

Digitizing super 8 film yourselfIn our previous post we looked at fixing a super 8 film projector. While watching film with a real projector has its own charm, it is inconvenient to say the least. First of all you make the entire room properly dark or you can't see anything. This is regardless of the fact that the projector bulb is consuming 100 watts of power to show the image. Even if you manage not to burn the film, merely running it through the projector causes wear, scratches and tearing. While film typically ages very well, eventually it will turn into magenta goop or gets eaten by vinegar syndrome . Thus you really want to convert all these films into high quality digital files. There are several companies that offer this service. If you only have a few reels, using those is the smart thing to do. I, on the other hand, have so much material that using a commercial service would cost thousands (possibly tens of thousands) of euros. Fortunately, this is a fairly common problem and there are dozens of existing projects on the Internet to be inspired by. The main technical problem with super 8 film is that it is very small. A sequence of 10 super 8 images is approximately as long as a matchstick. The fact that projectors can display 18 frames per second with sub millimeter registration is an astounding achievement of mechanical engineering. How do they do that? Very difficultly. Many of the DIY solutions start by taking an existing projector and modifying it to run slower. Then you remove the projection lens and aim a digital camera with a macro lens at the gate. This yields incredible results quality-wise but requires a fairly expensive macro lens and typically the modification on the projector is destructive. So that's out. Some more searching eventually lead me to this Github project . The basic idea is simple. Instead of using a projector or trying to replicate a film transport (which proper tension and all that) instead rely on the basic stiffness on film and drive it directly with stepper motor. Film is not aligned mechanically but instead by detecting the sprocket hole with some straightforward machine vision code. Time to fire up the ol' 3D printer and order components. This is what the end result looks like after assembly The thing at the top left that looks like a space cannon prop from a scifi movie is actually a microscope lens. Not only can it do > 1x optical magnification, it does it at a cost of about 25 euros. The downside is noticeable chromatic aberration. The small flat thing on the other end is the Raspberry Pi HQ camera module that can do 4k at 12 bits per channel. The whole thing is run via a single Raspberry Pi 3 with a stepper motor hat. The board at the bottom is used to distribute 12V DC power to the lamp and motors. Before going further, let's just spend some time appreciating just how awesome colors look in this film. Props to the chemical engineers at Kodak. And remember, the original image is about one third of the size of your smallest fingernail. The Github repo says that you probably need to adapt the code to your setup. I basically ended up reimplementing all of it from scratch. In the process I learned that OpenCV has its own GUI toolkit which is both simple (one could even say simplistic) and perfect for this use case. The first attempt took nine hours to process one 3.5 minute reel of film. Then I realized that trying to do 4k on material that physically maxes out at approximately 2k with the processing power of a potato is not a recipe for success. Halving the capture resolution and a few other optimizations brought the runtime down to about one hour per reel. With this, some more custom software for image processing and stabilization coupled with FFmpeg scripts one can start to go through the archive of films. Doing so raises a fair bit of questions. For example: Is that a 3 year old child driving a jury-rigged go-kart on a frozen lake on his own without even wearing a helmet? Yes it is. A bit later an adult drives the car but he is too heavy so the ice cracks under him. No one seems particularly concerned. This may seem strange to us but you have to understand that this was the very early 70s. The concept of safety had not been invented yet.Nibble Stew

Tuesday, August 11, 2026

Monday, August 10, 2026

The fastest double-to-string algorithm you’ve never heard ofvitaut.net https://vitaut.net/posts/2026/yy-dtoa/ - Żmij , the binary-to-decimal conversion library I wrote about a few posts back , started as an optimized port of Schubfach. Later I switched its core to a different algorithm, defined in yy_double.c from yyjson by ibireme . It has no paper, no name beyond the file it lives in (I'll refer to it as yy), and almost no public profile outside the JSON performance crowd. It also happens to be one of the fastest dtoa implementations. This post is a tour of yy through a small visualization, with a close look at one boundary case. Where yy fits in yy is in the Schubfach family. The shared idea, which I covered in an earlier post , is to find the shortest decimal $\sigma \cdot 10^{e_{10}}$ that round-trips back to a binary float $v$ by intersecting $v$'s rounding interval with decimal grids of various spacings, and picking the coarsest grid that still has a tick in the interval. yy's trick is doing this very cheaply. The whole algorithm runs on fixed-width integer arithmetic and uses only one multiplication by a precomputed power of 10, where classic Schubfach needs two or three. Four candidates For each binary float $v = c \cdot 2^{e_2}$, yy picks a decimal exponent $e_{10}$ via a fixed-point approximation of $\log_{10} 2$, then re-expresses $v$ at the decimal scale as $$ \bar v \approx v \cdot 10^{-e_{10}} $$ using a precomputed power-of-10 table $p_{10}$, a fixed-point value with $p_{10} \cdot 2^{e_p} \approx 10^{-e_{10}}$ for some binary exponent $e_p$. $\bar v$ then sits between four candidate decimal values: $d_1 = \lfloor \bar v \rfloor$ and $u_1 = d_1 + 1$, the integers immediately below and above $\bar v$. $d_0 = 10 \cdot \lfloor \bar v / 10 \rfloor$ and $u_0 = d_0 + 10$, the multiples of 10 below and above. Outputting $d_0$ or $u_0$ gives a decimal one digit shorter than $d_1$ or $u_1$, because the trailing zero folds into the exponent. Like classic Schubfach, yy prefers $d_0$ or $u_0$ when they round-trip and falls back to $d_1$ or $u_1$ otherwise. Three predicates do the work, evaluated against $\bar v$ and a half-ulp band $\delta$ around it. The first checks whether $\bar v - \delta$ reaches $d_0$: $$ \delta \ge \bar v_{10} + \varepsilon_c $$ with $\bar v_{10} = \bar v \bmod 10$. The second checks whether $\bar v + \delta$ reaches $u_0$: $$ \bar v_{10} + \delta \ge 10 + \eta_c $$ The third decides between the longer candidates $d_1$ and $u_1$ by checking whether $\bar v$ is past the midpoint: $$ \bar v \bmod 1 \ge \tfrac12 + \varepsilon_u $$ The biases $\varepsilon_c$, $\eta_c = 2\varepsilon_c - 1$, $\varepsilon_u$ are small parity adjustments ($0$ or $\pm 1$) that implement round-half-to-even at exact ties. The first predicate that fires picks the candidate; if none does, the answer is $d_1$. The $\varepsilon$ and $\eta$ terms are my bookkeeping, not yy's: they let me write the three predicates as simple, uniform formulas. The code doesn't adjust the thresholds at all. It runs the plain comparison and, only when it lands exactly on a tie, branches off and rounds to even by testing a low bit of the significand. This is where the one-multiplication claim from earlier comes in: $\delta$ doesn't need its own multiplication. The half-ulp of $v$ is $\tfrac12 \cdot \mathrm{ulp}(v) = 2^{e_2 - 1}$, which in $\bar v$'s scale gives $$ \delta = 2^{e_2 - 1} \cdot p_{10} \cdot 2^{e_p} = p_{10} \cdot 2^{e_2 + e_p - 1} $$ so $\delta$ is just $p_{10}$ shifted by an integer (no rounding, no second multiplication). The bounds of the rounding interval are then $\bar v \pm \delta$ via add and subtract. Schubfach instead multiplies $v$, $v_l$, and $v_r$ by $p_{10}$ separately, which is two extra 192-bit multiplications. The actual algorithm is a bit more involved than this, with extra paths for irregular intervals, subnormals, and the digit-emission loop. The sketch above is the core idea the rest hangs off of, and all you need to follow the visualization. A step-by-step at E4M3 scale E4M3 is an 8-bit floating-point format (1 sign bit, 4 exponent bits, 3 significand bits, bias 7) used for low-precision AI inference on recent GPUs. With only 256 encodings it fits on one page, which makes it a good target for visualizing things you'd otherwise have to take on faith at f64 scale. I went into more detail on the format in the previous post . The walk-through is one HTML page, e4m3-yy.html ; open it in a new tab. The page walks a value through yy's pipeline top to bottom. The main grid at the top plots every E4M3 value, with the rounding interval of the selected value highlighted: The middle panel is yy itself, step by step: $e_{10}$, the $p_{10}$ table with the active row highlighted, the scaling chain $\bar v = c \cdot 2^{e_2} \cdot p_{10} \cdot 2^{e_p}$, and the four candidates derived from $\bar v$: The bottom of that panel is the predicate table. Each row shows ✓ when the predicate fires, ✗ when it evaluates to false, and is greyed out when an earlier row already fired, alongside the actual comparison at 8-bit working-word precision. Below it, a small diagram puts the four candidates on a number line with $\bar v$ in the middle and a band of width $\pm \delta$: Hover any underlined hex literal for the exact infinite-precision tail; hover a ? over a comparison for a note on why that cell is on a tipping point. A boundary case that looks like a bug Set the encoding to 116 in the explorer, or work out $v = 12 \cdot 2^{4} = 192$ by hand. The bits are 0 1110 100 , so $c = 12$, $e_2 = 4$, and yy picks $e_{10} = \lfloor 4 \log_{10} 2 \rfloor = 1$, so $\bar v = 192 \cdot 10^{-1} = 19.2$ and the fine-grid fallback is $d_1 = \lfloor \bar v \rfloor = 19$, printed as 19e1 . The shorter grid is multiples of $10^{2} = 100$, with $d_0 = 100$ and $u_0 = 200$. If $v$'s rounding interval reaches $u_0 = 200$, yy can emit the shorter 2e2 instead of the longer 19e1 . The decision comes down to the second predicate, $\bar v_{10} + \delta \ge 10$. yy evaluates it in a Q4.4 fixed-point working word (4 integer bits, 4 fractional bits, packed in 8 bits), and the left-hand side comes out to 0x9.F ($= 9.9375$), one LSB short of $10$. The predicate is false, so yy emits 19e1 , a digit longer than it needs to be. That looks wrong, and the reason it isn't comes down to one term. The comparison yy actually runs is $$ \bar v_{10} + \delta \ge 10 + \eta_c $$ with $\eta_c = -1$ LSB here. Lowering the threshold by a unit in the last place looks like an off-by-one, but it is correcting for one. In exact arithmetic the interval reaches $u_0$ exactly: $$ \bar v + \delta = 19.2 + \tfrac12 \cdot 2^{4} \cdot 10^{-1} = 19.2 + 0.8 = 20.0, $$ so $u_0 = 200 = 10 \cdot 10^{1}$ sits at the edge of the interval. yy has no exact arithmetic. Its $p_{10}$ table is stored wider than the Q4.4 working word, 16 bits at this scale, and the $10^{-1}$ row floors to 0xCCCC , dropping a 0.8 LSB tail, the largest truncation any row carries. Multiplying by that rounded-down $p_{10}$ and packing the product back into Q4.4 is what turns a true 10.0 into 0x9.F , and $\eta_c$ subtracts the same LSB from the threshold to match: $$ \bar v_{10} + \delta \ge 10 + \eta_c \iff \mathtt{0x9.F} \ge \mathtt{0xA.0 - 0x0.1} $$ Both sides are 0x9.F . The predicate ties, fires, and yy emits 2e2 . The visualization flags this cell with a ? because it's bias-sensitive: flip $\eta_c$ from $-1$ back to $0$ and the verdict flips, and yy emits 19e1 . Both decimals round-trip: 200 parses to the halfway point between $192$ and $208$, and round-half-to-even picks $192$ because $c = 12$ is even. Try it The explorer is a single HTML file with no build step ( source ). Click through the encodings to see where $p_{10}$ truncation and round-half-to-even actually change yy's output. Algorithms that live in JSON libraries don't get the citation count of the ones that ship with papers. yy is worth knowing about anyway. Fun fact The smallest normal double , $2^{-1022}$, is regular: its predecessor sits exactly one ULP below. Schubfach-family algorithms (yy, Dragonbox, Żmij) flag the "irregular" case by checking whether the significand has all fraction bits zero, which is exactly the powers of two, this one included. Harmless, but as far as I know nobody special-cases it. - https://vitaut.net/posts/2026/yy-dtoa/ -vitaut.net
From Prototype to Production: The EngFlow Build Analytics JourneyFrom Prototype to Production: The EngFlow Build Analytics Journey Every build tells a story. Which targets took the longest? Where did cache misses cost you minutes? Who triggered the invocation, and what source changes drove it? At EngFlow, we believe that surfacing these answers — automatically and at scale — transforms build optimization from reactive firefighting into proactive engineering. Today, we're sharing how our build analytics platform evolved from early prototypes to a production-grade system providing tangible value to our customers.EngFlow Blog

Sunday, August 9, 2026