Basically when converting for output Java (which then carries over to Kotlin) will output the shortest string representation that is closer to the exact value than it is to the next representable floating point value. That way if you tried to parse that string representation you will get the same exact value.
The literal 3.14 is converted to a double that has the exact value of 3.140000000000000124344978758017532527446746826171875.
The representable double values on either side of this value are:
3.13999999999999968025576890795491635799407958984375
and
3.1400000000000005684341886080801486968994140625
When converting the value to string it outputs 3.14 because that is the shortest representation that is closest to that exact value than it is to either of these 2 surrounding values.
The fact that it prints 3.14 does not mean that 3.14 is its exact value. Note that this code also prints 3.14:
val pi = 3.140000000000000124344978758017532527446746826171875
println(pi)
There are in fact an infinite number of text number representations you can put in that code and still get 3.14 out (for example any digits you append to this will not change anything).
FYI, if you want to see what the exact value is, convert it to BigDecimal:
println(BigDecimal(pi))