Removing the precision from double
Neia Neutuladh
neia at ikeran.org
Fri Nov 2 01:00:26 UTC 2018
On Thu, 01 Nov 2018 23:59:26 +0000, kerdemdemir wrote:
> I am doing trading and super scared of suprices like mathematical errors
> during the multiplications(or division 1/tickSize) since market will
> reject my orders even if there is a small mistake.
>
> Is this safe? Or is there a better way of doing that ?
tldr: consider using std.experimental.checkedint and highly granular units,
like milli-cents. Also consider using a rational number library. Doubles
aren't necessarily bad, but they'd concern me.
# 1. Fixed precision
The common way of working with sensitive financial stuff is fixed
precision numbers. With fixed precision, if you can represent 0.0001 cents
and you can represent ten billion dollars, you can represent ten billion
dollars minus 0.0001 cents. This is not the case with single-precision
floating point numbers:
writeln(0.0001f);
writefln("%20f", 10_000_000_000f);
writefln("%20f", 10_000_000_000f - 0.0001f);
prints:
0.0001
10000000000.000000
10000000000.000000
The problem with fixed precision is that you have to deal with overflow
and underflow. Let's say you pick your unit as a milli-cent (0.001 cents,
$0.00001).
This works okay...but if you're trying to assign a value to each CPU
cycle, you'll run into integer underflow. Like $10/hour divided by CPU
cycles per hour is going to be under 0.001 cents. And if you try inverting
things to get cycles per dollar, you might end up with absurdly large
numbers in the middle of your computation, and they might not fit into a
single 64-bit integer.
## Fixed precision with Checked
std.experimental.checkedint solves the overflow problem by turning it into
an error. Or a warning, if you choose. It turns incorrect code from a
silent error into a crash, which is a lot nicer than quietly turning a
sale into a purchase.
It doesn't solve the underflow problem. If you try dividing $100 by 37,
you'll get 270270 milli-cents, losing a bit over a quarter milli-cent, and
you won't be notified. If that's deep in the middle of a complex
calculation, errors like that can add up. In practice, adding six decimal
places over what you actually need will probably be good enough...but
you'll never know for certain.
# Floating point numbers
Floating point numbers fix the underflow and overflow problems by being
approximate. You can get numbers up to 10^38 on a 32-bit float, but that
really represents a large range of numbers near 10^38.
**That said**, a double can precisely represent any integer up to about
2^53. So if you were going to use milli-cents and never needed to
represent a value over one quadrillion, you could just use doubles. It
would probably be easier. The problem is that you won't know when you go
outside that range and start getting approximate values.
# Rational numbers
A rational number can precisely represent the ratio between two integers.
If you're using rational numbers to represent dollars, you can represent a
third of a dollar exactly. $100 / 37 is stored as 100/37, not as something
close to, but not exactly equal to, 2.702 repeating.
You can still get overflow, so you'll want to use
std.experimental.checkedint as the numerator and denominator type for the
rational number. Also, every bit of precision you add to the denominator
sacrifices a bit of range. So a Rational!(Checked!(long, Throw)) can
precisely represent 1/(2^63), but it can't add that number to 10. Manual
rounding can help there.
More information about the Digitalmars-d-learn
mailing list