Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 40 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Decimal Floating Point Arithmetic for C/C++/Java/.NET
# Decimal Floating Point Arithmetic for Java/.NET/C/C++

"...it is a bad idea to use floating point to try to represent exact quantities like monetary amounts. Using floating point for dollars-and-cents calculations is a recipe for disaster. Floating point numbers are best reserved for values such as measurements, whose values are fundamentally inexact to begin with." -- [Brian Goetz](https://www.ibm.com/developerworks/library/j-jtp0114/index.html)
**DFP** is implementation of IEEE 754-2008 **Decimal64** for Java/.NET/C/C++.

## Why?

> "...it is a bad idea to use floating point to try to represent exact quantities like monetary amounts. Using floating point for dollars-and-cents calculations is a recipe for disaster. Floating point numbers are best reserved for values such as measurements, whose values are fundamentally inexact to begin with." — [Brian Goetz](https://www.ibm.com/developerworks/library/j-jtp0114/index.html)

Java lacks built-in type to represent Money or Quantity properties frequently used in financial domain.

Expand All @@ -13,44 +17,58 @@ Ideal data type for this purpose:
* Support efficient conversion to String and double


DFP uses Java long to represent base-10 floating point numbers. DFP is based on [IEEE 754-2008 standard](https://en.wikipedia.org/wiki/IEEE_754) and supports up to 16 significant decimal digits.
DFP uses Java `long` primitive type to represent base-10 floating point numbers. DFP is based on [IEEE 754-2008 standard](https://en.wikipedia.org/wiki/IEEE_754) and supports up to 16 significant decimal digits.

## Supported languages
* Java - pure Java implementation (since version 0.12). Supported on all platforms where Java is supported.
* .NET - wrapper over C implementation with some functions re-written in C#. Supported platforms:
* x86-64 (Windows, Linux, Mac)
* x86 (Windows, Linux)
* arm64 (Linux, Mac)
* arm7 (Linux)
* C/C++ - based on [Intel Decimal Floating Point Math Library](https://www.intel.com/content/www/us/en/developer/articles/tool/intel-decimal-floating-point-math-library.html) with some additional APIs (like string parsing and specialized math functions).
* Please see [DFP on Conan](https://conan.io/center/recipes/dfp) for supported platforms.

# How to use

# How to use
## Java
Add dependency (Gradle):
```
```groovy
implementation 'com.epam.deltix:dfp:1.0.10'
```
Use:
```
Use (allocation free):
```java
import com.epam.deltix.dfp.Decimal64Utils;

@Decimal long price = Decimal64Utils.parse ("123.45");
@Decimal long halfPrice = Decimal64Utils.divideByInteger (price, 2);
@Decimal long price = Decimal64Utils.parse("123.45");
@Decimal long halfPrice = Decimal64Utils.divideByInteger(price, 2);
System.out.println(Decimal64Utils.toString(halfPrice));
System.out.println(Decimal64Utils.toScientificString(halfPrice));
System.out.println(Decimal64Utils.toFloatString(halfPrice));
```

With value type wrapper (allocation on object creation):
```java
import com.epam.deltix.dfp.Decimal64;

Decimal64 price = Decimal64.parse("123.45");
Decimal64 halfPrice = price.divide(Decimal64.fromLong (2));
System.out.println(halfPrice.toString());
System.out.println(halfPrice.toScientificString());
System.out.println(halfPrice.toFloatString());
```


## Description/Usage

* [Quick Start Guide](docs/quickstart.md)
* [Tips and Trick](docs/TipsNTricks.md)
* [Tips and Tricks](docs/TipsNTricks.md)
* [FAQ](docs/FAQ.md)
* [How to build this project](docs/build.md)

## What is under the hood?

DFP was inspired on [Intel Decimal Floating-Point Math Library](https://software.intel.com/content/www/us/en/develop/articles/intel-decimal-floating-point-math-library.html) that is written in C and provides implementation for IEEE 754-2008. Early DFP versions used JNI wrappers for this Intel library. Starting from the release 0.12 DFP is 100% Java.

## Supported platforms

DFP for Java runs on all platforms where Java is supported.

DFP for .NET supports the following platforms:
* x86-64 (Windows, Linux, Mac)
* x86 (Windows, Linux)
* arm64 (Linux, Mac)
* arm7 (Linux)

DFP was inspired on [Intel Decimal Floating-Point Math Library](https://software.intel.com/content/www/us/en/develop/articles/intel-decimal-floating-point-math-library.html) that is written in C and provides implementation for IEEE 754-2008. Early DFP versions used JNI wrappers for this Intel library. Starting from the release 0.12 DFP for Java does not depend on native code.

## Credits

Expand Down
35 changes: 19 additions & 16 deletions docs/FAQ.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
# Frequently Asked Questions

## Why not use `decimal` C# data data type ?
Decimal in C# is just not that good.
## Why not use `decimal` C# data type ?
Decimal in C# is just not that good:
* It is uses 12 bytes for mantissa (effectively 16)
* It does not have CLR support. i.e. all operators will be working as functions (slower than DFP).TODO: Benchmark proof.

## What is the `Decimal64Utils.NULL`?
Is Java all the custom data types can be represented only by the classes. This is very expensive.
This requires much amount of memory and make a big pressure on the garbage collector.
But you can avoid this. You can work with decimal values packed to the `long` primitives.
So, the replacing the `Decimal64` class with the `long` values looks as a great idea till you
encounter `null` reference. How to represent `null` with the `long`?
`Decimal64` solves this problem by introducing special `Decimal64Utils.NULL` value.
According to the IEEE 754-2008, the NaN value can be encoded in different ways.
Just one value from the whole NaN combinations was used as `Decimal64Utils.NULL`.
But, since this value is NaN according to the standard, the `Decimal64Utils.isNaN(Decimal64Utils.NULL)` returns `true`.
You should also keep this in mind if you plan to use the ValueTypeAgent.
So, key points:
* There is no `NULL` value in IEEE 754-2008 standard.
* The `NULL` is just a one combination of possible `NaN` values.
* `Decimal64Utils.isNaN(Decimal64Utils.NULL)` returns `true`.
In Java, all custom data types can only be represented by classes, which is very expensive.
This approach requires a significant amount of memory and creates considerable pressure on the garbage collector.
However, you can avoid this by working with decimal values packed into Java's `long` primitives.

Replacing the `Decimal64` class with `long` values seems like a great idea until you encounter the problem of handling `null` references. How can `null` be represented using a `long`?

`Decimal64` addresses this issue by introducing a special value: `Decimal64Utils.NULL`.
According to the IEEE 754-2008 standard, NaN (Not a Number) values can be encoded in various ways.
`Decimal64Utils.NULL` is one specific binary value from the possible NaN binary representations that was chosen as `null` value in DFP for Java.

Since this value is technically considered `NaN` according to the standard, the method `Decimal64Utils.isNaN(Decimal64Utils.NULL)` returns `true`.
You should also keep this in mind if you intend to use the ValueTypeAgent.

Key points:
* There is no `null` value defined in the IEEE 754-2008 standard.
* `Decimal64Utils.NULL` is simply one specific binary representation of the possible `NaN` values.
* The method `Decimal64Utils.isNaN(Decimal64Utils.NULL)` returns `true`.

## What are the *Checked functions in Java?
The *Checked functions in Java are not intended to be used directly.
Expand Down
19 changes: 13 additions & 6 deletions docs/pitfalls.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@ We recommend using Decimal64Util.fromLong(123) or predefined constants like Deci

### Infix operators and similar

Examples that probably won't work as intended (assuming all variables are @Decimal):
Examples that won't work as intended (assuming all variables are @Decimal):

* `` remainingQuantity = childQuantity/100; ``
* `` if (remainingQuantity >= childQuantity) {} ``
* `` remainingQuantity -= childQuantity; ``

For all cases above you have to use appropriate `Decimal64Utils` functions:
* `` remainingQuantity = Decimal64Utils.divideByInteger(childQuantity, 100); ``
* ``if (Decimal64Utils.isGreaterOrEqual(remainingQuantity, childQuantity)) {} ``
* `` remainingQuantity = Decimal64Utils.subtract(remainingQuantity, childQuantity); ``


### Using of `java.lang.Math`` functions:

Expand All @@ -33,15 +38,17 @@ Accidental (or not) usage of Math library function to operate on @Decimal number
```java
@Decimal long childQuantity = Decimal64Utils.subtract(Math.min(remainingQuantity, displayQuantity), quantityOnTheMarket); // BUG
```

### Conversion from ``double``

Many Java APIs use `double` for market data prices and sizes. Problem is - when you convert double to decimal64 rounding errors are likely. For example, double value 99.085 will be converted as 99.08499999999999. You need to round results.
Many Java APIs use `double` for market data prices and sizes. Problem is - when you convert `double` to `Decimal64` rounding errors are likely. For example, double value 99.085 will be converted as 99.08499999999999. You need to round results.
For example, to convert price of some instrument use tick size:

```java
```java
@Decimal rawPrice = Decimal64Utils.fromDouble(99.085); // 99.08499999999999
@Decimal long tickSize = instrument.getTickSize(); // e.g. 0.005
@Decimal long tickSize = instrument.getTickSize(); // e.g. 0.005
Decimal64Utils.round(rawPrice, tickSize); // produces 99.085
```


It's important to understand that the source of the error here is not in the Decimal64 representation or in the conversion process,
but in the `double` representation itself, because the decimal value `99.085` cannot be represented exactly in binary floating-point format.
13 changes: 7 additions & 6 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Add the following dependency to your Java project (Gradle sample):

```groovy
dependencies {
compile 'com.epam.deltix:dfp:0.11.23'
compile 'com.epam.deltix:dfp:1.0.10'
}
```

Expand All @@ -16,9 +16,10 @@ import com.epam.deltix.dfp.Decimal64Utils;
@Decimal long commission = Decimal64Utils.fromFixedPoint(8, 4); // 8 basis points, 0.0008
@Decimal long price = Decimal64Utils.fromDouble (123.45);
@Decimal long adjustedPrice = Decimal64Utils.add (price, Decimal64Utils.multiply (price, commission));
System.out.println(Decimal64Utils.toString(adjustedPrice));
```

This may look a bit bulky, but if you are familiar with similar libraries like Joda Money, or BigDecimal, you will find it reasonable.
This may look a bit bulky, but if you are familiar with similar libraries like Joda Money, or BigDecimal, you will find it reasonable.

## Key points:

Expand All @@ -42,8 +43,8 @@ Another example. This time we illustrate conversion from string and back:
// logger.info("Processed quantity %s).withDecimal(result);
} catch (NumberFormatException e) {
throw new IllegalArgumentException ("Bad quantity: \" + quantityText + '"');
}
return result;
}
return result;
}
```

Expand All @@ -69,8 +70,8 @@ System.out.println("Decimal64: 0,3 - 0,2 = " + (Decimal64Utils.toString(b3))); /
```
# Decimal64 Value Type

For people who prefer strong type safety and want to avoid mixing `long` values with `@Decimal long` Deltix offers Decimal64 value type.
General idea is that DFP values are represented by immutable instances of class Decimal64. Special runtime agent converts usages of Decimal64 to primitive `long` using nifty bytecode modification technique.
For people who prefer strong type safety and want to avoid mixing `long` values with `@Decimal long` Deltix offers Decimal64 value type.
General idea is that DFP values are represented by immutable instances of class Decimal64. Special runtime agent converts usages of Decimal64 to primitive `long` using nifty bytecode modification technique.

Decimal64 gives you both type safe Decimal operations and runtime effectiveness of primitive data type.

Expand Down