Here's what I think I know. My impression is that I've "known" it for such a long time, I can't recall whether I "learned" it from documentation, Wolfram developers, or my own experimentation. Some of it is probably slightly inaccurate, but I doubt any of it is far off.
Arbitrary precision uses a variable-length array to represent the mantissa, probably an array of unsigned ints in a base 2^64 representation. I don't recall if I know anything about the exponent other than what is implied by $MaxNumber
. The precision seems to be kept as a machine real. It is meant to be a bound on the error ("uncertainty" in the docs). It is approximate, but it's unlikely to be too small.
The mantissa is longer than necessary, using the extra bits as guard bits to ensure actual round-off error is less than that implied by the rules of error propagation. The minimum length mantissa seems to be 64 bits, which is longer than the length of a double-precision machine real, which is 53 bits.
The length of the mantissa is not a function of the precision only. The mantissa is extended and truncated by the internal algorithm according to its rules, whatever they are. Also, when N[]
creates an arbitrary precision number, the length of the mantissa depends on the algorithm use to compute the number. These 100-digit approximations to Pi
have an internal difference:
ClearSystemCache[];
pi1 = N[Pi, 100];
pi2 = N[N[Pi, 1000], 100];
ByteCount /@ {pi1, pi2}
pi1 - pi2
SetPrecision[pi1, 120] - SetPrecision[pi2, 120]
(*
{120, 128}
0.*10^-100
5.91530794*10^-111
*)
N[]
caches results. If we recompute, we get a different result for N[Pi, 100]
, the same as we get for pi2
.
pi3 = N[Pi, 100];
pi4 = N[N[Pi, 1000], 100];
ByteCount /@ {pi3, pi4}
SetPrecision[pi3, 200] - SetPrecision[pi4, 200]
(*
{128, 128}
0.*10^-200
*)
One can see where the mantissa is extended, assuming ByteCount
is an accurate indirect measure. We can also see that where the mantissa is extended depends on which number N[]
is approximating:
ListLinePlot[{
Table[ByteCount@N[Sqrt[2], wp], {wp, 100}],
Table[ByteCount@N[2, wp], {wp, 100}],
Table[ByteCount@N[E, wp], {wp, 100}],
Table[ByteCount@N[Pi, wp], {wp, 100}]},
PlotStyle -> Reverse@Table[AbsoluteThickness[2 t], {t, 4}],
GridLines -> {10 Range[12], None},
PlotLegends -> {Sqrt[2], 2, E, Pi},
AxesLabel -> {"Precision", "Bytes"}]
We see that the smallest data structure to keep track of the mantissa, exponent, precision and whatever else is 80 bytes, much bigger than an 8-byte machine real. As others have mentioned, bignums are computed in software and machine numbers in hardware, which accounts for the principal difference in speed.
You can speed up arbitrary precision computation by setting $MaxPrecision = $MinPrecision
. I think this turns off precision tracking.
I've never understood why people use the setting WorkingPrecision -> 10
(slower, pretends less precision: lose-lose, no?) and often comment when I see it on site. It occurs in the docs, imho, because it produces output that is short and readable, not because it's a good value to use. It indirectly affects PrecisionGoal
and AccuracyGoal
, but if that's one's purpose, they should be set explicitly, not indirectly.
But at the risk of propagating a bad practice:
data = N[Range@100000, 10];
Total@data; // RepeatedTiming
Block[{$MaxPrecision, $MinPrecision},
$MaxPrecision = $MinPrecision = Precision@data;
(Total@data; // RepeatedTiming)
]
(*
{0.0136405, Null}
{0.00927122, Null}
*)