11

Is IEEE-754 arithmetic reproducible on different platforms?

I was testing some code written in R, that uses random numbers. I thought that setting the seed of the random number generator on all tested platforms would make the tests reproducible, but this does not seem to be true for rexp(), which generates exponentially distributed random numbers.

This is what I get on 32 bit Linux:

options(digits=22) ; set.seed(9) ; rexp(1, 5)
# [1] 0.2806184054728815824298
sessionInfo()
# R version 3.0.2 (2013-09-25)
# Platform: i686-pc-linux-gnu (32-bit)

and this is what I get on 64 bit OSX 10.9:

options(digits=22) ; set.seed(9) ; rexp(1, 5)
# [1] 0.2806184054728815269186
sessionInfo()
# R version 3.0.2 (2013-09-25)
# Platform: x86_64-apple-darwin10.8.0 (64-bit)

64 bit Linux gives the same results as 64 bit OSX, so this seems to be a 32 bit vs 64 bit issue.

Let us assume that both R versions were compiled with the same GCC version, and with the same (default R) compilation flags that make the compiler use IEEE-754 arithmetic.

My question is, can this be considered a bug in R? Or is it just a "normal" consequence of using approximate, finite precision floating point arithmetic?

I sent the same question to the R-devel mailing list, but got no answer on the list, and only one answer in private, trying to convince me that this is not a bug and I should live with it.

This is what IEEE-754 says about reproducibility (from Wikipedia):

The IEEE 754-1985 allowed many variations in implementations (such as the encoding of some values and the detection of certain exceptions). IEEE 754-2008 has tightened up many of these, but a few variations still remain (especially for binary formats). The reproducibility clause recommends that language standards should provide a means to write reproducible programs (i.e., programs that will produce the same result in all implementations of a language), and describes what needs to be done to achieve reproducible results.

And this is under "Recommendations".

My (subjective) opinion is that this is a bug, because the whole point of the IEEE-754 standard is having reproducible, platform-independent floating point arithmetic.

Gabor Csardi
  • 10,705
  • 1
  • 36
  • 53
  • `digits=22` is beyond your machine limit; see `.Machine$double.eps` which ends about six digits earlier. – Dirk Eddelbuettel Feb 08 '15 at 14:46
  • @DirkEddelbuettel I am not sure if that matters, the IEEE-754 recommendation is to "provide a means to write reproducible programs". E.g. if it is reproducible for 16 digits, but not for more, then R should truncate there. My problem is not that the operations are not accurate, that is fine. The problem is that the **same code** gives different results. IEEE-754 allows you to be wrong. But it recommends that you are always wrong the same way. – Gabor Csardi Feb 08 '15 at 18:51
  • Maybe best practices on how to truncate would indeed be useful. I am satisfied that `set.seed(42); print(runif(3), digits=16)` gives me the identical result on i386, x86_64 and whatever my Android phone has (which the latter running the [free R Console app](https://play.google.com/store/apps/details?id=com.appsopensource.R&hl=en)). – Dirk Eddelbuettel Feb 08 '15 at 19:14
  • Yes, it is not actually the RNG (probably), but creating an exponentially distributed random number from uniform ones. Which is not surprising, AFAIK the RNG is usually integer arithmetic. But then the exponential is floating point arithmetic. That's why `runif()` is good. – Gabor Csardi Feb 08 '15 at 23:14

1 Answers1

22

There are issues with reproducibility of even elementary floating-point operations in high-level languages, but they are usually controllable with various platform-specific operations such as setting compiler switches, using custom code to set floating-point controls and modes, or, if necessary, writing essential operations in assembly. As developed in comments, the specific problem you encountered may be that different C implementations use different precision to evaluate intermediate floating-point expressions. Often this can be controlled by compiler switches or by including casts and assignments in the expressions to require rounding to the nominal type (thus discarding excess precision).

However, the more complicated functions, such as exp and cos, are not routinely reproducible on different platforms. Although the 2008 IEEE-754 standard recommends that these functions be implemented with correct rounding, this task has not been completed for any math library with a run-time with a known bound. Nobody in the world has done the mathematics to accomplish this.

The CRlibm project has implemented some of the functions with known run-time bounds, but the work is incomplete. (Per Pascal Cuoq’s comment, when CRlibm does not have a proven run-time bound for correct rounding, it falls back to a result highly likely to be correctly rounded due to being computed with very high precision.) Figuring out how to deliver a correctly-rounded result in a bounded time and proving it is difficult for many functions. (Consider how you might prove that no value of cos(x), where x is any double value, is closer than some small distance e from the midpoint between two representable values. The midpoint is important because it is where rounding must change from returning one result to returning another, and e tells you how accurately and precisely you must calculate an approximation in order to provide correct rounding.)

The current state of affairs is that many of the functions in the math library are approximated, some accuracy looser than correct rounding is delivered, and different vendors use different implementations with different approximations. I am supposing that R uses some of these functions in its rexp implementation, and that it uses the native libraries of its target platforms, so it gets different results on different platforms.

To remedy this, you might consider using a common math library on the platforms you target (possibly CRlibm).

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • Thanks for the explanation, I certainly didn't think this was easy, but I expected that it was done for a basic set of operations and rest comes from these. I still think that in this particular case it is a bug that can be fixed (I'll try some day), because this is all `rexp` does: https://github.com/wch/r-source/blob/776708efe6003e36f02587ad47b2eaaaa19e2f69/src/nmath/sexp.c and `unif_rand()` seems to be reproducible across platforms. – Gabor Csardi Jan 19 '14 at 02:24
  • 1
    @GaborCsardi: Assuming `unif_rand` also does not contain any non-elementary functions, I suspect the difference is that the GCC compilation for 32-bit Linux uses extra precision (`long double`) in its arithmetic, even when the nominal types are `double`. There may be a switch to control for this. – Eric Postpischil Jan 19 '14 at 02:43
  • 3
    Technically, CRlibm offers bounded execution time with known run-time bounds: it only ever pushes the computation up to 512 bits of precision for non-exact cases of functions for which worst cases are not known. This means that for some functions, it is not correctly rounded but “correctly rounded with astronomical confidence”. The alternatives are discussed in section 1.3.5 of http://lipforge.ens-lyon.fr/frs/download.php/153/crlibm-1.0beta3.pdf – Pascal Cuoq Jan 19 '14 at 07:53
  • The “512 bits” part of the previous comment is a reminiscence for which I cannot find the source again. Something “astronomical”, anyway. – Pascal Cuoq Jan 19 '14 at 07:54
  • @PascalCuoq: Thanks, I incorporated a note about that into the answer. – Eric Postpischil Jan 21 '14 at 22:27