Resources Numerical accuracy Calculation policy

Sun position for shadows

The sun's altitude and azimuth for a date, clock time, latitude, longitude and time zone, and the direction shadows are cast from.

How the precision is kept

The arithmetic is 64-bit, but the accuracy is that of the formulas: the short Fourier series for the equation of time and the declination that the file header cites from the NOAA Global Monitoring Laboratory's General Solar Position Calculations. The header states the result is good to a fraction of a degree and that refraction is ignored.

Calculations under this policy

5 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.

m3d/sun.js CadCraft 0.8.2 64-bit 5 calculations Reviewed

Tests None found by the review

Day of the year

Exact

The day number of a date.

N = days before the month + day (+1 after February in a leap year)

Whole-number arithmetic with the Gregorian leap rule (divisible by 4, not by 100 unless by 400).

  • m3d/sun.js
  • lines 17 to 21
  • CadCraft 0.8.2
export function dayOfYear(month, day, year = 2026) {
  const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  const cum = [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334];
  return cum[month - 1] + day + (leap && month > 2 ? 1 : 0);
}

Equation of time and declination

64-bit

How far sun time runs ahead of clock time, and the sun's angle above the equator, for the date.

γ = 2π ÷ (365 or 366) × (N − 1 + (hour − 12) ÷ 24); eqtime = 229.18 × (0.000075 + 0.001868 cos γ − 0.032077 sin γ − 0.014615 cos 2γ − 0.040849 sin 2γ)

Two short trigonometric series with coefficients given to 6 decimals; 229.18 turns radians into minutes. The series, not the arithmetic, set the accuracy.

  • m3d/sun.js
  • lines 26 to 29
  • CadCraft 0.8.2
const g = (2 * Math.PI / (leap ? 366 : 365)) * (N - 1 + (hour - 12) / 24);
const eqtime = 229.18 * (0.000075 + 0.001868 * Math.cos(g) - 0.032077 * Math.sin(g) - 0.014615 * Math.cos(2 * g) - 0.040849 * Math.sin(2 * g));
const decl = 0.006918 - 0.399912 * Math.cos(g) + 0.070257 * Math.sin(g) - 0.006758 * Math.cos(2 * g) + 0.000907 * Math.sin(2 * g)
  - 0.002697 * Math.cos(3 * g) + 0.00148 * Math.sin(3 * g);

Hour angle and altitude

64-bit

The sun's height above the horizon.

tst = hour × 60 + eqtime + 4 × lon − 60 × tz; H = tst ÷ 4 − 180°; cos Z = sin φ sin δ + cos φ cos δ cos H; altitude = 90° − Z

cos Z is clamped to [−1, 1] before acos. When the sun is almost overhead cos Z is close to 1, where acos turns a small error in its input into a larger one in the angle.

  • m3d/sun.js
  • lines 30 to 36
  • CadCraft 0.8.2
const offset = eqtime + 4 * lon - 60 * tz;                // minutes
const tst = hour * 60 + offset;                           // true solar time, minutes
const ha = (tst / 4 - 180) * RAD;                         // hour angle
const phi = lat * RAD;
const cosZ = Math.sin(phi) * Math.sin(decl) + Math.cos(phi) * Math.cos(decl) * Math.cos(ha);
const zen = Math.acos(Math.max(-1, Math.min(1, cosZ)));
const altitude = 90 - zen / RAD;

Azimuth

64-bit

The sun's compass direction, in degrees clockwise from north.

azimuth = atan2(sin H, cos H sin φ − tan δ cos φ) + 180°, wrapped to 0…360

atan2 needs no clamp and keeps its digits in every quadrant. The result is wrapped with two remainders so it is never negative.

  • m3d/sun.js
  • lines 37 to 39
  • CadCraft 0.8.2
// azimuth measured from north, clockwise
const az = Math.atan2(Math.sin(ha), Math.cos(ha) * Math.sin(phi) - Math.tan(decl) * Math.cos(phi)) / RAD + 180;
const azimuth = ((az % 360) + 360) % 360;

Direction toward the sun

64-bit

The unit vector shadows are cast from, in model axes (north is +Y, east is +X).

dir = (sin a cos alt, cos a cos alt, sin alt)

Built from the altitude and azimuth after they were converted to degrees and back to radians, which adds two roundings to each angle.

  • m3d/sun.js
  • lines 40 to 43
  • CadCraft 0.8.2
const alt = altitude * RAD, a = azimuth * RAD;
return {
  altitude, azimuth, declination: decl / RAD, eqtime,
  dir: [Math.sin(a) * Math.cos(alt), Math.cos(a) * Math.cos(alt), Math.sin(alt)],

In detail

Method
Fractional year γ; equation of time and declination from Spencer's Fourier series (as cited in the file header); true solar time from longitude and time zone; zenith from the spherical cosine formula; azimuth from atan2.
Number types
64-bit numbers; angles in radians inside, degrees in the result.
Tolerances
cos of the zenith angle is clamped to [−1, 1] before acos.
Limits
Series coefficients are the published rounded decimals. No atmospheric refraction. The year only decides 365 or 366 days.

Approach: 64-bit, stated limit

The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.

Every policy that takes this approach