A Java library that abstracts the mathematical operations on real decimal numbers represented in computer memory as
floating-point binary numbers
(Double) or arbitrary-precision
decimal numbers
(BigDecimal).
For build.gradle.kts:
implementation("trade.invision", "num", "1.11.0")For build.gradle:
implementation group: 'trade.invision', name: 'num', version: '1.11.0'For pom.xml:
<dependency>
<groupId>trade.invision</groupId>
<artifactId>num</artifactId>
<version>1.11.0</version>
</dependency>IEEE 754 floating-point binary numbers
cannot accurately represent
non-integer real decimal numbers in computer memory and FPUs. In
applications that require accurate mathematical operations with non-integer (fractional) decimal numbers, such as
finance programs involving money transactions and currency, using
arbitrary-precision decimal numbers is best practice.
Using floating-point numbers (e.g. float or double) to represent currency can lead to undesirable
inequality calculations,
rounding errors, and
precision loss. Thankfully, Java
provides the BigDecimal
class for working with arbitrary-precision decimal numbers, though it has some
quirks
and doesn't provide many of the functions that the
Math class has for float and
double types. Additionally, mathematical operations on BigDecimal objects can be
significantly slower than the equivalent
mathematical operations on floating-point numbers. This can be problematic in applications such as quantitative finance
and algorithmic trading programs where fast execution time of mathematical operations on currency values is more
desirable and the tradeoff of worse accuracy and lower precision is worth it. Using integer types as a
fixed-point number to represent a currency's minor unit is
another approach, but it can be unintuitive to work with and is still subject to
precision loss. The side effects of using floating-point numbers for
currency may be negligible in certain contexts, such as working with relatively low precision numbers (like stock
prices) or measurements (like technical indicators). So, using floating-point numbers for currency values isn't always
a bad idea. Even Microsoft Excel, a program widely used in finance,
uses floating-point numbers.
You can really go back-and-forth on the tradeoffs between floating-point, fixed-point, and arbitrary-precision numbers.
Enter, the Num interface: an intuitive interface that allows you to focus on using currency values in your application
without worrying about the underlying number representation and easily switch between various number representations
at runtime.
Num, short for "number", is an interface for performing mathematical
operations on real decimal numbers. Implementations wrap a
Number instance so that
performing mathematical operations on floating-point binary numbers
(Double via
DoubleNum) or arbitrary-precision decimal numbers
(BigDecimal via
DecimalNum) is simple. Object instances of this interface are
immutable. All methods in this interface return non-null values or throw a RuntimeException (usually an
ArithmeticException).
All implementations of this interface are interoperable with each other. Operations involving different
implementations will result in a Num that trends towards an increase in precision. For example, subtracting a
DecimalNum from a DoubleNum will result in a DecimalNum. For another example, subtracting a DecimalNum with a
context precision of 16 from a DecimalNum with a context precision of 32 will result in a DecimalNum with a
context precision of 32. Mathematical operations that result in NaN, +Infinity, -Infinity, or
ArithmeticException will yield NaNNum.
To create a DoubleNum, provide an existing Number (byte, short, int, long, float, double), BigDecimal,
String, or Num to the DoubleNum.doubleNum() static method. Statically importing doubleNum() is preferred as your
code will likely look cleaner. Creating a DecimalNum is similar to DoubleNum, but requires you to specify a
precision and rounding mode via
MathContext. Use
DecimalNum.decimalNum(String, MathContext) or use one of the convenience methods, such as decimalNum64() which
provides approximately the same precision as double, allowing up to 16 significant figures of precision and the same
rounding policy as
double. Again, statically importing decimalNum() is preferred as your code will likely look cleaner.
You can create a NumFactory instance to abstract the Num
creation process by using one of the NumFactory creation methods available in DoubleNum or DecimalNum (e.g.
DecimalNum.decimalNum64Factory()). The NumFactory defines the underlying number representation for new Num
instances, so you can create a Num from an existing Number, BigDecimal, String, or Num by simply using
NumFactory.of(...).
For mathematical operations that have well-known abbreviations, such as log for logarithm or atanh for
inverseHyperbolicTangent, the Num interface provides methods for such abbreviations, also known as shorthand
methods. The shorthand methods should be preferred to the full name of the mathematical operation; the full name
exists for completeness and as a reference for the shorthand.
Check out the Javadoc for all classes and method signatures, but here's a quick reference:
addsubtractmultiplydivideremainder(shorthand:mod)power(shorthand:pow)square(shorthand:sq)cube(shorthand:cb)exponential(shorthand:exp)nthRoot(shorthand:rt)squareRoot(shorthand:sqrt)cubeRoot(shorthand:cbrt)naturalLogarithm(shorthand:ln)commonLogarithm(shorthand:log10)binaryLogarithm(shorthand:log2)logarithm(shorthand:log)absoluteValue(shorthand:abs)negate(shorthand:neg)reciprocal(shorthand:recip)increment(shorthand:inc)decrement(shorthand:dec)floorceildegrees(shorthand:deg)radians(shorthand:rad)piesine(shorthand:sin)cosine(shorthand:cos)tangent(shorthand:tan)inverseSine(shorthand:asin)inverseCosine(shorthand:acos)inverseTangent(shorthand:atan)inverseTangent2(shorthand:atan2)hyperbolicSine(shorthand:sinh)hyperbolicCosine(shorthand:cosh)hyperbolicTangent(shorthand:tanh)inverseHyperbolicSine(shorthand:asinh)inverseHyperbolicCosine(shorthand:acosh)inverseHyperbolicTangent(shorthand:atanh)hypotenuse(shorthand:hypot)average(shorthand:avg)minimum(shorthand:min)maximum(shorthand:max)clampintegerPart(shorthand:intPart)fractionalPart(shorthand:fracPart)roundsignificantFigures(shorthand:sigFigs)mantissaexponentsignumisNegative(shorthand:lt0)isNegativeOrZero(shorthand:le0)isPositive(shorthand:gt0)isPositiveOrZero(shorthand:ge0)isZero(shorthand:eq0)isEqual(shorthand:eq)isLessThan(shorthand:lt)isLessThanOrEqual(shorthand:le)isGreaterThan(shorthand:gt)isGreaterThanOrEqual(shorthand:ge)isNaNifNaNunwraptoBytetoShorttoInttoLongtoFloattoDoubletoBigDecimalgetContextgetContextPrecisiongetContextRoundingModefactoryequalshashCodetoString
This library's Num interface was inspired by the Num interface of the excellent ta4j
library. There are several improvements and additions that this library's Num interface provides:
- Interoperability between
DoubleNumandDecimalNum. - Several more mathematical operations (e.g. trigonometry functions) via
Math in
DoubleNumand via big-math inDecimalNum. - No default precision for
DecimalNum(see ta4j issue). - Configurable epsilon for tolerant comparison operations (see ta4j
DoubleNum). Numberused instead of primitive overloads.- Documentation improvements.
Big thanks to Eric Obermühlner for the excellent big-math library.
This project is maintained by Invision. Invision enables you to automate and test your investment and trading strategies across billions of data points in seconds using quantitative finance and algorithmic trading programs you build on our code or no-code platform.