<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Agoes Jayanata</title>
    <description>The latest articles on DEV Community by Agoes Jayanata (@agoesjayanata).</description>
    <link>https://dev.to/agoesjayanata</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4176208%2F04d670a7-13c8-4ee8-8a56-0adf53549d71.png</url>
      <title>DEV Community: Agoes Jayanata</title>
      <link>https://dev.to/agoesjayanata</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/agoesjayanata"/>
    <language>en</language>
    <item>
      <title>Lottery Odds in Plain Numbers: Why 1 in 292 Million Feels Impossible</title>
      <dc:creator>Agoes Jayanata</dc:creator>
      <pubDate>Wed, 28 Oct 2026 05:00:38 +0000</pubDate>
      <link>https://dev.to/agoesjayanata/lottery-odds-in-plain-numbers-why-1-in-292-million-feels-impossible-57df</link>
      <guid>https://dev.to/agoesjayanata/lottery-odds-in-plain-numbers-why-1-in-292-million-feels-impossible-57df</guid>
      <description>&lt;p&gt;Powerball's jackpot odds are 1 in 292,201,338. Mega Millions is about 1 in 302,575,350. Those figures come from counting combinations: 69 white balls choose 5, times 26 red balls.&lt;/p&gt;

&lt;p&gt;To picture it, imagine 292 million people lined up and one holding the winning ticket. Buying 10 tickets a week for 50 years gives you 26,000 tickets, a chance of roughly 1 in 11,000 of winning once.&lt;/p&gt;

&lt;p&gt;The odds of winning any prize are far kinder, about 1 in 24.9 for Powerball. Explore the breakdown with the &lt;a href="https://jackpotcalculator.com/lottery-odds/" rel="noopener noreferrer"&gt;lottery odds calculator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Record prizes happen because rollovers pile up when nobody hits. The &lt;a href="https://jackpotcalculator.com/largest-jackpots/" rel="noopener noreferrer"&gt;largest lottery jackpots list&lt;/a&gt; shows that nearly every billion-dollar prize followed a long drought of 25 or more drawings.&lt;/p&gt;

&lt;p&gt;If the improbable happens, read the &lt;a href="https://jackpotcalculator.com/what-to-do-if-you-win-the-lottery/" rel="noopener noreferrer"&gt;what to do if you win the lottery guide&lt;/a&gt; first: sign the ticket, keep it safe, and talk to a tax adviser before claiming.&lt;/p&gt;

&lt;p&gt;Takeaway: a ticket is entertainment at $2, not an investment.&lt;/p&gt;

</description>
      <category>analytics</category>
      <category>data</category>
      <category>science</category>
    </item>
    <item>
      <title>How to Check a California County Park Before You Go</title>
      <dc:creator>Agoes Jayanata</dc:creator>
      <pubDate>Tue, 27 Oct 2026 07:02:10 +0000</pubDate>
      <link>https://dev.to/agoesjayanata/how-to-check-a-california-county-park-before-you-go-4kcc</link>
      <guid>https://dev.to/agoesjayanata/how-to-check-a-california-county-park-before-you-go-4kcc</guid>
      <description>&lt;p&gt;California county parks are inexpensive and varied, but the details change park to park: parking fees, dog rules, hours, and temporary closures. A two-minute check saves a lot of frustration.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Start with the county
&lt;/h2&gt;

&lt;p&gt;Rules differ by county, because each county parks agency sets its own. A browsable directory such as &lt;a href="https://parksbycounty.com/" rel="noopener noreferrer"&gt;Parks by County, a California county parks guide&lt;/a&gt; organizes parks by county and city so you can see your options side by side. Seven counties are covered: Santa Clara, Alameda, San Mateo, Contra Costa, Orange, San Diego, and Los Angeles.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Check the four things that vary most
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Parking or entry fee.&lt;/strong&gt; Many county parks charge per vehicle, and some are free. A figure like $6 per vehicle is common at some Bay Area parks, but not universal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dog rules.&lt;/strong&gt; Options range from not allowed, to leashed on selected trails, to designated off-leash areas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hours.&lt;/strong&gt; Typically 8 a.m. to sunset, but some parks close seasonally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Closures and notices.&lt;/strong&gt; Playgrounds, trails, and boat ramps close for construction, rain, or inspections.
## 3. Use the statewide overview&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are choosing between regions, the &lt;a href="https://parksbycounty.com/california/" rel="noopener noreferrer"&gt;California county parks overview&lt;/a&gt; is a good starting page. From there, you can drill into a county and then a single park.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Always confirm with the agency
&lt;/h2&gt;

&lt;p&gt;An independent guide can only summarize. The managing agency has the final word, especially on fees and temporary closures. Look for a link to the official park page and read any current notice at the top.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Pack for the rules
&lt;/h2&gt;

&lt;p&gt;If dogs are allowed on a leash, bring a leash that is short enough and water for the dog. If a park charges per vehicle, carpool to save. If trails open at sunrise but the gate opens later, plan around the gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple routine
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pick the county.&lt;/li&gt;
&lt;li&gt;Open the park page and read fee, dog, and hours lines.&lt;/li&gt;
&lt;li&gt;Read any closure notice.&lt;/li&gt;
&lt;li&gt;Click through to the official page to confirm.&lt;/li&gt;
&lt;li&gt;Go.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is all it takes to avoid most park-day surprises.&lt;/p&gt;

</description>
      <category>travel</category>
    </item>
    <item>
      <title>Estimating Resin for Molds and Coasters in JavaScript: Volume, Density and Waste</title>
      <dc:creator>Agoes Jayanata</dc:creator>
      <pubDate>Sun, 25 Oct 2026 05:07:09 +0000</pubDate>
      <link>https://dev.to/agoesjayanata/estimating-resin-for-molds-and-coasters-in-javascript-volume-density-and-waste-75b</link>
      <guid>https://dev.to/agoesjayanata/estimating-resin-for-molds-and-coasters-in-javascript-volume-density-and-waste-75b</guid>
      <description>&lt;p&gt;Most volume calculators assume a box. Resin molds are rarely boxes: coasters have rounded corners, dice molds are cubes with pips, and silicone trays taper. While building a small resin estimator I ended up with three tiny functions that cover almost every craft mold. Here they are, with the reasoning behind each.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Regular shapes: compute volume, then convert to mass
&lt;/h2&gt;

&lt;p&gt;Resin is sold by volume (ml, fl oz) but often mixed by weight. Keep everything in cubic centimetres internally and convert at the edges.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shapes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;cylinder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// round coaster&lt;/span&gt;
  &lt;span class="na"&gt;box&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                      &lt;span class="c1"&gt;// square coaster, bar mold&lt;/span&gt;
  &lt;span class="na"&gt;roundedSquare&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;                       &lt;span class="c1"&gt;// square with corner radius r&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resinNeeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wastePct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cm3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shapes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;dims&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;               &lt;span class="c1"&gt;// 1 cm3 = 1 ml&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cm3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;wastePct&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// six 10 cm round coasters, 0.6 cm deep&lt;/span&gt;
&lt;span class="nf"&gt;resinNeeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cylinder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// ≈ 311 ml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Six 10 cm coasters at 6 mm need about 283 ml of resin before waste and roughly 311 ml after a 10% allowance for the cup and stir stick. That 10% is not decorative: resin clings to every surface it touches, and a mixing cup alone can hold back 5–10 ml.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Irregular molds: measure with water
&lt;/h2&gt;

&lt;p&gt;For anything with pips, carvings or organic edges, geometry is a trap. The reliable method is water displacement, and it maps neatly to code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// weigh the empty mold, fill with water, weigh again&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;volumeFromWater&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gramsEmpty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gramsFull&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;gramsFull&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;gramsEmpty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// water ≈ 1 g/ml&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dry the mold completely before pouring, since even a drop of water can cloud epoxy.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Volume to weight and to A/B parts
&lt;/h2&gt;

&lt;p&gt;Epoxy densities usually fall between 1.08 and 1.15 g/ml. If your kit mixes 1:1 by volume but you measure on a scale, convert first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;splitParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalMl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;density&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;grams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;totalMl&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;density&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;totalGrams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grams&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;partA&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;grams&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;partB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;grams&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;splitParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;311&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;           &lt;span class="c1"&gt;// { totalGrams: 342, partA: 171, partB: 171 }&lt;/span&gt;
&lt;span class="nf"&gt;splitParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;311&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;   &lt;span class="c1"&gt;// 2:1 system&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One caveat: when a kit specifies 1:1 &lt;em&gt;by volume&lt;/em&gt;, part A and part B often have slightly different densities, so the gram split is not exactly 50/50. Read the technical data sheet and use the by-weight ratio it lists if there is one.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Depth limits matter more than volume
&lt;/h2&gt;

&lt;p&gt;The other mistake the calculator catches is pour depth. A coating resin poured 2 cm thick in a deep mold can overheat, yellow or crack. A simple guard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;lifts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depthCm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxPourCm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;depthCm&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;maxPourCm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;lifts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 3 separate pours&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Try the full versions
&lt;/h2&gt;

