math is the comfortable middle layer of Shoddy's number work.
The runtime — the engine that runs your program — already hands you the hard
primitives, the words that need real numerical precision baked in:
Sin, Cos, Sqr (square root),
Exp, Log, Pi, and the arithmetic and
rounding words. This machine does not reimplement any of those. It sits on top
of them and adds the everyday conveniences you would otherwise write out by
hand every time: the constant E, a whole Tau instead
of two Pis, degrees-to-radians (radians are the angle unit the trig words use
— a full circle is 2×Pi, not 360), clamping and interpolation (pinning a
value inside bounds, and sliding smoothly between two values), a distance
formula, base-2 logs. Each one is a nice, small, obvious thing — a line or two
over a builtin, a word the engine itself supplies, that you already had. It
includes nothing else, so a program can pull in math on its
own.
A Brief History of the Logarithm
The most useful trick in this machine's territory was worked out by two
men. One of them was born a few miles from the mills this language is named
for. John Napier published the logarithm in 1614: a table that turned
multiplication, the slow operation, into addition, the fast one. Henry Briggs
— born at Warley Wood in the parish of Halifax, in the West Riding
— read it and travelled to Edinburgh to meet Napier. Briggs talked him
into the one improvement that made the idea universal: put the tables on base
10, so the whole-number part of a logarithm is just how many digits the
number has. Briggs then computed tens of thousands of the entries himself, by
hand, to fourteen places. For the next three and a half centuries, science
and engineering ran on his tables and their pocket form, the slide rule. The
runtime hands Shoddy Log. This machine adds Log2
and LogBase in Briggs's spirit: the base you compute in and the
base you think in need not be the same one.
Why It's Useful
Real programs are forever needing the same little scraps of arithmetic.
You want to keep a value inside sensible bounds (Clamp). You want
to slide smoothly from one number to another — a fade, a camera move, a
progress bar (Lerp). You have an angle in degrees but the trig
builtins speak radians (Rad). You want the distance between two
points (Dist), or a random number in a range, or a log in some
base that isn't 10 or e. None of these is hard. But writing them out
inline every time is tedious and easy to fumble — is it lo + (hi - lo)
* t or the other way round? math gives each a plain name
you can trust, so the arithmetic reads like what it means. Reach for it any
time you're doing more than plus and minus: graphics, animation, geometry,
simulation, or just tidying a raw number into a rounded, bounded one.
User's Guide
Everything here is a plain function over numbers. Pass numbers in, get a
number back — or, for Chance, a Boolean, a true-or-false value.
There is nothing to open or set up. Include the file and call the words. The
one wrinkle to know is a rule the machine sets for itself: no word
here may share a name with a builtin. In Shoddy a Def
(a word you define yourself) silently outranks a builtin of the same name.
Redefining, say, Log would therefore quietly hijack it
everywhere — a nasty surprise. That is why the natural log stays the
Log builtin and the base-10 log stays Log10. This
file adds only the ones the runtime doesn't already provide:
Log2 and LogBase.
Include "math.shoddy"
Def Main()
Print(Tau()) ' 6.283... — a full turn, in radians
Print(Rad(180)) ' 3.14159... — 180 degrees as radians
Print(Clamp(42, 0, 10)) ' 10 — pinned to the top of the range
Print(Lerp(0, 100, 0.25)) ' 25 — a quarter of the way from 0 to 100
Print(RoundTo(3.14159, 2)) ' 3.14
Print(Dist(0, 0, 3, 4)) ' 5 — Pythagoras, done for you
Print(Log2(8)) ' 3
If Chance(0.5) Then ' heads-or-tails, true half the time
Print("heads")
Else
Print("tails")
A few things worth remembering:
Angles: builtins speak radians, this machine bridges to
degrees.Rad and Deg convert between the
two. Angle(dx, dy) gives the direction of a vector — an arrow
from one point to another — as a radian angle. It takes its arguments in the
natural (dx, dy) order, wrapping the Atn2 builtin,
which wants them the other way round.
The interpolation family fits together.Lerp
slides from a to b by a fraction t.
InvLerp is its exact opposite: it tells you what fraction a value
sits at. Remap chains the two to move a number from one range
onto another. Smoothstep is the eased version, clamped at the
edges for gentle fades.
There's a small random cluster here too.RandRange, RandInt, Pick, and
Chance are built on the Rnd builtin, and the
Seed builtin makes any of them reproducible. Sometimes random
numbers are the main thing you want — deliberately seedless, treated as an
effect at the edge of your program. In that case, see the dedicated
random machine instead; the words here are the
convenience versions for when you're already reaching for
math.
Fractions and truncation follow the builtins.Frac uses the Fix builtin, which truncates toward
zero — it drops the fractional part. So Frac(-3.25) is
-0.25, not 0.75. Keep that sign in mind for
negatives.
Builtins
The thirty numeric words the runtime dispatches — not
defined here, documented here
These are not math's Defs. The engine dispatches
them, and a Def whose name is a builtin is refused. They are
listed on this page because math is the machine whose domain
they belong to. Its own header has always named all thirty as what it is
built on, and this is that claim made word by word. They need no
Include. The same thirty are documented in
machines/math.shoddy's own header block.
Number is the only numeric type — a double,
the standard format computers use for decimal numbers. There is no integer
type, so "whole number" below means a double with no fractional part, exact
to 253. Angles are radians at every boundary;
Rad and Deg below convert. All thirty are pure —
the same inputs always give the same answer — except Seed.
Rnd is not here, though this machine calls it.RandRange, RandInt, Pick and
Chance are all Rnd underneath. But a builtin is
documented in one machine only, and Rnd's machine is
random, whose whole header is a claim on it.
Seedis here, and the split is deliberate:
random is seedless by construction, and says so in as many
words. The word that reseeds the generator would contradict the machine it
otherwise sits beside. It reseeds random's words just the
same.
Arithmetic
Word
Description
a + b, a - b, a * b, a / b
The four operators. +
also joins two Strings, which is why the reckoner's dictionary needs no
separate word for &.
a Mod b
The remainder. It takes its sign from a
— the truncated form, the same rule BASIC and C use. Wrap is the
one that does not.
Wrap(a, b)
Floored modulo: the answer takes
its sign from b, so Wrap(-1, 360) is 359 where
-1 Mod 360 is −1. This is the one for angles, ring buffers and
anything that comes round again.
a ^ b
a raised to the power b. It
binds tighter than any other operator.
Negate(x)
The number with its sign turned round.
Abs(x)
Its magnitude, sign discarded.
Sgn(x)
−1, 0 or 1, according to the sign.
Min(a, b) / Max(a, b)
The smaller and the larger.
Clamp below is both at once, and is usually what a bounded value
wants.
Sqr(x)
The square root. Named for BASIC's SQR and not
for squaring — squaring is x ^ 2.
Rounding — and there are four
The difference matters and is easy to get wrong. Floor and
Ceil each go one way, regardless of sign. Fix goes
towards zero, so it and Floor differ on every negative number.
Round goes to the nearest.
Word
Description
Floor(x)
Down, towards minus infinity.
Floor(-2.5) is −3.
Ceil(x)
Up, towards plus infinity. Ceil(-2.5) is
−2.
Round(x)
To the nearest whole number. RoundTo
below rounds to a given number of places, and str's
ToFixed formats.
Fix(x)
Towards zero — the fractional part simply dropped.
Fix(-2.5) is −2, where Floor(-2.5) is −3.
Exponential and logarithmic
Word
Description
Exp(x)
e raised to the power x.
Log(x)
The natural logarithm, base e.
The name is BASIC's, and it is the one people misread: base ten is
Log10.
Log10(x)
The base-ten logarithm. Log2 and
LogBase below are derived from these two, since the runtime
supplies no other base.
Trigonometry, in radians
Word
Description
Sin(x) / Cos(x) / Tan(x)
The three, taking radians.
Rad below converts from degrees.
Atn(x)
The arctangent, in radians. Atn2 is the one
that knows which quadrant the point is in.
Atn2(y, x)
The angle of the point (x, y), in
radians, in the right quadrant. Note the argument order: y first,
as in C's atan2.
Asin(x) / Acos(x)
The inverse sine and cosine, in radians.
Outside −1 … 1 there is no real answer.
Tanh(x)
The hyperbolic tangent. It saturates towards 1 and
−1, so it never overflows. That is why neural uses
it as its activation function. The other eleven hyperbolics are
eng's.
Pi()
The constant. Tau below is two of them, and
E is this file's rather than the runtime's.
The one impure word
Word
Description
Seed(n)
Reseeds the engine's random generator, so that
Rnd answers reproducibly from here on. That covers everything
built on it: random's Random,
RandomRange, RandomInt, Shuffle and
Sample, and this machine's own RandRange,
RandInt, Pick and Chance. It is the
only builtin on this page with an effect, and the reason the game-flavoured
randomness below is not pure.
Word Reference
Every word — all pure functions over numbers
Constants
Word
Description
E()
Euler's number, about 2.71828 — computed as
Exp(1). The base of the natural log.
Tau()
A full turn in radians, about 6.28318 —
2 × Pi. Often the friendlier circle constant when you're
thinking in whole turns.
Angles builtins speak radians
Word
Description
Rad(deg)
Converts an angle in degrees to radians, ready for the
trig builtins.
Deg(rad)
Converts an angle in radians back to degrees, for
display or turtle-style headings.
Angle(dx, dy)
The direction of a vector (dx, dy) as
a radian angle in the range (-Pi, Pi]. Argument order reads naturally, left to
right.
Parts and rounding
Word
Description
Frac(x)
The fractional part of x — what's left
after truncating toward zero. So Frac(-3.25) is
-0.25.
RoundTo(x, n)
Rounds x to n decimal
places.
Shaping a range
Word
Description
Clamp(x, lo, hi)
Pins x into the range
lo..hi: below lo becomes
lo, above hi becomes hi.
Lerp(a, b, t)
Linear interpolation: the point a fraction
t of the way from a to b.
t = 0 gives a, t = 1 gives
b.
InvLerp(a, b, x)
The inverse of Lerp: what fraction
of the way from a to b the value x sits
at.
Remap(x, inLo, inHi, outLo, outHi)
Moves x linearly
from the input range onto the output range — InvLerp then
Lerp, in one step.
Smoothstep(edge0, edge1, x)
Smooth (Hermite) interpolation from
edge0 to edge1, clamped to [0, 1] at the edges — an
eased ramp instead of a straight one.
Geometry
Word
Description
Hypot(x, y)
The length of the hypotenuse —
Sqr(x² + y²) — i.e. the distance from the origin to the
point (x, y).
Dist(x0, y0, x1, y1)
The straight-line distance between two
points.
Logarithms natural and base-10 are builtins
Word
Description
Log2(x)
The base-2 logarithm of x.
LogBase(base, x)
The logarithm of x in any
base you name.
Random on the Rnd builtin; Seed reproduces
Word
Description
RandRange(lo, hi)
A random floating-point number in
[lo, hi) — lo possible, hi never
quite.
RandInt(lo, hi)
A random whole number in [lo, hi],
both ends included.
Pick(xs)
A uniformly-chosen element of a non-empty list or
array.
Chance(p)
True with probability p:
p ≤ 0 never, p ≥ 1 always. A weighted
coin.
Rad and Deg at the degrees boundary, Clamp guarding every Asin and Sqr against a rounding error past ±1, Hypot, and RoundTo for the seconds of an angle.