ADouble, the number that remembers
What ADouble actually is under the hood, why it looks exactly like a double from the outside, and why two of them can refuse to work together.
A double forgets the instant it's computed. x.add(y) runs the addition,
hands you back a number, and by the time you look at it, x, y, and the
add itself are gone — there's no way to ask a double how it got its
value. What if a number didn't forget?
The whole story
An ADouble never stores the number it represents — not even the one it was
created with. Its entire field list is a reference to the recorder that made
it and one int: which node on the tape it is. rec.input("S0", 100.0)
records 100.0 as that node's default replay value, but the handle you get
back, the ADouble itself, carries no number at all until something actually
replays the tape.
A double computes; an ADouble records
Valuation code written against ADouble looks identical to code written
against double — that's deliberate, it's the whole reason a payoff is a
"seam" you can swap in three lines. But the two types do opposite things when
you call the same method:
double x = 100.0;
double y = x + 37.0; // computes 137.0 right now, on the spot
ADouble x = rec.input("S0", 100.0);
ADouble y = x.add(37.0); // appends an ADD node to the tape, returns a
// handle to it — no arithmetic happens yet
ADouble.add, straight from the source, doesn't touch a number at all — it
asks the recorder for a new node and wraps the result:
public ADouble add(ADouble other) {
return binary(AadOp.ADD, other);
}
private ADouble binary(AadOp op, ADouble other) {
if (other.recorder != recorder) {
throw new IllegalArgumentException("operands come from different recordings");
}
return recorder.node(op, node, other.node);
}
sub, mul, div, exp, log, sqrt, abs, max, and min all follow
the same shape — every one of them is binary(...) or unary(...) under the
hood, appending exactly one node.
That other.recorder != recorder check is the whole reason you can't
accidentally mix up two valuations. Every AadRecorder.record(...) call
starts a fresh recording with its own tape; an ADouble from one recording
carries a reference back to the recorder that made it, and combining it with
one from a different recording throws immediately — a plain IllegalArgumentException
at the exact line that mixed them, not a silently wrong number three modules
later.
Try it yourself
Open ADouble.java
and sort its methods into two piles: the ones that take another ADouble
(binary, under the hood) and the ones that take a plain double (add(double),
sub(double), and so on). Now guess what the second pile actually does before
you scroll down — hint: an ADouble and a raw double can't share a
recorder, so something has to turn that double into an ADouble first.
(It calls recorder.constant(value), the same method that made a constant
node for the earlier example.)
▶️ Run it
There's nothing to run yet — an ADouble on its own is just a handle to one
node. Module 1.2 is where a handful of these turn into a whole tape you can
actually replay.
⚠️ What this doesn't do
This page is the whole ADouble type, and it's small on purpose: eleven
arithmetic operations (add, sub, mul, div, neg, exp, log,
sqrt, abs, max, min — a few doubled up to also take a plain
double), no trig, no comparisons beyond max/min. It doesn't show how a
sequence of these becomes an AadTape,
how that tape gets replayed, or how a reverse sweep turns a tape into
Greeks — that's the next two pages. And a single ADouble can't tell you
anything about a market, a payoff, or a bank; it's a building block, not a
pricer.
What's next
→ Deeper: Adjoint AD for dummies
walks the same (x+2)*x idea all the way through to a hand-computed reverse
sweep, node by node.
→ Next: Recording a tape, turning a few
ADouble operations into something you can actually replay.