&lt;p&gt;I put the finished logic, with unit switching and presets, into two free tools: a &lt;a href="https://epoxyplanner.com/resin-mold-calculator/" rel="noopener noreferrer"&gt;resin mold calculator&lt;/a&gt; for any shape or water-measured mold, and a &lt;a href="https://epoxyplanner.com/resin-coaster-calculator/" rel="noopener noreferrer"&gt;resin coaster calculator&lt;/a&gt; that handles sets, rounded corners and waste.&lt;/p&gt;

&lt;p&gt;The takeaway for anyone building estimators: keep the model small, give the user a measurement escape hatch (water displacement), and always show the waste allowance explicitly so it can be tuned. A calculator that hides its assumptions gets ignored the first time it is wrong.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>math</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Calculating Pool Volume for Rectangles, Circles and Ovals in JavaScript</title>
      <dc:creator>Agoes Jayanata</dc:creator>
      <pubDate>Sat, 24 Oct 2026 14:23:00 +0000</pubDate>
      <link>https://dev.to/agoesjayanata/calculating-pool-volume-for-rectangles-circles-and-ovals-in-javascript-bo2</link>
      <guid>https://dev.to/agoesjayanata/calculating-pool-volume-for-rectangles-circles-and-ovals-in-javascript-bo2</guid>
      <description>&lt;p&gt;If you've ever shocked a pool, balanced its chemistry, or ordered a fresh bag of salt for a salt-chlorine system, you already know the rule: the dose depends on the volume, and the volume depends on the shape. Swimming pools come in rectangles, circles, and ovals, and each one has its own volume formula. The good news is that they all collapse into a single area × average depth pattern, and a few lines of JavaScript are enough to cover every case a DIY calculator will meet.&lt;/p&gt;

&lt;p&gt;The first detail that trips people up is depth. Most pools slope from a shallow end to a deep end, so a single depth number doesn't describe the water. The standard fix is to average the two:&lt;/p&gt;

&lt;p&gt;averageDepth = (shallowDepth + deepDepth) / 2&lt;/p&gt;

&lt;p&gt;Use that average everywhere in the rest of the math. For a rectangular pool, the volume in cubic feet is straightforward:&lt;/p&gt;

&lt;p&gt;volumeFt3 = lengthFt * widthFt * averageDepthFt&lt;br&gt;
Pool chemicals and fill rates are in gallons, not cubic feet. One cubic foot of water equals 7.48 gallons, so:&lt;/p&gt;

&lt;p&gt;gallons = volumeFt3 * 7.48&lt;/p&gt;

&lt;p&gt;A circle is a rectangle with a different area. The surface area is π × radius², and the radius is half the diameter:&lt;/p&gt;

&lt;p&gt;volumeFt3 = Math.PI * radiusFt * radiusFt * averageDepthFt&lt;/p&gt;

&lt;p&gt;An oval (or ellipse) is the tricky one. The exact area of an ellipse is π × semiMajor × semiMinor, which in pool math is almost always rewritten with the 0.785 approximation factor:&lt;/p&gt;

&lt;p&gt;area ≈ 0.785 * lengthFt * widthFt&lt;/p&gt;

&lt;p&gt;The 0.785 is π/4, and it converts a length × width rectangle into the ellipse's area. Multiply by average depth and you have the volume:&lt;/p&gt;

&lt;p&gt;volumeFt3 = 0.785 * lengthFt * widthFt * averageDepthFt&lt;br&gt;
That lets you write one JavaScript function that covers every shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;poolVolumeGallons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shallowDepthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deepDepthFt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;avgDepth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shallowDepthFt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;deepDepthFt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;volumeFt3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rectangle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;volumeFt3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;avgDepth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;circle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;diameter&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;volumeFt3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;avgDepth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;oval&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;volumeFt3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.785&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;avgDepth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;volumeFt3&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;7.48&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 16 ft × 32 ft oval, 3 ft shallow and 9 ft deep:&lt;/p&gt;

&lt;p&gt;poolVolumeGallons('oval', { length: 32, width: 16 }, 3, 9)&lt;br&gt;
≈ 0.785 × 32 × 16 × 6 × 7.48 ≈ 18,160 gallons&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://fixupmath.com/pool-volume-calculator/" rel="noopener noreferrer"&gt;pool volume calculator&lt;/a&gt; walks through this exact pattern for each shape, including the average-depth step.&lt;/p&gt;
&lt;h2&gt;
  
  
  Salt Dosing From the Volume
&lt;/h2&gt;

&lt;p&gt;Once you trust your gallon count, chemistry becomes straightforward. The most common calculation is salt dosing for a salt-chlorine generator. The formula the industry uses is:&lt;/p&gt;

&lt;p&gt;pounds of salt = gallons × ppmDifference × 8.34 / 1,000,000&lt;/p&gt;

&lt;p&gt;The 8.34 is the weight of a gallon of water in pounds (water weighs about 8.34 lb/gal), and the 1,000,000 converts the parts-per-million ratio into actual pounds. In JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saltPoundsNeeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gallons&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;currentPPM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;targetPPM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ppmDiff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;targetPPM&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;currentPPM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ppmDiff&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gallons&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ppmDiff&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;8.34&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;saltPoundsNeeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;18160&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;≈&lt;/span&gt; &lt;span class="mi"&gt;308&lt;/span&gt; &lt;span class="nx"&gt;pounds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the 18,160-gallon oval above, raising salinity from 1,500 ppm to 3,500 ppm calls for about 308 pounds of salt. Note the guard clause: if the current reading is already at or above the target, the function returns zero instead of a meaningless negative number.&lt;/p&gt;

&lt;p&gt;A 10,000-gallon rectangular pool that's at 2,500 ppm and needs 3,500 ppm only needs roughly 83 pounds. Same formula, different volume — which is exactly why getting the volume right matters more than memorizing the salt constant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Pool volume is one of those calculations where the math is honest and the mistakes are human: measure the shallow and deep ends separately, average them, pick the right shape factor (1.0 for rectangles, π for circles, 0.785 for ovals), multiply through, and convert at the end with 7.48. Get the gallons right and every downstream dose — shock, algaecide, pH adjuster, salt — scales from the same number.&lt;/p&gt;

&lt;p&gt;If you're putting a pool calculator together, the single &lt;code&gt;poolVolumeGallons&lt;/code&gt; function above is a clean start. Add the salt dosing helper next, and you already cover the two questions pool owners ask most often. The &lt;a href="https://fixupmath.com/pool-volume-calculator/" rel="noopener noreferrer"&gt;pool volume calculator&lt;/a&gt; and &lt;a href="https://fixupmath.com/salt-pool-calculator/" rel="noopener noreferrer"&gt;salt pool calculator&lt;/a&gt; show the worked-out versions, including the average-depth step and the ppm-guard clause.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>math</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Board Feet and Cubic Yards: Two Tiny Unit Functions Every DIY Calculator Needs</title>
      <dc:creator>Agoes Jayanata</dc:creator>
      <pubDate>Sat, 24 Oct 2026 14:20:39 +0000</pubDate>
      <link>https://dev.to/agoesjayanata/board-feet-and-cubic-yards-two-tiny-unit-functions-every-diy-calculator-needs-1dmj</link>
      <guid>https://dev.to/agoesjayanata/board-feet-and-cubic-yards-two-tiny-unit-functions-every-diy-calculator-needs-1dmj</guid>
      <description>&lt;p&gt;If you've ever built a deck, framed a shed, or ordered concrete for a patio, you've probably reached for a calculator at some point. DIY calculators are useful, but the unit conversions behind them follow the same small patterns every time. Two of the most common are board feet, which measure lumber, and cubic yards, which measure bulk materials like dirt, mulch, and concrete.&lt;/p&gt;

&lt;p&gt;Both reduce to a tiny formula and a few lines of JavaScript. Below I'll show one function for each, the gotchas that make the difference between a clean build and a truckload of waste, and links to ready-made calculators you can try.&lt;/p&gt;

&lt;h2&gt;
  
  
  Board Feet
&lt;/h2&gt;

&lt;p&gt;A board foot is the standard unit for pricing lumber in the U.S. It represents a piece of wood 1 inch thick, 12 inches wide, and 12 inches long — exactly one cubic foot of volume expressed in lumber terms. The formula is simple:&lt;/p&gt;

&lt;p&gt;board feet = thickness(in) × width(in) × length(ft) / 12&lt;/p&gt;

&lt;p&gt;In JavaScript it becomes a single function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;boardFeet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;thicknessIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;widthIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lengthFt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;thicknessIn&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;widthIn&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;lengthFt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Call it with the actual measured dimensions, not the label on the sticker. A "2×4×8" stud is sold by its nominal size, but a dressed 2×4 is closer to 1.5 inches by 3.5 inches thick. Using the nominal 2 and 4 would overstate your board feet and make your takeoff look cheaper than it really is.&lt;br&gt;
For example, a true 2×4 that is 1.5 in thick, 3.5 in wide, and 8 ft long:&lt;/p&gt;

&lt;p&gt;boardFeet(1.5, 3.5, 8) → (1.5 × 3.5 × 8) / 12 = 3.5 board feet&lt;/p&gt;

&lt;p&gt;If you used the nominal 2 and 4 instead, you'd get 5.33 board feet — over 50% more. That's why every serious lumber takeoff uses actual dimensions.&lt;/p&gt;

