DEV Community

kaiyilu
kaiyilu

Posted on

Solving Carpenter 1/16" Tape Rounding and IES Lighting Grids in Vanilla JS

Floating-point math in JavaScript is straightforward until your output has to survive a real jobsite tape measure or a ceiling full of floor joists and garage door tracks.

While building two browser-based layout calculators for residential framing and ceiling lighting, I ran into three geometry problems where naive total / count math quietly fails in the real world. Here is how the underlying math works and how I implemented it in zero-dependency JavaScript.

1. Why "Step-Off" Rounding Ruins Railing Layouts

Under the 2024 International Residential Code (IRC R312.1.3), open guardrail balusters cannot allow a 4-inch sphere to pass through.

Suppose you have a 92" clear inside span between two deck posts using nominal 2x2 wood spindles (1.5" actual width):

export function solveLevelBalusters(clearSpanIn, balusterWidthIn = 1.5, maxClearGapIn = 4.0) {
  const minCount = Math.max(
    1,
    Math.floor((clearSpanIn - maxClearGapIn) / (balusterWidthIn + maxClearGapIn)) + 1
  );

  // If landing right on 4.000", add one spindle for lumber shrinkage margin
  let count = minCount;
  let exactGap = (clearSpanIn - count * balusterWidthIn) / (count + 1);
  if (exactGap >= maxClearGapIn - 0.02) {
    count += 1;
    exactGap = (clearSpanIn - count * balusterWidthIn) / (count + 1);
  }

  return { count, exactGap, onCenter: exactGap + balusterWidthIn };
}
Enter fullscreen mode Exit fullscreen mode

For 92", 18 spindles give 19 equal gaps of 3.421" (4.921" on-center).

If you convert 4.921" to the nearest 1/16" (4-15/16", which is 4.9375") and tell a carpenter to step off a 4-15/16" spacer block 18 times down the rail, that tiny +0.0164" rounding error compounds across every spindle. By the last post, the final gap shrinks by nearly 5/16".

The fix is simple but critical: always compute cumulative positions in full floating-point from 0" (hooked on the inside face of Post A), and only snap to 1/16" at the display step:

export function formatSixteenths(inches) {
  const totalSixteenths = Math.round(inches * 16);
  const whole = Math.floor(totalSixteenths / 16);
  let num = totalSixteenths % 16;
  if (num === 0) return `${whole}"`;

  let den = 16;
  while (num % 2 === 0 && den > 1) {
    num /= 2;
    den /= 2;
  }
  return whole > 0 ? `${whole}-${num}/${den}"` : `${num}/${den}"`;
}
Enter fullscreen mode Exit fullscreen mode

2. Stair Rake Projection (cos θ Trap)

Stair railings introduce a second trap. Plumb balusters stand vertically, which means the horizontal clear gap G is governed by level geometry, while carpenters hook their tape diagonally along the sloped bottom rail.

Given stair unit rise R and unit run T (for example, 7.5" rise over 10.5" run):

  1. Rake angle: θ = atan2(R, T) (≈ 35.54°)
  2. Horizontal span from sloped rail length Ls: Lh = Ls * cos(θ)
  3. Diagonal on-center step along the rail: S_sloped = (G + W) / cos(θ)

If someone marks 3-3/4" gaps directly along the 35.5° rail instead of projecting by cos(θ), the actual horizontal gap drops to 3.05", wasting spindles and crowding the stair run. And don't forget the triangular throat formed by the tread, riser, and bottom rail, where IRC R312.1.3 Exception 1 permits a 6-inch sphere (d = a + b - c incircle check on a right triangle).

I put the interactive 2D SVG/Canvas version of this cumulative tape schedule live at Baluster Spacing Calculator so you can test level runs, stair rake angles, and inline fence gate bays directly in the browser.

3. IES Zonal Cavity Lumen Method & Garage Door Shadows

On the electrical side, most "divide ceiling height by 2" rules of thumb fail in two rooms: low 7.5 ft basements (where wide-angle pancake wafers cause harsh glare) and high-bay garages (where open overhead garage doors block the front row of shop lights).

Instead of guessing fixture counts from raw floor square footage alone, the IES Zonal Cavity equation accounts for how much light is absorbed by walls above the 30" workplane:

export function solveZonalCavity({
  lengthFt,
  widthFt,
  ceilingHeightFt,
  workplaneFt = 2.5,
  targetFootcandles = 75,
  lumensPerFixture = 4500,
  llf = 0.85,
}) {
  const areaSqFt = lengthFt * widthFt;
  const hrc = Math.max(1, ceilingHeightFt - workplaneFt);

  // Room Cavity Ratio: tall narrow rooms lose more light to walls
  const rcr = (5 * hrc * (lengthFt + widthFt)) / areaSqFt;
  const cu = Math.max(0.42, Math.min(0.88, 0.88 - 0.066 * rcr));

  const rawFixtures = (areaSqFt * targetFootcandles) / (lumensPerFixture * cu * llf);
  return { areaSqFt, rcr: Number(rcr.toFixed(2)), cu: Number(cu.toFixed(3)), minFixtures: Math.ceil(rawFixtures) };
}
Enter fullscreen mode Exit fullscreen mode

In a 24x24 workshop with a 10 ft ceiling aiming for 75 fc at bench height (RCR = 2.92, CU = 0.688), bare lumen division (43,200 lm / 4,500 lm = 10 lights) under-lights the bench by nearly 30%. Once cavity loss and light loss factors are included, you actually need 14 fixtures, and shifting the front row clear of the rolled-up overhead door tracks prevents dark hood shadows. You can test the live photometric heatmap and door-track clearance solver at Lighting Layout Calculator.

Open-Source Reference Equations

If you need the standalone ESM formulas for your own home-improvement scripts or CAD macros without pulling in UI dependencies, I published the reference implementations under MIT in kaiyilu/building-layout-math.

Top comments (0)