From 9421180ce3f24856eec03084d56b582e2a5eea85 Mon Sep 17 00:00:00 2001 From: Alexei Osipov Date: Thu, 2 Oct 2025 22:18:32 +0200 Subject: [PATCH 1/3] Refresh README and other docs --- README.md | 60 +++++++++++++++++++++++++++++----------------- docs/FAQ.md | 35 ++++++++++++++------------- docs/pitfalls.md | 18 +++++++++----- docs/quickstart.md | 13 +++++----- 4 files changed, 76 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index a4c89631..770fcd0c 100644 --- a/README.md +++ b/README.md @@ -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. @@ -13,44 +17,56 @@ 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. -# How to use +## Supported languages +* Java - pure Java implementation (since version 0.12). Supported on all platforms where Java is supported. +* .NET - wrapper over C implementation. Supported platforms: + * x86-64 (Windows, Linux, Mac) + * x86 (Windows, Linux) + * arm64 (Linux, Mac) + * arm7 (Linux) +* C/C++ - provided by [Intel Decimal Floating Point Math Library](https://www.intel.com/content/www/us/en/developer/articles/tool/intel-decimal-floating-point-math-library.html) +# 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 diff --git a/docs/FAQ.md b/docs/FAQ.md index 0d32764a..813dbd15 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -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. diff --git a/docs/pitfalls.md b/docs/pitfalls.md index 53635d2a..62d5b003 100644 --- a/docs/pitfalls.md +++ b/docs/pitfalls.md @@ -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: @@ -33,15 +38,16 @@ 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 error is in `double` representation itself and diff --git a/docs/quickstart.md b/docs/quickstart.md index 327c2939..16fb7320 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -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' } ``` @@ -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: @@ -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; } ``` @@ -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. From d9e4f26081ced662fc4d49e4bc0b3ac6e9d25f1b Mon Sep 17 00:00:00 2001 From: Alexei Osipov Date: Fri, 3 Oct 2025 17:41:55 +0200 Subject: [PATCH 2/3] PR fixes --- README.md | 4 ++-- docs/pitfalls.md | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 770fcd0c..7ef3db03 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,12 @@ DFP uses Java `long` primitive type to represent base-10 floating point numbers. ## Supported languages * Java - pure Java implementation (since version 0.12). Supported on all platforms where Java is supported. -* .NET - wrapper over C implementation. Supported platforms: +* .NET - wrapper over C implementation with some functions re-written in C#. Supported platforms are same as for C implementation. +* 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). Supported platforms: * x86-64 (Windows, Linux, Mac) * x86 (Windows, Linux) * arm64 (Linux, Mac) * arm7 (Linux) -* C/C++ - provided by [Intel Decimal Floating Point Math Library](https://www.intel.com/content/www/us/en/developer/articles/tool/intel-decimal-floating-point-math-library.html) # How to use ## Java diff --git a/docs/pitfalls.md b/docs/pitfalls.md index 62d5b003..9beee240 100644 --- a/docs/pitfalls.md +++ b/docs/pitfalls.md @@ -50,4 +50,5 @@ Many Java APIs use `double` for market data prices and sizes. Problem is - when Decimal64Utils.round(rawPrice, tickSize); // produces 99.085 ``` -It's important to understand that the error is in `double` representation itself and +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. From 89086514acfe410a8e8e2b44ae577dbcabc5b97c Mon Sep 17 00:00:00 2001 From: Alexei Osipov Date: Fri, 3 Oct 2025 18:05:30 +0200 Subject: [PATCH 3/3] PR fixes 2 --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7ef3db03..5f026368 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,14 @@ DFP uses Java `long` primitive type to represent base-10 floating point numbers. ## 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 are same as for C implementation. -* 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). Supported platforms: +* .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 ## Java