louiscad
03/03/2023, 2:17 AM1.6896646614334472 - 1.6893290421702434
sometimes returns 3.356192632038013E-4
like in the JVM, but at other times, I get 0.0003356192632038013
.0.0003356192632038013
which is not what IEEE 754 should give I think. Kotlin code with JDK 18 gives me 3.356192632038013E-4
, which is using Java's double
, which is supposed to be IEEE 754 too!fun main() {
val a = 1.6896646614334472
val b = 1.6893290421702434
val c = (a - b)
println("$a - $b = $c")
}
0.0003356192632038013
3.356192632038013E-4
3.356192632038013E-4
sin(0.8314090181354269)
gives different results on JS compared to Android and SOME JVM environments: the last digit can be 7
or 8
, and we still don't know why.jw
03/03/2023, 2:29 AMlouiscad
03/03/2023, 2:30 AMjw
03/03/2023, 2:30 AMlouiscad
03/03/2023, 2:31 AMjw
03/03/2023, 2:33 AMlouiscad
03/03/2023, 2:34 AMDouble
(at least from a Kotlin standpoint), and I have *
, +
, cos
, and sin
being usedjw
03/03/2023, 2:35 AMlouiscad
03/03/2023, 2:50 AMsin(0.8314090181354269)
What do you get on your machine on Kotlin/JVM?Chris Lee
03/03/2023, 2:52 AMjw
03/03/2023, 2:53 AMlouiscad
03/03/2023, 2:53 AM0.7388815504611167
for Kotlin 1.8.10, be it the JVM, JS (legacy) or JS IR.
On machine though, on the JVM, I get 0.7388815504611168
(notice the last digit).
My JDK is 64-Bit Server VM Zulu18.30+11-CA
Chris Lee
03/03/2023, 2:55 AMimport kotlin.math.*
fun main() {
println(sin(0.8314090181354269).toRawBits())
}
louiscad
03/03/2023, 2:57 AM4604830472895931656
locallyChris Lee
03/03/2023, 2:57 AM4604830472895931655
louiscad
03/03/2023, 2:57 AMChris Lee
03/03/2023, 2:57 AMlouiscad
03/03/2023, 2:58 AMChris Lee
03/03/2023, 3:05 AMlouiscad
03/03/2023, 3:05 AM64-Bit Server VM Zulu18.30+11-CA
as stated before in the threadChris Lee
03/03/2023, 3:07 AM4604830472895931656
louiscad
03/03/2023, 3:08 AMChris Lee
03/03/2023, 3:08 AMlouiscad
03/03/2023, 3:09 AMChris Lee
03/03/2023, 3:13 AMlouiscad
03/03/2023, 3:14 AMChris Lee
03/03/2023, 3:16 AMMacBook M1, aarch64, Corretto-17.0.6.10.1: 4604830472895931656
aarch64, 64-Bit Server VM Zulu18.30+11-CA: 4604830472895931656
Playground: 4604830472895931655
Chrome on aarch64: 4604830472895931655
godbolt: 4604830472895931655
MacBook M1, aarch64, Corretto-17.0.6.10.1: 4604830472895931656
aarch64, 64-Bit Server VM Zulu18.30+11-CA: 4604830472895931656
Playground: 4604830472895931655
Chrome on aarch64: 4604830472895931655
godbolt: 4604830472895931655
Github Actions (linux) 4604830472895931655
louiscad
03/03/2023, 3:20 AMChris Lee
03/03/2023, 3:24 AMlouiscad
03/03/2023, 3:25 AM0.7388815504611168
• JS (legacy & IR), JVM on Linux (x64): 0.7388815504611167
Chris Lee
03/03/2023, 3:25 AMlouiscad
03/03/2023, 3:26 AMChris Lee
03/03/2023, 3:27 AMprintln(sin(0.8314090181354269).toRawBits())
MacOS aarch64, Corretto-17.0.6.10.1: 4604830472895931656
MacOS aarch64, Zulu18.30+11-CA: 4604830472895931656
Playground: 4604830472895931655
Chrome on aarch64: 4604830472895931655
godbolt (Linux/Intel): 4604830472895931655
Github Actions (linux): 4604830472895931655
louiscad
03/03/2023, 3:27 AMChris Lee
03/03/2023, 3:28 AMlouiscad
03/03/2023, 3:28 AMChris Lee
03/03/2023, 3:28 AMlouiscad
03/03/2023, 3:36 AMChris Lee
03/03/2023, 3:37 AMlouiscad
03/03/2023, 3:37 AMChris Lee
03/03/2023, 3:37 AMlouiscad
03/03/2023, 3:38 AMChris Lee
03/03/2023, 3:41 AMlouiscad
03/03/2023, 3:42 AMChris Lee
03/03/2023, 3:43 AM4604830472895931655
. Same as JVM/Intel etc.louiscad
03/03/2023, 3:43 AMChris Lee
03/03/2023, 3:43 AMlouiscad
03/03/2023, 3:43 AMChris Lee
03/03/2023, 3:43 AMlouiscad
03/03/2023, 3:44 AMChris Lee
03/03/2023, 3:44 AMlouiscad
03/03/2023, 3:45 AMChris Lee
03/03/2023, 3:45 AMlouiscad
03/03/2023, 3:46 AMChris Lee
03/03/2023, 3:48 AMephemient
03/03/2023, 3:49 AMChris Lee
03/03/2023, 3:51 AMephemient
03/03/2023, 3:51 AMstrictfp
modifier, preventing using the intermediate 80-bit x87 FPU values. (that's gone now; SSE works better on x86-64 and isn't an oddball size)Chris Lee
03/03/2023, 3:55 AMmath.sin
boils down to StrictMath.sin
on the JVM. It is documented as being non-exact:
The computed result must be within 1 ulp of the exact result.
ephemient
03/03/2023, 3:56 AMMath.sin
from what I can seeChris Lee
03/03/2023, 3:57 AM@SinceKotlin("1.2")
@InlineOnly
public actual inline fun sin(x: Double): Double = nativeMath.sin(x)
to:
@IntrinsicCandidate
public static double sin(double a) {
return StrictMath.sin(a); // default impl. delegates to StrictMath
}
Kotlin 1.8.10.ephemient
03/03/2023, 3:59 AMimport java.lang.Math as nativeMath
public actual inline fun sin(x: Double): Double = nativeMath.sin(x)
Chris Lee
03/03/2023, 4:00 AMephemient
03/03/2023, 4:01 AMChris Lee
03/03/2023, 4:02 AMephemient
03/03/2023, 4:03 AMChris Lee
03/03/2023, 4:04 AMephemient
03/03/2023, 4:13 AM@IntrinsicCandidate
public static double sin(double a) {
that means that the JIT may produce some different native code instead of running that function normallyChris Lee
03/03/2023, 4:14 AMephemient
03/03/2023, 4:15 AMChris Lee
03/03/2023, 4:15 AMephemient
03/03/2023, 4:17 AMChris Lee
03/03/2023, 4:20 AMephemient
03/03/2023, 4:30 AMsin
isn't an instruction in ASM, it's library code)Chris Lee
03/03/2023, 4:34 AMarchSin
internal function), but of course the implementations will differ, as will the compiler steps etc.ephemient
03/03/2023, 4:37 AMChris Lee
03/03/2023, 4:43 AMstubs.go
has the directive
go:build !s390x
indicating that most of the trig operations don’t have assembly/intrinsic implementations outside of s390x platform.ephemient
03/03/2023, 4:47 AMChris Lee
03/03/2023, 4:48 AMelizarov
03/03/2023, 8:25 AMsin
just does not (and pragmatically can not) guarantee that it always produces the same result. The actual implementation of sin
uses an approximate algorithm and it only guarantees that its error does not exceed 1 ULP. That is, it can be off from the correct answer in the last bit. This especially happens when the true answer is almost half way in between neighboring double values. In this case, different implementations of sin
function can get different values of the last bit. This is totally inevitable.
The actual background here is way more complicated. For example, old versions of libc tried to provide “correctly rounded result” in all the cases. That meant that sometimes, when approximation is close to the half of the last bit, it has to use arbitrary-precision arithmetics to figure out more digits to round the result correctly. It ended up causing all sorts of performance problems in games, where sometimes, if you are unlucky, sin computations will be very slow. Modern libc does not do this anymore. Nobody does, nor should be doing this. It is perfectly fine for a sin
value to have different value in the last bit on different implementations (runtimes, versions, archs).sin
should be returning, so that it is always the same no matter which runtime/platform you compute it with, yet a fast algorithm to perform such computation does not exist. So a pragmatic tradeoff is to require that the sin
returns the correct value “up to the last bit”. For that purpose, multiple fast algorithms exist, so different runtimes use different such algos, producing different results in the last bit, just as expected.ephemient
03/03/2023, 8:38 AMelizarov
03/03/2023, 8:40 AMsin
/`cos` are working. So V8 provided very fast yet very inaccurate implementation for `sin`/`cos` with error not just in the last bit (as customary) but really being very far off. Of course, they had to fix it when people stared using V8 for real stuff, but the title of the fastest JS engine was already won, so it was not a problem anymore.ephemient
03/03/2023, 8:41 AMelizarov
03/03/2023, 8:42 AMephemient
03/03/2023, 8:43 AMsin
and cos
are "implementation-approximated", so there doesn't seem to be a guarantee about the number of bits of accuracyelizarov
03/03/2023, 8:44 AMephemient
03/03/2023, 8:48 AMstrictfp
differences across platforms at some point in the past, didn't they? not anymore on JVM, but LLVM is happy to optimize those chains to produce different results depending on optimization level and targetelizarov
03/03/2023, 8:51 AM