BigDecimal serialization with unknown scale and precision - kotlin

I am trying to serialize a data class using Avro4k.
#Serializable
class Aggregator(
#ScalePrecision(10, 10)
#Serializable(with = BigDecimalSerializer::class)
val value: BigDecimal
)
The problem is that I don't know the scale and precision of the BigDecimal beforehand, and I get rounding errors if they are not big enough.
Is there a way to not inform the scale and precision, and use whatever values the provided object have? I don't know if Avro / Avro4k would support that.

Related

Kotlin BigDecimal multiplication wrong results

I need to use BigDecimal for some computation but am a bit surprised by the behaviour:
val thousand = BigDecimal(1000)
val fee = BigDecimal(0.005)
println(thousand * fee)
You'd expect the console to contain 5 but the result is 5.000000000000000104083408558608425664715468883514404296875000
I know that I can limit the precision and do some rounding with setScale but the real question is Why is this needed in the first place. This result is obviously wrong.
What am I missing?
The issue is likely to be with the construction of the fee BigDecimal. This is taking a double value and converting it to a BigDecimal. Unfortunately, some fairly simple decimal fractions are impossible to precisely represent as doubles or floats, and this constructor for BigDecimal will take that imprecise double as its value.
From the documentation:
The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.
The way around this is to use the String constructor, which gets round the issue of having to convert "via" a double.

Get mantissa and exponent of double/float in kotlin

How can I access the mantissa and exponent of a double in kotlin?
A multiplatform option would be ideal, but the Double class has no such methods/properties.
Kotlin provides the toBits() function, which is just a wrapper of the JDK's doubleToLongBits(). You can see from the docs for doubleToLongBits:
Bits 62-52 (the bits that are selected by the mask 0x7ff0000000000000L) represent the exponent. Bits 51-0 (the bits that are selected by the mask 0x000fffffffffffffL) represent the significand (sometimes called the mantissa) of the floating-point number.
So you can write properties that extract the values for you:
val Double.mantissa get() = toBits() and 0x000fffffffffffff
val Double.exponent get() = toBits() and 0x7ff0000000000000 shr 52
fun main() {
println(1.0.mantissa)
println(1.0.exponent)
}
Output:
0
1023
Note that these are the raw IEEE-754 encoded values. You will need to convert them according to the IEEE-754 spec if you want the numerical values.
I can suggest common for all programming languages solution:
val mantissa = doubleValue - doubleValue.toInt()

kotlin rounding off in BigDecimal

import java.math.BigDecimal
BigDecimal(0.235).setScale(2, BigDecimal.ROUND_HALF_UP) // 0.23
BigDecimal("0.235").setScale(2, BigDecimal.ROUND_HALF_UP) // 0.24
In kotlin, when input 0.235 is given as double then the output is 0.23.
when input 0.235 is given as string then the output is 0.24
Here is the definition of ROUND_HALF_UP given in the documentation:
Rounding mode where values are rounded towards the nearest
neighbor. Ties are broken by rounding up.
From the BigDecimal docs:
The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a
BigDecimal which is exactly equal to 0.1 (an unscaled value of 1,
with a scale of 1), but it is actually equal to
0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that
matter, as a binary fraction of any finite length). Thus, the value
that is being passed in to the constructor is not exactly equal to
0.1, appearances notwithstanding.
The String constructor, on the other hand, is perfectly predictable: writing new BigDecimal("0.1") creates a BigDecimal which
is exactly equal to 0.1, as one would expect. Therefore, it is
generally recommended that the String
constructor
be used in preference to this one.
The issue here is that in the first case you are calling the BigDecimal constructor using a floating point (read: not exact) literal. Consider the following script (in Java):
BigDecimal blah = new BigDecimal(0.235d);
System.out.println(blah);
This prints 0.23499999999999998667732370449812151491641998291015625 in my demo tool. That is, you are not actually passing in literal 0.235, but rather a floating point approximation to it. It so happens, in this case, that the actual literal value is slightly less than 0.235, leading the round half up to result in 0.23 rather than 0.24.

