AI programming : are you angry yet?AI-assisted programming is fast evolving and there is a tension between โwe no longer need to understand the codeโ and โwhat is my purpose as a programmerโ. I recorded a short video on this topic with how I think the tension can result in conflicts.๐Daniel Lemire's blogWelcome to SwedenCpp
Latest blogs, videos, podcasts and releases in one stream
Wednesday, August 12, 2026
AI programming : are you angry yet?AI-assisted programming is fast evolving and there is a tension between โwe no longer need to understand the codeโ and โwhat is my purpose as a programmerโ. I recorded a short video on this topic with how I think the tension can result in conflicts.๐Daniel Lemire's blog
Real-Time Raytraced Acoustics for Games - Anton Lundberg - ADCx Copenhagen 2026๐ฅaudiodevcon
ยต-Boosting Your CMake Productivity with Environment VariablesSave yourself repetitive typing: CMake can read configuration values straight from environment variables, letting you set your preferred generator, enable compile_commands.json, and more - once in your shell config, applied to every build folder from then on.๐KDABIf this page is useful, please consider donating a coffee
Tuesday, August 11, 2026
Whatโs New in vcpkg (Jul 2026)These updates include major SBOM improvements in vcpkg-tool, 302 updated ports including multiple major library upgrades, and other minor improvements and bug fixes. The post Whatโs New in vcpkg (Jul 2026) appeared first on C++ Team Blog .๐C++ Team Blog
The little-known winstart.bat batch fileIt had been there long before Windows 95, but nobody remembered. The post The little-known winstart.bat batch file appeared first on The Old New Thing .๐The Old New Thing
ITK 5.4.7 ReleasedITK 5.4.7 is a maintenance release focused on image IO capabilities, computational performance, build reliability, and packaging resilience.๐Kitware Inc
After Reflection: The Runtime Story - Saksham Sharma - C++Now 2026๐ฅCppNow
Braden Ganetsky: From Twisty Puzzles to C++๐ฅC++ Alliance
Vienna Traffic Simulator - Denys Gusti๐ฅMeetingCpp
span [C++ Shorts Lesson 34]๐ฅMike ShahMonday, August 10, 2026
How to Choose and Use the Right Container in C++26 p1๐ฅGlobalCpp
Saving Time With Runtime and Having Registry Level Synthesis of Your Software As You Write It๐ฅCppOnline
How can I perform a CopyยญFile in unbuffered mode?Try one of the fancier alternatives to CopyยญFile . The post How can I perform a CopyยญFile in unbuffered mode? appeared first on The Old New Thing .๐The Old New Thing
2026 EuroLLVM - LLVM JIT - Upcoming Challenges and Opportunities๐ฅLLVM
2026 EuroLLVM - What Compiler Implementers and Language Designers Need to Know About Pointer Auth...๐ฅLLVM
2026 EuroLLVM - Tracking Warnings at Scale: Extending Clang Diagnostics to Support Issue...๐ฅLLVM
2026 EuroLLVM - What's new in LLDB on Windows๐ฅLLVM
2026 EuroLLVM - Highlighting function names in LLDB backtraces๐ฅLLVM
From Self Taught to Committee Memberโs First Accepted Paper - Waffl3x CppCast 410 / C++Weekly 545๐ฅJason Turner
August's Overload Journal has been published.The August 2026 ACCU Overload journal has been published and should arrive at members' addresses in the next few days. Overload 194 and previous issues of Overload can be accessed via the Journals menu.๐ACCU
Measuring and Improving UI Performance with the JUCE C++ Framework - Anthony Nicholls - ADC 2025๐ฅaudiodevcon
Can you find an error? Drop your answers in the comments! #coding #csharp #dev #error๐ฅPVS-Studio
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.netSunday, August 9, 2026
Profile-guided optimization in GoWhen a compiler optimizes your program, it has to guess. Which functions are worth inlining? Which side of a branch is the common one? Which method does this interface call actually reach? At compile time it cannot know, so it uses heuristics. Profile-guided optimization (PGO) replaces the guessing with measurement: you run your program, record โฆ Continue reading Profile-guided optimization in Go๐Daniel Lemire's blog
boolalpha in c++๐ฅCppNuts
Scaling beman.exemplar - Eddie Nolan - C++Now 2026๐ฅCppNow
Making a Game engine with Godot features using standard C++ - Raphaรซl Talaรฏa๐ฅMeetingCpp
c++ project structure [C++ Shorts Lesson 33]๐ฅMike ShahSaturday, August 8, 2026
Understanding std::counting_semaphore and std::binary_semaphore from C++20This article explains the two semaphore types introduced in C++20: std::counting_semaphore and std::binary_semaphore . Weโll first use a counting semaphore to limit how many threads can operate at the same time. Then weโll use a binary semaphore to send a signal between threads. Weโll also look at timed waiting, a small RAII helper, and a few more details. Note: The synchronization features discussed here are available in C++20. The examples use C++23 std::println for cleaner output. Letโs go. Basics A mutex works well when only one thread should enter a protected section at a time. But sometimes that limit is too strict. Imagine an application with three database connections. Running only one database operation at a time would waste two of them. On the other hand, allowing any number of threads to start an operation could overload the database. What we need is a limit: three threads may continue, while the others wait. There is another common case. One thread prepares some data, and another thread waits until the data is ready. Semaphores work well for both problems. A lot of multi-threading libraries have semaphores, but itโs pretty cool that the C++20 Standard Library now includes them right out of the box. API A counting semaphore is declared as: std::counting_semaphore The main operations are: Function Description counting_semaphore(desired) Creates a semaphore with the counter set to desired acquire() Decreases the counter, or waits if it is zero release(update) Increases the counter by update ; the default is 1 try_acquire() Tries once without waiting try_acquire_for() Waits for a limited duration try_acquire_until() Waits until a given time point max() Returns the largest counter value supported by the implementation std::binary_semaphore is an alias for: std::counting_semaphore It is useful when one outstanding signal is enough. Letโs start with the counting version. Limiting concurrency with std::counting_semaphore Suppose we have eight jobs, but only three should perform an expensive operation at the same time: #include #include #include #include // #include #include int main () { constexpr int workerCount = 8 ; constexpr int slotCount = 3 ; std :: counting_semaphore slotCount > slots { slotCount }; std :: mutex outputMutex ; int active = 0 ; auto worker = [ & ]( int id ) { slots . acquire (); { std :: lock_guard lock ( outputMutex ); ++ active ; std :: println ( "worker {} entered; active={}" , id , active ); } std :: this_thread :: sleep_for ( std :: chrono :: milliseconds ( 250 ) ); { std :: lock_guard lock ( outputMutex ); -- active ; std :: println ( "worker {} leaving; active={}" , id , active ); } slots . release (); }; std :: vector std :: jthread > threads ; threads . reserve ( workerCount ); for ( int i = 0 ; i workerCount ; ++ i ) threads . emplace_back ( worker , i ); } Run @Compiler Explorer The semaphore starts with three available slots. The first three workers call acquire() and continue. The internal counter then reaches zero. When a fourth worker calls acquire() , it has to wait. Once one of the active workers finishes and calls release() , a slot becomes available again. One waiting worker can then continue. One possible part of the output is: worker 0 entered; active=1 worker 2 entered; active=2 worker 1 entered; active=3 worker 2 leaving; active=2 worker 4 entered; active=3 The order may change between runs, but active should never be greater than three. The mutex in this example does not limit the number of workers. It only protects the diagnostic counter and keeps the output readable. The semaphore is the part that enforces the three-worker limit. Logging can slightly change thread scheduling, but it does not change the rule enforced by the semaphore. The mutex is held only for a short print operation, not for the simulated work. This is also why the code is not a traditional critical section. Three workers are allowed to run the operation together. We are limiting concurrency, not forcing complete mutual exclusion. There is one more detail: the semaphore knows how many slots are available, but it does not know what those slots represent. If they stood for three real database connections, we would still need a separate container holding those connections. Returning a slot with RAII The example above calls release() by hand. That works, but it is easy to make a mistake. An early return or an exception between acquire() and release() could prevent the slot from being returned. Other threads might then wait forever, even though the underlying work has already stopped. This is similar to calling lock() and forgetting to call unlock() . A small RAII guard can help: template std :: ptrdiff_t LeastMaxValue > class SemaphoreGuard { public : explicit SemaphoreGuard ( std :: counting_semaphore LeastMaxValue >& sem ) : sem_ ( sem ) { sem_ . acquire (); } ~ SemaphoreGuard () { sem_ . release (); } SemaphoreGuard ( const SemaphoreGuard & ) = delete ; SemaphoreGuard & operator = ( const SemaphoreGuard & ) = delete ; private : std :: counting_semaphore LeastMaxValue >& sem_ ; }; It can be used at the start of a scope: SemaphoreGuard guard { slots }; The constructor waits for a slot, and the destructor returns it. This works well when the slot represents reusable capacity: a database connection, an upload slot, a buffer, or access to limited hardware. On the other hand, it does not fit every signaling example. A one-way signal is often meant to be consumed rather than returned. Signaling with std::binary_semaphore A mutex has ownership. The thread that locks it must also unlock it. A semaphore does not have this rule. One thread can wait in acquire() , while another thread calls release() . That makes std::binary_semaphore useful for simple communication between threads. In the next example, the worker waits until the main thread tells it to start. It calculates a result and then sends a signal back: #include #include #include int main () { std :: binary_semaphore startSignal { 0 }; std :: binary_semaphore doneSignal { 0 }; int result = 0 ; std :: jthread worker ([ & ] { startSignal . acquire (); result = 42 ; doneSignal . release (); }); std :: println ( "[main] starting worker" ); startSignal . release (); doneSignal . acquire (); std :: println ( "[main] result = {}" , result ); } Run @Compiler Explorer Both semaphore counters start at zero. The worker stops at startSignal.acquire() . The main thread calls startSignal.release() , which sends the start signal and lets the worker continue. After writing result , the worker calls doneSignal.release() . The main thread waits for this signal before reading the result. The semaphore also handles memory synchronization. The main thread sees the writes made by the worker before the release() that allowed doneSignal.acquire() to finish. That means the main thread can safely read result after doneSignal.acquire() returns. The signal is not returned afterward, and that is fine. It represented a one-time notification rather than a reusable slot. Waiting with a timeout acquire() can wait forever. Some code cannot accept that. For example, an application may want to report an error, retry the operation, or switch to a fallback path after waiting for too long. try_acquire_for() waits for a relative duration: #include #include #include #include int main () { std :: counting_semaphore 1 > sem { 0 }; std :: jthread notifier ([ & ] { std :: this_thread :: sleep_for ( std :: chrono :: milliseconds ( 200 ) ); sem . release (); }); if ( sem . try_acquire_for ( std :: chrono :: milliseconds ( 100 ))) { std :: println ( "First wait succeeded" ); } else { std :: println ( "First wait timed out" ); } if ( sem . try_acquire_for ( std :: chrono :: milliseconds ( 300 ))) { std :: println ( "Second wait succeeded" ); } else { std :: println ( "Second wait timed out" ); } } See @Compiler Explorer A typical result is: First wait timed out Second wait succeeded The first call stops waiting before the notifier calls release() . It does not decrease the counter. Later, the notifier increases the counter, and the second call succeeds. A timeout is not an exact scheduling deadline. Waiting for 100 milliseconds means the function will not report a timeout before that duration has passed. The operating system may resume the thread a little later. Semaphore, mutex, or condition variable? These tools solve different problems. Use a mutex when one thread should own a protected section. The thread that locks the mutex must unlock it, and std::lock_guard makes that ownership easy to manage. Use a counting semaphore when up to N operations may run at the same time. Use a binary semaphore when one thread needs to send a simple signal to another thread. A condition variable is a better fit when threads wait for shared state to satisfy a condition. It normally works together with a mutex and a predicate. Tool Common use std::mutex One thread owns a protected section std::counting_semaphore Up to N operations run together std::binary_semaphore One thread signals another std::condition_variable Threads wait for a shared-state condition A semaphore also remembers an unused counter value. If release() runs before another thread starts waiting, a later acquire() can still use that value. With a condition variable, code normally checks shared state rather than relying on a stored notification. Details worth knowing LeastMaxValue is a lower bound In: std :: counting_semaphore 3 > the value 3 means that the implementation must support a counter of at least three. Its real maximum may be larger. max() returns the value supported by the implementation. Application code should normally use the number it actually needs and not depend on any extra range offered by one standard library. Do not increase the counter past max() release(update) requires that the new counter value does not exceed max() . This matters with std::binary_semaphore . It is not an event flag that can be set many times without being cleared. Calling release() twice without an acquire() between those calls may break the functionโs precondition. try_acquire() may fail spuriously try_acquire() performs one non-blocking attempt. It is allowed to return false even when the counter is greater than zero. Use acquire() when the thread must wait. Use try_acquire_for() or try_acquire_until() when it should wait only for a limited time. Waiting order is not guaranteed If several threads are waiting, the standard does not say that the oldest waiter must run first. Code that needs strict first-in, first-out behavior requires an extra queue or scheduling layer. Watch the lifetime Do not destroy a semaphore while another thread may still be using it. All calls to acquire() , release() , and the timed functions must finish before the semaphoreโs lifetime ends. Summary std::counting_semaphore is useful when several threads may run an operation together, but their number must stay below a fixed limit. std::binary_semaphore works well for simple signals between threads. Use a mutex for normal exclusive access and a condition variable when threads wait for a shared-state condition. The main thing is to be clear about what the counter means. It may represent free worker slots, connections, buffers, or a signal waiting to be received. References std::counting_semaphore and std::binary_semaphore - cppreference C++ working draft: semaphores Implementing C++20 semaphores - Red Hat Developer C++ Concurrency in Action, Second Edition by Anthony Williams Concurrency with Modern C++ by Rainer Grimm Back to you Do you use semaphores mainly for limiting concurrent work or for signaling? Have you tried C++20 or some third-party libraries?๐C++ StoriesFriday, August 7, 2026
A Collection of Structured PromptsHow-tos, rulebooks, lessons, and prompts.๐My Very Best AI Slop
Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 5Cheating with std::unique_ptr . The post Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 5 appeared first on The Old New Thing .๐The Old New Thing
Link What You Include - Maintain a Coherent CMake Target Model - Frank Miller - C++Now 2026๐ฅCppNow
Commercialising Audio Plugins - Going From Development to Sales and Beyond - Tobias Lรธnnerรธd Madsen๐ฅaudiodevcon
Neural Rendering for Subsurface Scattering - Cyrill Miksch๐ฅMeetingCpp
header guards [C++ Shorts Lesson 32]๐ฅMike ShahThursday, August 6, 2026
Kitware Awarded up to $17.5 Million ARPA-H Contract to Develop Virtual Testing Platform for Robot-Assisted Stroke Interventions3.5-year TAIR project will create a realistic, patient-specific virtual environment to accelerate the development and evaluation of robotic stroke devices. Clifton Park, NY โ August 6, 2026 โ Kitware has been awarded up to $17.5 million by the Advanced Research Projects Agency for Health (ARPA-H), an agency within the U.S. Department of Health and Human [โฆ]๐Kitware Inc
Lightning Talk: RPC With Coroutines, RAII & Callable Weakpointers - Edward Boggis-Rolfe๐ฅCppOnline
Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 4Moving things around if possible. The post Creating a fake agile wrapper that is technically agile but is not useful outside its home apartment, part 4 appeared first on The Old New Thing .๐The Old New Thing
Why is shared ptr 16 Bytes?! ๐คฏ | C++๐ฅCppNuts
Measurement uncertainty and measured constantsMeasurement uncertainty and measured constants When you write constexpr double G = 6.674e-11; in your code, the type system makes a claim that is not true. It claims the value is exact. It is not. The Newtonian constant of gravitation is one of the least precisely known constants in physics. CODATA 2018 lists it as 6.674 30(15) ร 10โปยนยน mยณ kgโปยน sโปยฒ , and the (15) means the fifth significant digit is already uncertain. Only four digits of G are actually known. Every result derived from that double inherits an uncertainty that the program neither tracks nor reports. The code happily prints ten significant digits of a solar mass computed from a constant that guarantees four. mp-units now models this honestly. This post introduces the uncertain representation type, the standard_uncertainty and relative_standard_uncertainty metadata for measured constants, and the measurement_of helper that connects them.๐mp-units