&lt;p&gt;A worked-out version lives in this &lt;a href="https://fixupmath.com/board-foot-calculator/" rel="noopener noreferrer"&gt;board foot calculator&lt;/a&gt;, which walks through the thickness, width, and length step by step.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cubic Yards
&lt;/h2&gt;

&lt;p&gt;Cubic yards is how ready-mix concrete, topsoil, and mulch are sold. The formula starts in feet and divides by 27, because one cubic yard equals 27 cubic feet (3 ft × 3 ft × 3 ft):&lt;/p&gt;

&lt;p&gt;cubic yards = length(ft) × width(ft) × depth(ft) / 27&lt;br&gt;
The JavaScript version is equally short:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cubicYards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lengthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;widthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depthFt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lengthFt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;widthFt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;depthFt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A 12 ft × 8 ft patio poured 4 inches deep (0.333 ft):&lt;/p&gt;

&lt;p&gt;cubicYards(12, 8, 0.333) ≈ 1.18 cubic yards&lt;/p&gt;

&lt;p&gt;Here's where beginners trip up. You don't order 1.18 yards of concrete — suppliers deliver by the yard and you want a little extra for spill, windrows, and form variation. The standard practice is to round up to the nearest tenth or a quarter-yard and add 5-10% for waste:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;yardsToOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lengthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;widthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wastePct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;cubicYards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lengthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;widthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;depthFt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;exact&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;wastePct&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;yardsToOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.333&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="mf"&gt;1.3&lt;/span&gt; &lt;span class="nx"&gt;cubic&lt;/span&gt; &lt;span class="nx"&gt;yards&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/ 27&lt;/code&gt; is the entire math; everything else is the real world. A complete walk-through is in this &lt;a href="https://fixupmath.com/cubic-yard-calculator/" rel="noopener noreferrer"&gt;cubic yard calculator&lt;/a&gt;, including inch-to-feet depth conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why These Two Together
&lt;/h2&gt;

&lt;p&gt;Board feet and cubic yards show up on almost the same job. You'll calculate board feet for the framing, then cubic yards for the footing concrete that holds it. Both formulas are one multiplication chain and one division; the judgment calls — actual vs nominal dimensions, and rounding up for purchase — are what separate a useful calculator from a dangerous one.&lt;/p&gt;

&lt;p&gt;If you're building a DIY estimator, start with these two functions. Keep thickness, width, and length in their native units so the function signature tells you the unit at the call site, and round up on anything the customer pays for. You can try the &lt;a href="https://fixupmath.com/board-foot-calculator/" rel="noopener noreferrer"&gt;board foot calculator&lt;/a&gt; and &lt;a href="https://fixupmath.com/cubic-yard-calculator/" rel="noopener noreferrer"&gt;cubic yard calculator&lt;/a&gt; side by side to see how the same pattern repeats across different materials.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>math</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Building a Multilingual Calculator Site: Metric vs Imperial Unit Handling (epoxy example)</title>
      <dc:creator>Agoes Jayanata</dc:creator>
      <pubDate>Sat, 24 Oct 2026 12:29:17 +0000</pubDate>
      <link>https://dev.to/agoesjayanata/building-a-multilingual-calculator-site-metric-vs-imperial-unit-handling-epoxy-example-5d5d</link>
      <guid>https://dev.to/agoesjayanata/building-a-multilingual-calculator-site-metric-vs-imperial-unit-handling-epoxy-example-5d5d</guid>
      <description>&lt;p&gt;When a calculator spans languages, unit systems and rounding rules collide with locale conventions. A single input — "how much epoxy do I need?" — might be answered in millimetres and litres in Munich, inches and US gallons in Houston, or centimetres and litres in São Paulo. Behind the scenes you want one source of truth so you don't end up with six different volume formulas diverging over time.&lt;/p&gt;

&lt;p&gt;The approach that keeps things maintainable is simple in principle and a little fiddly in practice: store every value internally in SI units (metre, kilogram, second, litre) and only convert at the UI edge. The conversion layer becomes a thin, testable adapter between the user's units and the model, and the rest of your code never thinks about inches or gallons again.&lt;/p&gt;

&lt;h2&gt;
  
  
  SI as the internal model
&lt;/h2&gt;

&lt;p&gt;In a material-estimation tool like &lt;a href="https://epoxyplanner.com/" rel="noopener noreferrer"&gt;Epoxy Planner&lt;/a&gt;, the model is just a few numbers: area, thickness, coverage rate, and the resulting volume. Pick SI once and commit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Length: metre (m). The user may type millimetres for a floor coating thickness — convert to metres the moment the value enters the model.&lt;/li&gt;
&lt;li&gt;Area: square metre (m²). Surface-area inputs in square feet convert to m² at the input; the calculator never sees ft².&lt;/li&gt;
&lt;li&gt;Volume: litre (L). A US user enters gallons; multiply by 3.78541 and store litres.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rule of thumb: the conversion constant lives next to the input control, not inside the formula. That way your coverage-rate math is &lt;code&gt;area_m2 * thickness_m * 1000 = litres&lt;/code&gt;, and it stays the same whether the input panel is German or English.&lt;/p&gt;

&lt;h2&gt;
  
  
  Converting at the UI edge
&lt;/h2&gt;

&lt;p&gt;Define a small conversion map and use it consistently for input and display. Here is a minimal example covering the units a coating calculator typically needs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TO_SI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;mm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.0254&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3048&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ft2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.092903&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;m2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gal-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;3.78541&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;L&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toSI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TO_SI&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;best&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;units&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;units&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;best&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;category&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;units&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;best&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 20 mm film → metres, 500 sq ft → m², 5 US gal → litres&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;toSI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;     &lt;span class="c1"&gt;// { length: 0.02 }&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;toSI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ft2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;   &lt;span class="c1"&gt;// { area: 46.4515 }&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;toSI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gal-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// { volume: 18.92705 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Going the other way, display units are derived by dividing by the same constant. Because you only touch the conversion table, adding a new unit (say, imperial gallons) is a one-line change and the formula code is untouched.&lt;/p&gt;

&lt;p&gt;Be aware of the direction mismatch for area and volume: &lt;code&gt;ft² → m²&lt;/code&gt; uses 0.092903, but &lt;code&gt;m² → ft²&lt;/code&gt; divides by that constant (≈10.7639). Using the same table with division works cleanly as long as you don't accidentally multiply by the "to SI" factor on the way out — a common off-by-one-constant bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Locale-aware number formatting
&lt;/h2&gt;

&lt;p&gt;Once the calculator hands back litres, you have to render them for the user. In Western Europe and Latin America a comma is the decimal separator, and a dot groups thousands. &lt;code&gt;Intl.NumberFormat&lt;/code&gt; handles this without you shipping locale tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatForLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NumberFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;minimumFractionDigits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;maximumFractionDigits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 18.927 litres shown differently in Germany and Brazil&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;formatForLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;18.92705&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;de-DE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// "18,93"&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;formatForLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;18.92705&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pt-BR&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// "18,93"&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;formatForLocale&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;18.92705&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// "18.93"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The locales where a decimal comma matters for this kind of site include &lt;code&gt;de&lt;/code&gt;, &lt;code&gt;fr&lt;/code&gt;, &lt;code&gt;es&lt;/code&gt;, &lt;code&gt;it&lt;/code&gt;, and &lt;code&gt;pt-BR&lt;/code&gt;. Round-trip carefully on input: if you let the user type into a &lt;code&gt;number&lt;/code&gt; input, browsers already handle the locale separator, but a free-text field needs &lt;code&gt;Number(str.replace(',', '.'))&lt;/code&gt; before conversion, otherwise &lt;code&gt;"3,5"&lt;/code&gt; parses as &lt;code&gt;3&lt;/code&gt; in JavaScript and silently truncates the order.&lt;/p&gt;

&lt;p&gt;When &lt;a href="https://epoxyplanner.com/" rel="noopener noreferrer"&gt;Epoxy Planner&lt;/a&gt; localised the calculator, the German epoxy floor calculator at the &lt;a href="https://epoxyplanner.com/de/epoxidboden-rechner/" rel="noopener noreferrer"&gt;German epoxy floor calculator&lt;/a&gt; URL needed the comma, and the &lt;a href="https://epoxyplanner.com/pt-br/calculadora-volume-resina-epoxi/" rel="noopener noreferrer"&gt;Brazilian Portuguese version&lt;/a&gt; needed it too — the difference was in word order, not the decimal mark.&lt;/p&gt;

&lt;h2&gt;
  
  
  hreflang alternates
&lt;/h2&gt;

&lt;p&gt;Search engines won't serve a French user a page formatted for US conventions if you tell them the relationship between your locale pages. Emit &lt;code&gt;hreflang&lt;/code&gt; link elements for every language variant, plus an &lt;code&gt;x-default&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"de"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://epoxyplanner.com/de/..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"fr"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://epoxyplanner.com/fr/..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"es"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://epoxyplanner.com/es/..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"it"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://epoxyplanner.com/it/..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"pt-br"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://epoxyplanner.com/pt-br/..."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"alternate"&lt;/span&gt; &lt;span class="na"&gt;hreflang=&lt;/span&gt;&lt;span class="s"&gt;"x-default"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://epoxyplanner.com/"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two implementation notes that bite in practice. First, every alternate page must link back to every other alternate, including itself — a missing self-link is the most common hreflang audit finding. Second, the path prefix you choose (&lt;code&gt;/de/&lt;/code&gt;, &lt;code&gt;/fr/&lt;/code&gt;, &lt;code&gt;/es/&lt;/code&gt;, &lt;code&gt;/it/&lt;/code&gt;, &lt;code&gt;/pt-br/&lt;/code&gt;) is what shows in the URL and in search results, so keep it stable. Switching from &lt;code&gt;/pt/&lt;/code&gt; to &lt;code&gt;/pt-br/&lt;/code&gt; mid-project forces a redirect chain that dilutes ranking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rounding rules for material orders
&lt;/h2&gt;

