
Lightweight decimal wrapper around BigDecimal simplifying fixed-point math: intuitive operators, HALF_UP rounding, high-scale division, scale-insensitive equality, handy round/eq extensions for calculations.
Working with decimals in Java is unpleasant.
The float and double classes can be used, but they rely on floating-point arithmetic and are not recommended when fixed-point precision is required (e.g., in financial calculations).
For such cases, the BigDecimal class is typically used.
Unfortunately, code written with BigDecimal is often verbose and difficult to read. In addition, BigDecimal has several pitfalls (e.g., equals behavior, loss of scale during division).
Kotlin provides operators (-, +, *, /), which should improve readability. However, some problems inherited from BigDecimal still remain. For example:
div) uses the original scale and applies HALF_EVEN rounding, which is not suitable in most situations. As a result, the division operator (/) often cannot be used directly in formulas.2.0 != 2.00. Therefore, compareTo must be used instead of ==.To address these issues, the Deci class can be used.
The idea is to create a simple BigDecimal wrapper that behaves slightly differently:
HALF_UP roundingBigDecimal, Int, and Long
==) ignores scaleAdditional functions:
round – rounds a number to the specified number of decimal places and returns a Deci
eq – compares numbers of various types (including null)BigDecimal, Int, and Long have .deci extension functions to convert values to Deci
Deci does not reinvent math algorithms - at its core, it reuses Java’s BigDecimal.
With Deci, you can use operators, making formulas easier to read compared to method calls with BigDecimal.
val result = (price * quantity - fee) * 100 / (price * quantity) round 2You would expect numbers to be equal regardless of trailing decimal zeros. This is not true for BigDecimal:
println(BigDecimal("1.0") == BigDecimal("1"))false
With BigDecimal, you must use compareTo instead of equals.
With Deci, the behavior is as expected:
println(Deci("1.0") == Deci("1"))true
BigDecimal keeps the scale of the first operand when dividing:
println(BigDecimal("5") / BigDecimal("2"))2
Deci uses a high scale (up to 20 decimal places), which is sufficient for most real-world cases.
println(5.deci / 2.deci)2.5
println(100000.deci / 3.deci)33333.33333333333333333333
println(Deci("0.00001") / 3.deci)0.0000033333333333333333333
BigDecimal uses half-even rounding by default, while Deci uses half-up rounding, which is more common.
println(BigDecimal("2.5") / BigDecimal("2"))1.2
println(Deci("2.5") / Deci("2") round 1)1.3
Add the Maven dependency:
<dependency>
<groupId>com.github.labai</groupId>
<artifactId>deci-jvm</artifactId>
<version>0.1.0</version>
</dependency>sourceSets {
commonMain.dependencies {
implementation("com.github.labai:deci:0.1.0")
}
}Working with decimals in Java is unpleasant.
The float and double classes can be used, but they rely on floating-point arithmetic and are not recommended when fixed-point precision is required (e.g., in financial calculations).
For such cases, the BigDecimal class is typically used.
Unfortunately, code written with BigDecimal is often verbose and difficult to read. In addition, BigDecimal has several pitfalls (e.g., equals behavior, loss of scale during division).
Kotlin provides operators (-, +, *, /), which should improve readability. However, some problems inherited from BigDecimal still remain. For example:
div) uses the original scale and applies HALF_EVEN rounding, which is not suitable in most situations. As a result, the division operator (/) often cannot be used directly in formulas.2.0 != 2.00. Therefore, compareTo must be used instead of ==.To address these issues, the Deci class can be used.
The idea is to create a simple BigDecimal wrapper that behaves slightly differently:
HALF_UP roundingBigDecimal, Int, and Long
==) ignores scaleAdditional functions:
round – rounds a number to the specified number of decimal places and returns a Deci
eq – compares numbers of various types (including null)BigDecimal, Int, and Long have .deci extension functions to convert values to Deci
Deci does not reinvent math algorithms - at its core, it reuses Java’s BigDecimal.
With Deci, you can use operators, making formulas easier to read compared to method calls with BigDecimal.
val result = (price * quantity - fee) * 100 / (price * quantity) round 2You would expect numbers to be equal regardless of trailing decimal zeros. This is not true for BigDecimal:
println(BigDecimal("1.0") == BigDecimal("1"))false
With BigDecimal, you must use compareTo instead of equals.
With Deci, the behavior is as expected:
println(Deci("1.0") == Deci("1"))true
BigDecimal keeps the scale of the first operand when dividing:
println(BigDecimal("5") / BigDecimal("2"))2
Deci uses a high scale (up to 20 decimal places), which is sufficient for most real-world cases.
println(5.deci / 2.deci)2.5
println(100000.deci / 3.deci)33333.33333333333333333333
println(Deci("0.00001") / 3.deci)0.0000033333333333333333333
BigDecimal uses half-even rounding by default, while Deci uses half-up rounding, which is more common.
println(BigDecimal("2.5") / BigDecimal("2"))1.2
println(Deci("2.5") / Deci("2") round 1)1.3
Add the Maven dependency:
<dependency>
<groupId>com.github.labai</groupId>
<artifactId>deci-jvm</artifactId>
<version>0.1.0</version>
</dependency>sourceSets {
commonMain.dependencies {
implementation("com.github.labai:deci:0.1.0")
}
}