Kotlin double greater than float wrong expression resolving

I ran into an issue which I was wondering if the expected behavior is correct or whether I found a bug.
Given this:
0.08 > 0.08 == false
0.08 > 0.08F == true
0.08 > 0.08F.toDouble() == true
0.08.toFloat() > 0.08F == false
Why does the third expression is not false?
Any ideas?
It is not a bug, it's based on rounding errors.
Executing the following code:
val d = 0.08
val f = 0.08F
val fd = f.toDouble()
print("%.20f".format(d) + "\n")
print("%.20f".format(f) + "\n")
print("%.20f".format(fd))
gives you the following output:
0.08000000000000000000
0.07999999821186066000
0.07999999821186066000
So as you can see, 0.08 double value is (till the 20th decimal place) exact to 0.08 while the float is (due to lower precision) not able to be represented as exact so it contains a rounded value, which is slightly lower than 0.08
Converting your approximate (a little lower) 0.08 float to a double doesn't increase your precision, you still have your rounding error of the float, which results in being the converted double to be a little bit lower.
// Edit: If you are interested in how exactly floating point numbers work, I would recommend you to have a look at the wikipedia article on floating point arithmetic and at this question: Is floating point math broken?
This is a supplement to the existing answer. Java BigDecimal has two useful properties for analyzing floating point behavior. Conversion from float or double to BigDecimal is exact, and so is its default string conversion. The simplest use of these properties is to print the exact value of a floating point expression. BigDecimal arithmetic can also be used, for example, to find the half way points between a double and its neighbors when studying rounding.
This program:
import java.math.BigDecimal;
public strictfp class Test {
public static void main(String[] args) {
System.out.println(new BigDecimal(0.08));
System.out.println(new BigDecimal(0.08f));
}
}
outputs:
0.08000000000000000166533453693773481063544750213623046875
0.07999999821186065673828125
which confirms that 0.08 is represented to the expected precision in each format, and the double representation is strictly greater than the float.

If I only need 1 or 2 digit accuracy, is float better than decimal type?

If I only need 1 or 2 digit after the decimal place accuracy, should I use float or still go with decimal(18,2)?
The numeric value in question represents salary.
You should use decimal type or better still, money type, which is specially suited for these needs.
You should not use float as float is an approximate representation of a decimal value.
See MSDN documentation why we should not use float here
The float and real data types are known as approximate data types. The behavior of float and real follows the IEEE 754 specification on approximate numeric data types.
Approximate numeric data types do not store the exact values specified for many numbers; they store an extremely close approximation of the value. For many applications, the tiny difference between the specified value and the stored approximation is not noticeable. At times, though, the difference becomes noticeable.
Because of the approximate nature of the float and real data types, do not use these data types when exact numeric behavior is required, such as in financial applications, in operations involving rounding, or in equality checks. Instead, use the integer, decimal, money, or smallmoney data types.
The main question in deciding whether you want to use a binary decimal or a decimal decimal is not accuracy, really. It's "how would I expect the calculations to proceed".
The main thing you get from decimal is something you can calculate on paper, with fixed-point numbers. E.g.:
13.22
+ 7.3
-----
20.52
It's not really that decimal is more precise than a float (though it can be that as well, for certain applications). The point is it makes the same mistakes you would make on paper - it's a decimal decimal number, not binary.
Or in another line of thought, your inputs are definitely decimal decimal numbers (typically, in a string or such). So if you use a float, you get decimal -> binary -> calculation -> decimal. Binary to decimal means no loss of information (and finite binary number can be exactly represented in a finite decimal number), but the other way around this isn't true - even something like 0.1 has no finite representation in a binary decimal number (just like 1 / 3 has no finite representation in a decimal decimal number, but works fine in say base 6).