&lt;p&gt;Coverage math is continuous; epoxy kits are discrete. A calculator that prints "18.93 L" is misleading when the store sells 5 L buckets and 20 L drums. The business rule for material orders is almost always "round up to the next available kit size," never round to nearest and never round down.&lt;/p&gt;

&lt;p&gt;A clean way to implement this is a small &lt;code&gt;ceilToKits&lt;/code&gt; helper that returns both the kit breakdown and the true purchase volume:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ceilToKits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;litres&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;kitSizes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;litres&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;kits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;kitSizes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;kits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nx"&gt;remaining&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;purchasedLitres&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;kits&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;kits&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;purchasedLitres&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;waste&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;purchasedLitres&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;litres&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 18.93 L → one 20 L kit, 1.07 L over-pour&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;ceilToKits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;18.92705&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how the input to the function is still the SI value (litres). The kit sizes are business data, not unit data, so they live in product config, not in the converter. This separation is what lets you change from "5 L / 20 L" buckets to "1 gal / 5 gal" pails for a US variant by swapping one config array instead of rewriting the coverage math.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;The architecture that survives localisation looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Input adapter&lt;/strong&gt; — reads the user's unit selector, parses the locale-aware number, calls &lt;code&gt;toSI&lt;/code&gt;, hands an SI value to the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Model&lt;/strong&gt; — coverage formula in metres, square metres and litres. No units, no locale, no rounding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output adapter&lt;/strong&gt; — takes the litres, runs &lt;code&gt;ceilToKits&lt;/code&gt; with the regional kit catalog, then &lt;code&gt;Intl.NumberFormat&lt;/code&gt; with the page locale for display.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every layer has a single responsibility and a clear contract. The converter only knows unit constants, the model only knows arithmetic, and the formatter only knows locales and kit sizes.&lt;/p&gt;

&lt;p&gt;For a worked-through example of this pattern in a real product, see &lt;a href="https://epoxyplanner.com/" rel="noopener noreferrer"&gt;Epoxy Planner&lt;/a&gt; — the same coverage model powers the English, the &lt;a href="https://epoxyplanner.com/de/epoxidboden-rechner/" rel="noopener noreferrer"&gt;German epoxy floor calculator&lt;/a&gt;, and the &lt;a href="https://epoxyplanner.com/pt-br/calculadora-volume-resina-epoxi/" rel="noopener noreferrer"&gt;Brazilian Portuguese version&lt;/a&gt;, differing only at the UI edge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Store values in SI internally; convert at the UI edge so the formula never changes.&lt;/li&gt;
&lt;li&gt;Keep your &lt;code&gt;toSI&lt;/code&gt; / &lt;code&gt;fromSI&lt;/code&gt; table small and test the inverse explicitly — area and volume factors invert, they don't mirror.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;Intl.NumberFormat&lt;/code&gt; for every locale that writes decimals with a comma (&lt;code&gt;de&lt;/code&gt;, &lt;code&gt;fr&lt;/code&gt;, &lt;code&gt;es&lt;/code&gt;, &lt;code&gt;it&lt;/code&gt;, &lt;code&gt;pt-BR&lt;/code&gt;), and normalise input before parsing.&lt;/li&gt;
&lt;li&gt;Emit a complete, self-linking &lt;code&gt;hreflang&lt;/code&gt; set for &lt;code&gt;/de/&lt;/code&gt;, &lt;code&gt;/fr/&lt;/code&gt;, &lt;code&gt;/es/&lt;/code&gt;, &lt;code&gt;/it/&lt;/code&gt;, &lt;code&gt;/pt-br/&lt;/code&gt; and an &lt;code&gt;x-default&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Round material orders up to kit sizes with a dedicated helper; treat kit sizes as product config, not units.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>i18n</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Coding a Voltage Drop and Wire Size Calculator in JavaScript</title>
      <dc:creator>Agoes Jayanata</dc:creator>
      <pubDate>Thu, 22 Oct 2026 17:07:02 +0000</pubDate>
      <link>https://dev.to/agoesjayanata/coding-a-voltage-drop-and-wire-size-calculator-in-javascript-4foc</link>
      <guid>https://dev.to/agoesjayanata/coding-a-voltage-drop-and-wire-size-calculator-in-javascript-4foc</guid>
      <description>&lt;p&gt;When you're designing a branch circuit or sizing the conductors for a panel feed, the conversation quickly narrows to one question: how much voltage is going to disappear between the source and the load, and what wire size will keep that drop acceptable? Electrical codes and good practice keep circling back to Ohm's law and a single rule of thumb, and it turns out those rules translate almost directly into JavaScript. In this post we'll build the math for a &lt;a href="https://fixupmath.com/voltage-drop-calculator/" rel="noopener noreferrer"&gt;voltage drop calculator&lt;/a&gt; and a &lt;a href="https://fixupmath.com/wire-size-calculator/" rel="noopener noreferrer"&gt;wire size calculator&lt;/a&gt;, work through a real example, and write a short function that picks the smallest American Wire Gauge (AWG) size that keeps voltage drop under the familiar 3 percent guideline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ohm's law and the voltage drop idea
&lt;/h2&gt;

&lt;p&gt;Ohm's law is the starting point: V = I × R. Voltage (V) equals current (I) multiplied by the resistance (R) of the path. In a real circuit the conductors themselves have resistance, so some of the source voltage is used up just getting power to the load. That lost voltage is the "voltage drop," and if it gets too large the load — a motor, a light, an outlet — won't perform as designed.&lt;/p&gt;

&lt;p&gt;The National Electrical Code (NEC) doesn't mandate a single number, but it recommends in Informational Note No. 2 of 210.19(A)(1) that the branch-circuit voltage drop not exceed 3 percent. Many designers also budget 3 percent for the feeder, so the total end-to-end drop stays under about 6 percent. For this calculator we'll target that 3 percent guideline on the branch circuit.&lt;/p&gt;

&lt;h2&gt;
  
  
  The single-phase voltage drop formula
&lt;/h2&gt;

&lt;p&gt;For a single-phase circuit (the kind that comes from a typical 120 V or 240 V residential service) the voltage drop is computed as:&lt;/p&gt;

&lt;p&gt;VD = 2 × K × I × L / CM&lt;/p&gt;

&lt;p&gt;where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VD&lt;/strong&gt; is the voltage drop in volts,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2&lt;/strong&gt; accounts for the round trip (hot and neutral),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;K&lt;/strong&gt; is the resistivity constant of the conductor material — approximately 12.9 for copper at around 75 °F (the NEC and many handbooks use this value for copper),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I&lt;/strong&gt; is the current in amperes,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;L&lt;/strong&gt; is the one-way length of the run in feet,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CM&lt;/strong&gt; is the cross-sectional area of the conductor in circular mils.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few quick notes: the factor of 2 is what makes single-phase different from three-phase. Three-phase uses √3 instead of 2 and drops the neutral-return path. The K value of 12.9 is an approximation — actual resistance depends on temperature and the precise alloy — but it's the number that shows up in tables and calculators everywhere, and it's what we'll hard-code in the snippet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Circular mils and the AWG table
&lt;/h2&gt;

&lt;p&gt;Wire size in North America is specified by the AWG system, and in that system the physical cross-section is expressed in circular mils (CM). A circular mil is the area of a circle one mil (0.001 inch) in diameter. The higher the AWG number, the thinner the wire and the smaller the CM value.&lt;/p&gt;

&lt;p&gt;For a voltage drop calculator we only need a small lookup table of the most common sizes. Here are representative values:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AWG&lt;/th&gt;
&lt;th&gt;Circular mils (CM)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;4,110&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;6,530&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10,380&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;16,510&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;26,240&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;41,740&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;52,620&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;66,360&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;83,690&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1/0&lt;/td&gt;
&lt;td&gt;105,600&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are the standard values you'll find in the NEC Chapter 9, Table 8. A production &lt;a href="https://fixupmath.com/wire-size-calculator/" rel="noopener noreferrer"&gt;wire size calculator&lt;/a&gt; usually stores the full table and iterates from 14 AWG upward until the computed drop satisfies the target.&lt;/p&gt;

&lt;h2&gt;
  
  
  Worked example: 20 A at 120 V over 100 ft
&lt;/h2&gt;

&lt;p&gt;Let's run the numbers for a common case: a 20 A branch circuit feeding a load at the end of a 100 ft one-way run, nominally 120 V, copper conductors, single phase.&lt;/p&gt;

&lt;p&gt;First try 12 AWG, whose CM is 6,530.&lt;/p&gt;

&lt;p&gt;VD = 2 × 12.9 × 20 × 100 / 6,530&lt;br&gt;
  = 51,600 / 6,530&lt;br&gt;
  ≈ 7.90 V&lt;/p&gt;

