Why do I lose precision when using Duration from k...
# announcements
j
Why do I lose precision when using Duration from kotlin.time with some values?
Copy code
// 18 minutes and 17.1 seconds => 1097.1 seconds => PT18M17.099999999S
val duration = 1097.1.toDuration(DurationUnit.SECONDS)
print(duration.toIsoString())
This loss of precision also exists if I use the
toComponents
extension or
inWholeX
accessors
d
Before Kotlin 1.5 Duration uses a
Double
internally. Update to 1.5 and it should be fixed, it uses a Long now.
j
I'm using 1.5.0 afaik, this is a fresh IntelliJ project with Java 15
Copy code
plugins {
    id 'java'
    id 'org.jetbrains.kotlin.jvm' version '1.5.0'
}

group 'org.example'
version '1.0-SNAPSHOT'

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib"
}
d
Well, you have to realize that
Double
is a floating point number so inherently imprecise.
1097.1
cannot be represented as a double, you will actually get
1097.0999755859375
.
If you use a precise input it works just fine:
Duration.seconds(1097) + Duration.milliseconds(100)
j
I'm aware of floating point precision issues and I know I can get around it. I still think
Double.toDuration
and
convertDurationUnit(double)
are pretty much useless if they can't keep the correct nanoseconds value. For example I can do
Copy code
BigDecimal(1097.1).setScale(9, RoundingMode.HALF_EVEN).times(BigDecimal(1E9)).toLong()
and go from double seconds to long nanoseconds just fine. And I'm sure there are better ways to do the same
d
They keep the correct nanosecond value. The value you gave to the function is
1097.0999755859375
. The double value
1097.1
does not exist.
j
Look, you are using Float precision instead of Double, you didn't even read my message and you don't seem to understand that just because IEEE 754 isn't a precise representation it doesn't make it unusable. I can write:
Copy code
val doubleValue = 1097.1
println(doubleValue.toString())
and it will print 1097.1, JVM is smart enough to figure that out. I can write
BigDecimal(1097.1).setScale(9, RoundingMode.HALF_EVEN)
and get a precise representation to 9 decimal values of the Double which is enough for a Duration type.
There's probably a good reason why they aren't doing something like that with Duration but if all you are going to tell me is "doubles aren't precise" let's just stop talking since we both agree
d
Duration is a common class, so it cannot use BigDecimal.
Double#toString
also doesn't print the actual stored FP value, it does some normalization stuff to print a "nice value". 0.1.toString() also prints 0.1, even though 0.1 cannot be exactly represented by either float or double. The same is true for 1097.1, it cannot be exactly represented by float or double. Yes, you can use rounding, but having
Duration
just round without any reason sounds bad. How much should it round? Which rounding mode? If you want rounding, do it yourself in whatever way you desire before passing it to Duration.
I assume what the JDK does in Double.toString is print the value with the fewest decimal digits that would still result in the same double value - but I do not quite know, the documentation is pretty cryptic.