&lt;p&gt;As a percentage of the 120 V nominal: 7.90 / 120 ≈ 6.58… wait — let me recheck the arithmetic. 2 × 12.9 × 20 × 100 = 51,600. Dividing 51,600 by 6,530 gives about 7.90 V. 7.90 / 120 ≈ 0.0658, which is roughly 6.6 percent.&lt;/p&gt;

&lt;p&gt;That's already over the 3 percent guideline. The 3 percent of 120 V is 3.6 V. To stay under 3.6 V we'd need CM ≥ 51,600 / 3.6 ≈ 14,333 circular mils. The next standard size up from 12 AWG is 10 AWG at 10,380 CM — which is still under 14,333 — so let me re-examine.&lt;/p&gt;

&lt;p&gt;Actually, let's redo this with a slightly different, more typical current. The example that illustrates the upsize decision most cleanly uses a load closer to what a 12 AWG branch actually carries. Let's recompute 12 AWG at the same 100 ft but confirm the voltage drop, then check 10 AWG.&lt;/p&gt;

&lt;p&gt;For 10 AWG (CM = 10,380):&lt;br&gt;
VD = 51,600 / 10,380 ≈ 4.97 V, which is 4.97 / 120 ≈ 4.14 percent.&lt;/p&gt;

&lt;p&gt;Still over 3 percent. So at 20 A over 100 ft, even 10 AWG doesn't quite hit 3 percent with K = 12.9. The message here is the important one: 12 AWG gives roughly 6.6 percent drop, which is unacceptable, and upsizing to 10 AWG improves it to about 4.1 percent — a real reduction, but you may still want to consider an even larger conductor, a shorter run, or a higher voltage.&lt;/p&gt;

&lt;p&gt;That said, the pattern most designers see in practice — and the one we'll use to motivate the code — is that 12 AWG often lands you right around 3 to 4 percent on moderate runs and upsizing one step to 10 AWG brings you under the guideline. A calculator makes the trade-off explicit instead of guessing. If you want a more generous K that reflects higher temperature, the numbers shift, which is another reason to let the code do the comparison.&lt;/p&gt;
&lt;h2&gt;
  
  
  Picking the smallest AWG under 3 percent in JavaScript
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://fixupmath.com/voltage-drop-calculator/" rel="noopener noreferrer"&gt;voltage drop calculator&lt;/a&gt; at FixupMath evaluates the formula and compares the result to the target. We can do the same in a short function that walks the AWG table from the smallest conductor upward and returns the first one whose drop is at or below 3 percent of the source voltage.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Circular mils for common AWG sizes (NEC Ch. 9, Table 8)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;AWG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4110&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6530&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10380&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16510&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;26240&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;41740&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;52620&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;66360&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="na"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;83690&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;105600&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;K_COPPER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;12.9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// single-phase copper constant&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PHASE_FACTOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// round-trip for single-phase&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;voltageDropVolts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lengthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PHASE_FACTOR&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;K_COPPER&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;currentA&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;lengthFt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/**
 * Returns the smallest AWG entry whose voltage drop is under
 * maxDropPercent percent of nominalVoltage.
 * Returns null if no size in the table is sufficient.
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;smallestAwgUnderPercent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;currentA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;lengthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;nominalVoltage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;maxDropPercent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxDropVolts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxDropPercent&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;nominalVoltage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;AWG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;drop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;voltageDropVolts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lengthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;percent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;drop&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;nominalVoltage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;drop&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;maxDropVolts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;dropVolts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;dropPercent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;percent&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example: 20 A at 120 V over 100 ft&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;smallestAwgUnderPercent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Pick &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;awg&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; AWG: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dropVolts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; V  (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dropPercent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;%)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;No standard size in the table keeps drop under 3%. Upsize or shorten the run.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With current = 20, length = 100, and nominal = 120, the loop tries 14, 12, 10, 8… and returns the first entry whose drop is ≤ 3.6 V. Because even 10 AWG drops about 4.97 V at those inputs, this particular call would walk all the way to 8 AWG (CM = 16,510 → drop ≈ 3.12 V, ≈ 2.6 percent) and return 8 AWG. The point is the pattern: start small, compute, upsize until you satisfy the guideline. If the table runs out before you satisfy it, the function signals that explicitly rather than silently picking an undersized wire.&lt;/p&gt;

&lt;p&gt;If you prefer a different K, a three-phase factor, or a stricter 2 percent target for sensitive equipment, you pass those in as parameters instead of hard-coding. That's the whole design philosophy of the &lt;a href="https://fixupmath.com/wire-size-calculator/" rel="noopener noreferrer"&gt;wire size calculator&lt;/a&gt;: the formula is the formula; the constants are the knobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;This article and any code or calculator built from it are for educational purposes only. Voltage drop, ampacity, temperature correction, and the choice of conductor are all governed by your local electrical code and by the conditions of the actual installation (ambient temperature, number of current-carrying conductors in a raceway, type of insulation, and so on). Always follow your local code, use the tables and correction factors it references, and have a licensed electrician or qualified authority review anything that will be energized. The numbers in this post use K ≈ 12.9 and a limited AWG table as illustrations; a real design may require a different K, parallel conductors, or a three-phase formulation.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>math</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Geometry Behind a Stair Stringer and Rafter Length Calculator</title>
      <dc:creator>Agoes Jayanata</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:38:54 +0000</pubDate>
      <link>https://dev.to/agoesjayanata/the-geometry-behind-a-stair-stringer-and-rafter-length-calculator-2c48</link>
      <guid>https://dev.to/agoesjayanata/the-geometry-behind-a-stair-stringer-and-rafter-length-calculator-2c48</guid>
      <description>&lt;p&gt;Framing is just applied geometry. Every stair stringer and every rafter reduces to a right triangle: a vertical rise, a horizontal run, and the hypotenuse that the lumber has to cover. Get the triangle right and the structure works. The &lt;a href="https://fixupmath.com/stair-calculator/" rel="noopener noreferrer"&gt;stair calculator&lt;/a&gt; and &lt;a href="https://fixupmath.com/rafter-calculator/" rel="noopener noreferrer"&gt;rafter calculator&lt;/a&gt; at FixUpMath were built with Astro and published free because the math — not a paywall — should be the product. Building either one is a compact project that teaches you integer ceilings, the Pythagorean theorem, and how to turn pitch into a slope ratio in JavaScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stairs: rise and run
&lt;/h2&gt;

&lt;p&gt;A stair is defined by its total rise (the vertical distance from one finished floor to the next) and its total run (how far the run of treads extends horizontally). The code's first job is to turn those into per-step dimensions.&lt;/p&gt;

&lt;p&gt;Building codes in the U.S. cap the riser at 7.75 in and typically require a minimum tread run around 10 in. Given a total rise in inches, the number of risers is the ceiling of total-rise divided by the max riser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;riserCount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalRiseIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;maxRiserIn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;7.75&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalRiseIn&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;maxRiserIn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Math.ceil&lt;/code&gt; again — same reason. If a 90 in rise gives you 11.61 risers, you can't build 11.61 of anything. Twelve risers at 7.5 in each satisfy the code and split the rise evenly. The uniform riser height is then &lt;code&gt;totalRiseIn / riserCount&lt;/code&gt;, and the total run is &lt;code&gt;riserCount * treadRunIn&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stringer length via Pythagoras
&lt;/h2&gt;

&lt;p&gt;A straight-run stringer carries every riser and every tread. Its horizontal projection is the total run; its vertical projection is the total rise. The cut length along the hypotenuse is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;stringerLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalRiseIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;totalRunIn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalRiseIn&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;totalRunIn&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the theoretical length. In practice you add a few inches for the heel cut, the toe cut, and the bottom notch — and you buy the 2×12 a foot or two longer than the computed hypotenuse. In UI terms, show the computed length and the recommended purchase length separately so the user can see where the extra came from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rafters: span and pitch
&lt;/h2&gt;

&lt;p&gt;A rafter is the same shape of problem. You know the span (the horizontal distance the roof covers) and the pitch (rise per 12 in of run, written as a ratio like 6/12). A 6/12 pitch means the roof rises 6 in for every 12 in it runs.&lt;/p&gt;

&lt;p&gt;Half the span is the horizontal run the rafter covers. The rise over that run is &lt;code&gt;pitchRatio * halfSpan&lt;/code&gt;, and the rafter length is the hypotenuse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rafterLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;spanIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pitchNumerator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pitchDenominator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;halfSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;spanIn&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pitchNumerator&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;pitchDenominator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;halfSpan&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rafter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;halfSpan&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;rise&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rafter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rise&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;halfSpan&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Return an object, not a single number. The rise and half-span are the intermediate values the user needs to audit, and the rafter length is the number that goes on the cut list. For a 28 ft (336 in) span at 6/12: half-span 168 in, rise 84 in, rafter ≈ 189.7 in (about 15 ft 9.7 in).&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the code trips
&lt;/h2&gt;

&lt;p&gt;Three edge cases show up repeatedly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Riser count rounding.&lt;/strong&gt; Always &lt;code&gt;Math.ceil&lt;/code&gt;. A 7.74 in riser is still a code-compliant answer only if every riser is ≤ 7.75 in, and &lt;code&gt;Math.round&lt;/code&gt; can produce a taller riser that fails inspection.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pitch normalization.&lt;/strong&gt; Let the user type "6/12" or just "6" and normalize it. A bare number is conventionally "N over 12". Label the assumption.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Units.&lt;/strong&gt; Stair users think in inches; roofers often think in feet for the span. Convert at the boundary and keep one canonical unit (inches) inside the math.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Putting a stair + rafter example together
&lt;/h2&gt;

&lt;p&gt;A deck stair with a 96 in total rise, 10 in treads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;risers = &lt;code&gt;ceil(96 / 7.75)&lt;/code&gt; = 13&lt;/li&gt;
&lt;li&gt;uniform riser = 96 / 13 ≈ 7.38 in&lt;/li&gt;
&lt;li&gt;total run = 13 × 10 = 130 in&lt;/li&gt;
&lt;li&gt;stringer = &lt;code&gt;sqrt(96² + 130²)&lt;/code&gt; ≈ 161.8 in (buy a 2×12 in 14 ft)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A gable roof, 24 ft (288 in) span at 8/12:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;half-span = 144 in&lt;/li&gt;
&lt;li&gt;rise = (8/12) × 144 = 96 in&lt;/li&gt;
&lt;li&gt;rafter = &lt;code&gt;sqrt(144² + 96²)&lt;/code&gt; ≈ 173.2 in (about 14 ft 5.2 in)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Right-triangle calculators are small enough to finish in an afternoon and real enough to matter. They teach the same pattern that comes up everywhere in tooling code: decompose the geometry, compute the intermediates, round the units that have to be whole, and show the user the work. The &lt;a href="https://fixupmath.com/stair-calculator/" rel="noopener noreferrer"&gt;stair calculator&lt;/a&gt; and &lt;a href="https://fixupmath.com/rafter-calculator/" rel="noopener noreferrer"&gt;rafter calculator&lt;/a&gt; are free, built with Astro, and a reasonable reference if you're practicing the pattern.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>math</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building Unit-Aware Construction Calculators: From Cubic Yards to 80-lb Bags</title>
      <dc:creator>Agoes Jayanata</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:35:38 +0000</pubDate>
      <link>https://dev.to/agoesjayanata/building-unit-aware-construction-calculators-from-cubic-yards-to-80-lb-bags-391l</link>
      <guid>https://dev.to/agoesjayanata/building-unit-aware-construction-calculators-from-cubic-yards-to-80-lb-bags-391l</guid>
      <description>&lt;p&gt;Construction work lives inside units. A driveway is measured in feet and inches, poured in cubic yards, and bought in 80-lb bags of premix. Get the unit conversions wrong and you either short the job or waste money on bags that sit in the truck bed. The &lt;a href="https://fixupmath.com/" rel="noopener noreferrer"&gt;FixUpMath&lt;/a&gt; calculator suite was built with Astro and published free, because the math — not a paywall — should be the product. Building a calculator like this is a small project that teaches you real things: input parsing, dimensional analysis, and defensive rounding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parsing ft/in/metric input
&lt;/h2&gt;

&lt;p&gt;A box labeled "Length" might receive &lt;code&gt;12 ft 6 in&lt;/code&gt;, &lt;code&gt;12'6"&lt;/code&gt;, &lt;code&gt;3.81 m&lt;/code&gt;, or just &lt;code&gt;12&lt;/code&gt;. Your parser has to decide the unit and return a single number in a canonical unit — usually feet for imperial, meters for metric.&lt;/p&gt;

&lt;p&gt;A pragmatic approach is to match a compound imperial pattern first, fall back to a single number with a unit suffix, and treat a bare number as feet. Here's a starting point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// "12 ft 6 in" / "12'6""&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;compound&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.?\d&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;ft|'|feet&lt;/span&gt;&lt;span class="se"&gt;?)\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.?\d&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;in|"|inch&lt;/span&gt;&lt;span class="se"&gt;)?&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;feet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compound&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compound&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;feet&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;inches&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// "3.81 m"&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.?\d&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;m|meters&lt;/span&gt;&lt;span class="se"&gt;?)&lt;/span&gt;&lt;span class="sr"&gt;/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metric&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;3.28084&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// "12 ft" or bare "12" (assumed feet)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.?\d&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;ft|'|feet&lt;/span&gt;&lt;span class="se"&gt;?)&lt;/span&gt;&lt;span class="sr"&gt;$/i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Bare number → feet&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bare&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.?\d&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;$/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bare&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bare&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Cannot parse length: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Surface the assumption. If you silently treat a bare &lt;code&gt;12&lt;/code&gt; as feet, say so in the UI with helper text like "No unit? We assume feet." Defensive parsing is a feature, not a bug.&lt;/p&gt;

&lt;h2&gt;
  
  
  Volume = L × W × T
&lt;/h2&gt;

&lt;p&gt;Once you have length, width, and thickness all in feet, volume in cubic feet is just the product:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;volumeCubicFeet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lengthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;widthFt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;thicknessFt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;lengthFt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;widthFt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;thicknessFt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The thickness is where most DIYers trip. Bagged concrete for a patio is typically 4 in (0.333 ft); a driveway pour is often 6 in (0.5 ft). Hardcoding thickness without labeling it as an assumption is a bug waiting to happen — surface it in the UI with a dropdown and a default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Converting ft³ to yd³
&lt;/h2&gt;

&lt;p&gt;Ready-mix concrete is sold by the cubic yard. One cubic yard is 27 cubic feet, so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;cubicFeetToCubicYards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cuFt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cuFt&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Division by 27 is the canonical trap in calculator code. Write it as a named constant (&lt;code&gt;const CU_FT_PER_CU_YD = 27&lt;/code&gt;) so the magic number can be reviewed and so a linter or a reviewer doesn't have to guess what &lt;code&gt;27&lt;/code&gt; means at 2 AM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying a waste factor
&lt;/h2&gt;

&lt;p&gt;Concrete shrinks, edges spill, and forms leak a little. The industry rule of thumb is to add 5–10% waste. Expose it as a slider so the user can feel the risk:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;applyWaste&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;yards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;wastePercent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;yards&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;wastePercent&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a 2.00 yd³ slab at 10% waste you get 2.20 yd³. Round the displayed value, not the intermediate — that's a general numerical hygiene rule that saves a lot of "why is my answer off by one bag?" complaints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rounding up to purchasable units: the 80-lb bag
&lt;/h2&gt;

&lt;p&gt;Bagged / premixed concrete is sold by the bag, not by the fraction. A standard 80-lb bag yields roughly 0.6 ft³ (about 0.0222 yd³). You can't buy 0.4 of a bag, so you always round up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;bagsNeeded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cuYards&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cuFtPerBag&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cuFt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cuYards&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cuFt&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;cuFtPerBag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Math.ceil&lt;/code&gt; is the most important function in construction calculator code. &lt;code&gt;Math.round&lt;/code&gt; will short the job. &lt;code&gt;Math.floor&lt;/code&gt; is a budget disaster. Every "how many X do I buy?" question in this domain rounds up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;End-to-end, a patio of 12 ft × 10 ft × 4 in (0.333 ft) thickness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;volume = 12 × 10 × 0.333 = 39.96 ft³&lt;/li&gt;
&lt;li&gt;yards = 39.96 / 27 = 1.48 yd³&lt;/li&gt;
&lt;li&gt;+10% waste = 1.63 yd³&lt;/li&gt;
&lt;li&gt;bags = ceil(1.63 × 27 / 0.6) = ceil(73.35) = &lt;strong&gt;74 bags&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Show the user the intermediate values (volume, yards, waste-adjusted) so they can audit the math, and put the bag count in big type at the top.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Unit-aware calculators are a great project for learning input parsing, dimensional analysis, and defensive rounding — and they're genuinely useful. The &lt;a href="https://fixupmath.com/" rel="noopener noreferrer"&gt;FixUpMath&lt;/a&gt; suite was built with Astro and published free so the math, not the paywall, is the product. If you're playing around with calculator apps, start with concrete: it teaches you parsing, unit conversion, waste factors, and the most important JavaScript function in the construction world — &lt;code&gt;Math.ceil&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Try the live &lt;a href="https://fixupmath.com/concrete-calculator/" rel="noopener noreferrer"&gt;concrete calculator&lt;/a&gt; and compare it to your own implementation. The trickiest part isn't the math; it's the edge cases in what a real person will type into a box labeled "Length".&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>math</category>
    </item>
    <item>
      <title>The Math Behind an Epoxy Resin Calculator (with JavaScript)</title>
      <dc:creator>Agoes Jayanata</dc:creator>
      <pubDate>Thu, 22 Oct 2026 16:22:04 +0000</pubDate>
      <link>https://dev.to/agoesjayanata/the-math-behind-an-epoxy-resin-calculator-with-javascript-4h67</link>
      <guid>https://dev.to/agoesjayanata/the-math-behind-an-epoxy-resin-calculator-with-javascript-4h67</guid>
      <description>&lt;p&gt;Building an epoxy resin calculator sounds simple until you realize the math under the hood spans geometry, unit systems, waste modeling, mix-ratio arithmetic, and multi-layer scheduling. In this post we walk through each calculation and ship 3 short JavaScript functions that power &lt;a href="https://epoxyplanner.com/epoxy-calculator/" rel="noopener noreferrer"&gt;Epoxy Planner&lt;/a&gt;, a web tool that turns project dimensions into part quantities. For the full accounting of where every constant comes from, see the &lt;a href="https://epoxyplanner.com/methodology/" rel="noopener noreferrer"&gt;methodology page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Volume of the pour
&lt;/h2&gt;

&lt;p&gt;Most pourable epoxies conform to the shape of their container, so the calculator starts by computing volume in cubic inches and later converts to fluid units. Three primitives cover the vast majority of projects.&lt;/p&gt;

&lt;p&gt;A rectangular slab (river table, countertop, tray) is &lt;code&gt;length × width × depth&lt;/code&gt;. A cylinder (a round table top, a tumbler, a column) is &lt;code&gt;π × r² × height&lt;/code&gt;. A sphere (a decorative paperweight, a dome cap) is &lt;code&gt;4/3 × π × r³&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;When a project combines shapes — a rectangular slab with a cylindrical inlay — the calculator sums the primitives. Here is a shape-agnostic &lt;code&gt;volume()&lt;/code&gt; that accepts the shape tag and the dimensions the UI collected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;volume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dims&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rect&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cylinder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;dims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sphere&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All dimensions are expected in inches at this stage so every primitive returns cubic inches.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Unit conversions
&lt;/h2&gt;

&lt;p&gt;Customers rarely think in cubic inches. The calculator converts to the units they buy resin in. The constants below are what the &lt;a href="https://epoxyplanner.com/methodology/" rel="noopener noreferrer"&gt;methodology page&lt;/a&gt; publishes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 US gallon = 231 cubic inches (exact by US definition)&lt;/li&gt;
&lt;li&gt;1 liter = 61.024 cubic inches&lt;/li&gt;
&lt;li&gt;1 cubic inch ≈ 16.387 milliliters&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A small converter keeps the units tractable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CU_IN_PER_US_GAL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;231&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CU_IN_PER_LITER&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;61.024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ML_PER_CU_IN&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;16.387&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;convertVolume&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cuIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;us_gal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cuIn&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;CU_IN_PER_US_GAL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;liter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cuIn&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;CU_IN_PER_LITER&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cuIn&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ML_PER_CU_IN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cuIn&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;to&lt;/code&gt; argument mirrors the unit selector in the UI. If a user asks for 2.4 liters of a rectangular 12 × 18 × 0.75" slab, &lt;code&gt;volume()&lt;/code&gt; produces 162 cu in, &lt;code&gt;convertVolume(162, 'liter')&lt;/code&gt; yields ≈ 2.655 L, and the UI rounds up to the nearest available container size.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Waste factor and seepage
&lt;/h2&gt;

&lt;p&gt;Real pours consume more resin than the raw volume. Two separate effects are modeled.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;waste factor&lt;/strong&gt; (typically 5–15%) accounts for pour loss, mixing-container residue, surface cleanup, and the fact that mixing two parts inevitably leaves a small film in each cup. It is applied as a multiplicative factor: &lt;code&gt;total × (1 + waste)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Seepage&lt;/strong&gt; applies only to porous substrates — raw wood, concrete, green marble — where the substrate soaks up a thin layer of resin. Seepage is modeled as a volume proportional to the exposed surface area times a seepage depth (commonly 0.02–0.05"). A 24 × 36" wood slab with 0.03" seepage on both faces absorbs roughly &lt;code&gt;2 × 24 × 36 × 0.03 = 51.84 cu in&lt;/code&gt; on top of the pour volume.&lt;/p&gt;

&lt;p&gt;Both adjustments are additive to the base volume before Part A/B splitting, so the ratio math stays clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Splitting Part A and Part B by ratio
&lt;/h2&gt;

&lt;p&gt;Most epoxies are two-part systems mixed by volume ratio. 1:1 is the easiest (equal parts resin and hardener). 2:1 is common in cast/resin kits (two parts resin to one part hardener).&lt;/p&gt;

&lt;p&gt;Given a total volume &lt;code&gt;V&lt;/code&gt; and a ratio expressed as &lt;code&gt;resinParts : hardenerParts&lt;/code&gt;, the split is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A = V × resinParts / (resinParts + hardenerParts)
B = V × hardenerParts / (resinParts + hardenerParts)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;splitByRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;totalVolume&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resinParts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hardenerParts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resinParts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;hardenerParts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;partA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;totalVolume&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;resinParts&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;partB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;totalVolume&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;hardenerParts&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;partA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;partB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;resinParts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hardenerParts&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// 1:1 example&lt;/span&gt;
&lt;span class="nf"&gt;splitByRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// { partA: 50, partB: 50, ratio: '1:1' }&lt;/span&gt;

&lt;span class="c1"&gt;// 2:1 example (two parts resin, one part hardener)&lt;/span&gt;
&lt;span class="nf"&gt;splitByRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// { partA: 66.67, partB: 33.33, ratio: '2:1' }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the split, each part's volume is converted to the user's chosen unit and rounded up to the nearest container the supplier sells. That is where many DIY calculators over-order — they round the total once and split, instead of splitting then rounding each component.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Splitting deep pours into lifts
&lt;/h2&gt;

&lt;p&gt;Epoxy cures exothermically. Cast pours deeper than the manufacturer's maximum layer depth can crack, yellow, or delaminate, so thick pieces are built in &lt;strong&gt;lifts&lt;/strong&gt; (also called layers or coats).&lt;/p&gt;

&lt;p&gt;If a slab needs 3" of depth and the resin's max layer depth is 1", the calculator computes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;liftCount = ceil(totalDepth / maxLayerDepth)
layerDepth = totalDepth / liftCount
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The layer volume for each lift is &lt;code&gt;footprintArea × layerDepth&lt;/code&gt;. The per-lift volume is what gets passed into waste + ratio + unit conversion, then multiplied by &lt;code&gt;liftCount&lt;/code&gt; to arrive at the project total.&lt;/p&gt;

&lt;p&gt;This matters for two reasons. First, the calculator can tell the builder how many pours they're scheduling. Second, waste is typically quoted per-pour (each new mix event leaves residue in fresh cups), so modeling waste per lift rather than once on the whole depth usually yields a higher and more realistic total.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;A real calculator chains these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sum volumes across every shape the user added (via &lt;code&gt;volume()&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Add seepage volume if the substrate is porous.&lt;/li&gt;
&lt;li&gt;Apply the waste factor to the subtotal.&lt;/li&gt;
&lt;li&gt;Split into Part A and Part B via &lt;code&gt;splitByRatio()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If the depth exceeds the max layer depth, recompute per-lift volume, apply waste per lift, split per lift, then multiply by &lt;code&gt;liftCount&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Convert each part to the desired unit via &lt;code&gt;convertVolume()&lt;/code&gt; and round up to sellable containers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That pipeline is exactly what &lt;a href="https://epoxyplanner.com/epoxy-calculator/" rel="noopener noreferrer"&gt;Epoxy Planner&lt;/a&gt; runs client-side. If you want the constants, assumptions, and edge cases in writing, the &lt;a href="https://epoxyplanner.com/methodology/" rel="noopener noreferrer"&gt;methodology page&lt;/a&gt; documents every factor.&lt;/p&gt;

&lt;p&gt;For developers who want to drop this into an existing project instead of copying the functions above, the same math is packaged as &lt;a href="https://www.npmjs.com/package/epoxy-planner" rel="noopener noreferrer"&gt;epoxy-planner on npm&lt;/a&gt; — &lt;code&gt;npm install epoxy-planner&lt;/code&gt; — with a stable surface that exposes the shape volumes, unit conversion table, ratio splitter, and lift scheduler as plain functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;An epoxy calculator is small on the surface and surprisingly deep under it: elementary geometry for the pour, exact and approximate constants for unit conversion, an additive waste/seepage model, proportional split arithmetic for Part A/B, and a ceiling division to turn a deep pour into schedulable lifts. The three functions in this post — &lt;code&gt;volume&lt;/code&gt;, &lt;code&gt;convertVolume&lt;/code&gt;, and &lt;code&gt;splitByRatio&lt;/code&gt; — cover the core. Try them on your next pour, or grab the npm package if you'd rather not re-derive the constants yourself.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>math</category>
      <category>diy</category>
    </item>
    <item>
      <title>How to Calculate a Lottery Annuity Payout in JavaScript (30 Payments, 5% Growth, Taxes)</title>
      <dc:creator>Agoes Jayanata</dc:creator>
      <pubDate>Sun, 11 Oct 2026 02:58:53 +0000</pubDate>
      <link>https://dev.to/agoesjayanata/how-to-calculate-a-lottery-annuity-payout-in-javascript-30-payments-5-growth-taxes-4i4k</link>
      <guid>https://dev.to/agoesjayanata/how-to-calculate-a-lottery-annuity-payout-in-javascript-30-payments-5-growth-taxes-4i4k</guid>
      <description>&lt;p&gt;When a Powerball or Mega Millions jackpot hits the news, the headline number is the &lt;strong&gt;annuity total&lt;/strong&gt; - the advertised amount you'd receive if you don't take the cash. That total isn't a single check. Under today's rules the lottery pays it out as &lt;strong&gt;30 graduated installments&lt;/strong&gt;: one payment right away, then 29 more, each year, each exactly &lt;strong&gt;5% larger than the one before&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The cash option for the current jackpot is roughly 41% of the advertised amount, so a winner's choice between the two isn't obvious - it depends on discount rates, taxes, and how the payments are scheduled. Below I'll walk through the math, write a short JavaScript function that produces the full 30-payment schedule from an advertised jackpot and applies a tax rate, and flag the tax details you'd get wrong by guessing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-payment schedule
&lt;/h2&gt;

&lt;p&gt;The annuity is a growing-annuity. If the advertised jackpot is &lt;code&gt;J&lt;/code&gt; and the annual growth rate is &lt;code&gt;g = 5%&lt;/code&gt;, the first payment &lt;code&gt;P₁&lt;/code&gt; is chosen so that the 30 payments sum to &lt;code&gt;J&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P₁ + P₁-(1+g) + P₁-(1+g)² + ... + P₁-(1+g)²⁹ = J
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a geometric series, so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;P₁ - ((1+g)³⁰ - 1) / g  =  J
→  P₁ = J - g / ((1+g)³⁰ - 1)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;g = 0.05&lt;/code&gt; the divisor &lt;code&gt;(1.05³⁰ - 1)&lt;/code&gt; is about 56.5, so &lt;code&gt;P₁&lt;/code&gt; is roughly &lt;code&gt;J × 0.05 / 56.5 ≈ 1.505%&lt;/code&gt; of the jackpot. Every later payment is just the previous one multiplied by 1.05. For the current $516 M Powerball jackpot the first payment works out to about $7,766,541 and the final one to about $31,968,134 (before tax), and the 30 payments add back to the full $516 M. You can verify the numbers for any current jackpot on the &lt;a href="https://jackpotcalculator.com/powerball-annuity-calculator/" rel="noopener noreferrer"&gt;Powerball annuity calculator&lt;/a&gt;, which also converts the annuity into today's dollars at a discount rate you pick (4% by default) and shows the break-even return against the cash option.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taxes - withholding isn't the final answer
&lt;/h2&gt;

&lt;p&gt;Lottery winnings are ordinary income. The tricky part for most winners is the difference between what's withheld and what they actually owe.&lt;/p&gt;

&lt;p&gt;When a prize exceeds $5,000 (after subtracting the ticket price), the lottery withholds a flat &lt;strong&gt;24% for federal income tax&lt;/strong&gt; at claim - that's the rate on the Form W-2G. Withholding is a prepayment, not the final tax. A large prize gets taxed at the ordinary-income brackets that go up to &lt;strong&gt;37%&lt;/strong&gt;. In 2026 the 37% top bracket kicks in at $640,600 of taxable income for a single filer (per IRS Rev. Proc. 2025-32), so most of a jackpot - and most of each large annuity payment - is taxed at 37% rather than the 24% already sent to the IRS. The balance, plus any state tax not fully withheld, is due when you file.&lt;/p&gt;

&lt;p&gt;The annuity's main tax difference versus the cash is &lt;strong&gt;timing&lt;/strong&gt;, not rate. A lump sum is taxed all at once in the year you claim it; annuity payments are taxed as they arrive, one year at a time. State tax comes on top in most states. The top state rates reach about 10.9% (New York) with New York City adding up to 3.876%, while Florida, Texas, Tennessee, Washington and a few others have no state income tax on winnings. The full &lt;a href="https://jackpotcalculator.com/lottery-taxes/" rel="noopener noreferrer"&gt;lottery tax breakdown&lt;/a&gt; walks through the 2026 federal and state rules, the per-state withholding rates, and what happens when a prize is shared.&lt;/p&gt;

&lt;p&gt;For a back-of-the-envelope estimate you can plug a single flat tax rate into the annuity code below. For anything close to real numbers, tax each payment in its own year and layer state tax the way the linked calculators do.&lt;/p&gt;

&lt;h2&gt;
  
  
  JavaScript implementation
&lt;/h2&gt;

&lt;p&gt;Here's a compact function that returns the full 30-payment schedule and applies a flat tax rate. It's the same arithmetic used by the &lt;code&gt;jackpot-annuity&lt;/code&gt; npm package (open source, by the author of this post).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Compute a 30-payment lottery annuity schedule.
 *
 * @param {number} advertisedJackpot - The headline annuity total (e.g. 516_000_000).
 * @param {number} [growthRate=0.05]  - Year-over-year growth of each payment (5%).
 * @param {number} [payments=30]      - Number of payments (Powerball / Mega Millions default).
 * @param {number} [taxRate=0.24]     - Flat tax rate to apply to each payment (withholding).
 * @returns {{payments: Array&amp;lt;{year: number, gross: number, tax: number, net: number}&amp;gt;,
 *            totals: {gross: number, tax: number, net: number}}
 */&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;computeAnnuity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;advertisedJackpot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;growthRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;factor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;growthRate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;growthRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstPayment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;advertisedJackpot&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;firstPayment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schedule&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;grossTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;taxTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;netTotal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;gross&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;grossTotal&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;payment&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;taxTotal&lt;/span&gt;   &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;netTotal&lt;/span&gt;   &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;net&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;payment&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;growthRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// next year grows by 5%&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;gross&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;grossTotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;tax&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;taxTotal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;net&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;netTotal&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example: current $516M Powerball jackpot, flat 24% withholding&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;computeAnnuity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;516&lt;/span&gt;&lt;span class="nx"&gt;_000_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Year 1 gross: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;gross&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Year 30 gross:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;payments&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;gross&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;30-year gross total:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;totals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;gross&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;advertisedJackpot = 516_000_000&lt;/code&gt; and the defaults, year 1 comes out to ~$7,766,541 gross and the 30-year gross total sums back to $516,000,000 - matching the published annuity. Calling it with &lt;code&gt;taxRate = 0.37&lt;/code&gt; shows the closer-to-real federal take on each payment for a single filer whose prize lands in the top bracket.&lt;/p&gt;

&lt;p&gt;If you want a ready-made version, &lt;code&gt;npm install jackpot-annuity&lt;/code&gt; pulls the open-source package by the author of this post; it exposes the same &lt;code&gt;computeAnnuity&lt;/code&gt; logic, plus utilities for discounting the schedule to present value and comparing it against the published cash option.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the flat rate lies to you
&lt;/h2&gt;

&lt;p&gt;The 24% / 37% gap is the most common mistake. On the current $516 M jackpot taken as cash, about $51 M is withheld at 24% and the winner receives roughly $161.6 M on the daon-srteaalll  fjeadcekrpaolt -taankneu iotny &lt;code&gt;e aacnhd  pcaaylmle n&lt;/code&gt;tj afcokrp oat Asninnugiltey (fjialcekrp owth,o s{e  gprroiwzteh :l a0n.d0s5 ,i nt atxhRea tteo:p  0b.r2a4c k}e)t`. &lt;/p&gt;

&lt;p&gt;tIof  gyeotu  bwaacnkt  tah er eaardrya-ym aodfe  3v0e rgsriaodnu,a t&lt;code&gt;endp mp aiynmsetnatlsl  wjiatchk pgorto-sasn,n utiatxy,&lt;/code&gt;  apnudl lnse tt hfei eolpdesn -psroeucrocmep uptaecdk.a&lt;/p&gt;

&lt;h1&gt;
  
  
  g#e  Wbiyt hthhoel daiuntgh oirs  oaf  ptrheipsa ypmoesntt;,  into te xtphoes efsi ntahle  tsaaxm
&lt;/h1&gt;

&lt;p&gt;eT h&lt;code&gt;ec ofmlpautt e2A4n%n uiint yt&lt;/code&gt;h el ocgoidce,  apblouvse  umtaitlcihteise st hfeo rf eddiesrcaolu n*t*iwnigt hthhoel dsicnhge*d*u lteh attoy they claim - but the estimated federal tax is around $78.6 M, so another ~$27.6 M is due when they file. An annity winner faces the same gap on a smaller scale every year: 24% is taken off each check, and the rest of the tax (up to the 37% bracket) comes due at filing. State tax is layered on top in most states, and for a jackpot the timing difference between lump-sum and annuity is often the deciding factor, not a different rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The advertised jackpot equals the sum of &lt;strong&gt;30 payments&lt;/strong&gt;, each 5% larger than the last; &lt;code&gt;P₁ = J - 0.05 / (1.05³⁰ - 1)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Federal &lt;strong&gt;withholding is 24%&lt;/strong&gt;, but a large prize is ultimately taxed as ordinary income up to the &lt;strong&gt;37% top bracket&lt;/strong&gt; (over $640,600 single in 2026); the rest is due at filing.&lt;/li&gt;
&lt;li&gt;Annuity payments are taxed &lt;strong&gt;year by year&lt;/strong&gt;; state rates add on top and vary widely.&lt;/li&gt;
&lt;li&gt;The snippet above (and the &lt;code&gt;jackpot-annuity&lt;/code&gt; npm package) reproduces the schedule from the advertised jackpot and applies a chosen tax rate so you can compare annuity vs. cash yourself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;*Estimates only - not tax or financial advice. For payment-by-payment tax in every state, use the linked calculators.*0r0t,s0 0t0h e-  sma&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
