19

I am trying to understand roundoff error for basic arithmetic operations in MATLAB and I came across the following curious example.

(0.3)^3 == (0.3)*(0.3)*(0.3)

ans = 0

I'd like to know exactly how the left-hand side is computed. MATLAB documentation suggests that for integer powers an 'exponentiation by squaring' algorithm is used.

"Matrix power. X^p is X to the power p, if p is a scalar. If p is an integer, the power is computed by repeated squaring."

So I assumed (0.3)^3 and (0.3)*(0.3)^2 would return the same value. But this is not the case. How do I explain the difference in roundoff error?

bla
  • 25,846
  • 10
  • 70
  • 101
Nick
  • 201
  • 1
  • 5
  • It's interesting to note that WolframAlpha correctly designates this as true. – kylex Jan 23 '13 at 00:36
  • Does (0.3)^2 return the same result as 0.3*0.3? And can you give me the exact result generated by both 0.3^3 and 0.3*0.3*0.3? It would be quite helpful in reverse-engineering what's going on. – Stephen Canon Jan 23 '13 at 00:39
  • 3
    Please show us where you read that "for integer powers an 'exponentiation by squaring' algorithm is used". I can't find this by a Google search and if it's not the case, then the question is trivial. – s.bandara Jan 23 '13 at 00:41
  • 2
    In practice you shouldn't compare floating point values with == (this is true whether you're using matlab or anything else), but rather use something more like [abs(#1 - #2) < epsilon]. If you subtract them (at least on my machine), the difference is -3.4694e-18 – alrikai Jan 23 '13 at 00:41
  • I think the solution would be clear if we could find a way to compile MATLAB to assembly opcodes. Being unfamiliar with MATLAB myself, I don't know whether one is available. – Ben Mordecai Jan 23 '13 at 00:42
  • FWIW, Python 2.7 seems to be giving me the exact same outputs as matlab, which you can disassemble, though I don't know how to do it without constant folding: `dis.dis(compile('.3 * .3 * .3', '', 'eval'),)` gives `0 LOAD_CONST 2 (0.027)`, while disassembling `.3 ** 3` gives `0.026999999999999996`. In both Matlab and Python, we have `.3^2 == .3 * .3` and `.3^4 == .3 * .3 * .3 * .3`, though it's not true for 6 or 8 (breaking both the "even number" and "power of two" theories). For Python, it's at least theoretically possible to track down the exact code used for exponentiation. – Danica Jan 23 '13 at 00:44
  • 1
    @StephenCanon on my machine (0.3)^2 == (0.3)*(0.3) returns 1, as does (0.3)^4 == (0.3)*(0.3)*(0.3)*(0.3), while the other combinations don't. – alrikai Jan 23 '13 at 00:45
  • I would say that matrices and number are treated differently by Matlab. And for numbers the answer is the one proposed by @Jeremy – Emanuele Paolini Jan 23 '13 at 00:56
  • Could they be implementing it as exp(log(.3)*3)? It would certainly solve the general problem, taking into account signs, but its a terribly inefficient way to do this. – sizzzzlerz Jan 23 '13 at 00:57
  • Interestingly `0.3*0.3*0.3*0.3==0.3^4` does yield `1`. so does `(0.3*0.3)*0.3==(0.3^2)*0.3` and `(0.3*0.3)*0.3==0.3*(0.3^2)` – bla Jan 23 '13 at 01:38
  • @s.bandara I don't know why you were looking in Google. The *document he cited* says "If p is an integer, the power is computed by repeated squaring". – user207421 Jan 23 '13 at 01:40
  • @EJP Not that it's especially important, but if you look at the edit history the link wasn't added until after s.bandara's comment. – Danica Jan 23 '13 at 01:42
  • @EricPostpischil Are you sure you don't have those two backwards? I got the opposite of that: `.3 * .3 * .3` is the closest double to .027, `pow(.3, 3)` is the slightly smaller one, in Python, Matlab, and a C program (compiled without optimizations just in case). – Danica Jan 23 '13 at 01:48
  • 1
    @Dougal: You are right, I had them swapped. `.3*.3*.3` yields 0.0269999999999999996946886682280819513835012912750244140625 and is closer to the exact result. `pow(.3, 3)` yields 0.0269999999999999962252417162744677625596523284912109375. Time to get some sleep. – Eric Postpischil Jan 23 '13 at 03:46
  • @Dougal He cited the document in the original post. The edit just added a link. The Matlab documentation is the Matlab documentation. Not Google. – user207421 Jan 23 '13 at 05:20
  • 1
    Related: [this question](http://stackoverflow.com/questions/1674477/what-number-in-binary-can-only-be-represented-as-an-approximation), and also [this question](http://stackoverflow.com/questions/13699596/is-this-a-matlab-bug-do-you-have-the-same-issue/13699708#13699708). Lesson: never use the operator `==` in a floating point context. – Rody Oldenhuis Jan 23 '13 at 07:31
  • 2
    @RudyOldenhuis: The advice not to use `==` with floating point is naïve and is not applicable to this question. – Eric Postpischil Jan 23 '13 at 10:24
  • @alrikai: Comparing floating-point values with a tolerance is not a general solution because it increases false acceptance of unequal values as equal. The proper comparison of floating-point values is application-specific. – Eric Postpischil Jan 23 '13 at 10:29
  • @EricPostpischil: 1) please type my name correctly, 2) did you actually read the questions I linked to? The point is: most operators on floats that are mathematically identical are *almost never* computationally identical, so don't expect `==` to work as you mathematically expect it to. Out there in the wild (so outside your own domain), the most effective bug-preventing way to compare floats it is use a round-off related tolerance (in Matlab, `eps(0.3)`). False positives are so rare in most contexts that it is better to say that the need to handle *them* is application-specific. – Rody Oldenhuis Jan 23 '13 at 15:26
  • @EricPostpischil: Remember that for every single person who needs two quads to be *exactly* identical and thus googled his way to this question, there are at least 1000 people reading it who *don't even really need double* for their application, but use it and run into this issue anyway. As an advice that applies to *most* people, "avoid `==`" definitely does apply. – Rody Oldenhuis Jan 23 '13 at 15:30
  • 1
    @RodyOldenhuis: Your comments are not relevant to this question. – Eric Postpischil Jan 23 '13 at 15:41
  • @EricPostpischil: That's why I didn't post it as an answer, and I started out with "related". But whatever man, go be a happy purist. – Rody Oldenhuis Jan 23 '13 at 18:40
  • 1
    I'm amazed to find so many helpful comments. Thanks to everyone who contributed to the discussion :) – Nick Jan 24 '13 at 01:01

5 Answers5

5

I don't know anything about MATLAB, but I tried it in Ruby:

irb> 0.3 ** 3
  => 0.026999999999999996
irb> 0.3 * 0.3 * 0.3
  => 0.027

According to the Ruby source code, the exponentiation operator casts the right-hand operand to a float if the left-hand operand is a float, and then calls the standard C function pow(). The float variant of the pow() function must implement a more complex algorithm for handling non-integer exponents, which would use operations that result in roundoff error. Maybe MATLAB works similarly.

Paige Ruten
  • 172,675
  • 36
  • 177
  • 197
  • 1
    The numbers look identical to those in Matlab and Python (on my 64-bit OS X machine), and I just tracked down that Python [also calls `pow()`](http://hg.python.org/releasing/2.7.3/file/7bb96963d067/Objects/floatobject.c#l911). So now we just need to look at the source of `pow()`.... – Danica Jan 23 '13 at 00:52
  • Okay, with a little digging I found [this implementation of `__ieee754_powf`](https://github.com/JuliaLang/openlibm/blob/master/src/e_powf.c#L148) (which is not the one used on any of our systems but might use the same algorithm), and the actual one used in OSX from [this directory](http://www.opensource.apple.com/tarballs/Libm/) (filename `Source/Intel/powf.s`, at least in Libm-315; I don't know which version is used on which OSX). I might look into the actual algorithm later, though it's not especially readable to me.... – Danica Jan 23 '13 at 01:14
  • @Dougal: That is an old version of OSX Libm, and that file is for the single-precision (float) powf. The double-precision (double) pow is in Intel/xmm_power.c. It has since been written in assembly quite nicely by Stephen Canon. And it will not tell us why Matlab is getting the results it is, since it is not the Matlab implementation. – Eric Postpischil Jan 23 '13 at 01:26
  • @EricPostpischil It seems pretty likely that matlab is calling the system `pow()`, since it gets exactly the same results as python and ruby, or at least is using the same algorithm in this case. – Danica Jan 23 '13 at 01:27
  • 1
    @Dougal: Showing the same bug would be evidence of a common implementation. Showing the same accurate result is merely evidence of good programming. – Eric Postpischil Jan 23 '13 at 01:35
  • As pointed out above, it doesn't actually get the most accurate number for me; I think you may have mixed up the numbers you got out from your test. – Danica Jan 23 '13 at 02:06
  • Having Ruby performs the same as most of the other languages is not surprising. I would have expected Mathlab to do better though... – Déjà vu Jan 23 '13 at 06:11
  • For your information, the exact same thing happens in the .NET Framework. I tried this with Microsoft Powershell: `[Math]::Pow(0.3, 3.0).ToString("R")` and `(0.3 * 0.3 * 0.3).ToString("R")`. Same results as above. The absolute difference between the two comes out as `3.4694E-18` (rounded by me). This is `2 ** -58` exactly. – Jeppe Stig Nielsen Jan 23 '13 at 22:34
4

Interestingly, scalar ^ seems to be implemented using pow while matrix ^ is implemented using square-and-multiply. To wit:

octave:13> format hex
octave:14> 0.3^3
ans = 3f9ba5e353f7ced8
octave:15> 0.3*0.3*0.3
ans = 3f9ba5e353f7ced9
octave:20> [0.3 0;0 0.3]^3
ans =

  3f9ba5e353f7ced9  0000000000000000
  0000000000000000  3f9ba5e353f7ced9

octave:21> [0.3 0;0 0.3] * [0.3 0;0 0.3] * [0.3 0;0 0.3]
ans =

  3f9ba5e353f7ced9  0000000000000000
  0000000000000000  3f9ba5e353f7ced9

This is confirmed by running octave under gdb and setting a breakpoint in pow.

The same is likely true in matlab, but I can't really verify.

tmyklebu
  • 13,915
  • 3
  • 28
  • 57
  • 2
    Same results in matlab. It's not super surprising, though, that matrix `^` uses square-and-multiply; `pow` doesn't exactly work on matrix multiplication. Note also that `.^` on matrices is (of course) the same as `^` on scalars. – Danica Jan 23 '13 at 05:28
  • It has been changed along matlab history, in on an old matlab 6.5.1, (0.3*([1 1;1 1])).^3 - 0.3^3 is not zero... So the of course should be moderated :) – aka.nice Jan 23 '13 at 14:56
3

Here's a little test program that follows what the system pow() from Source/Intel/xmm_power.c, in Apple's Libm-2026, does in this case:

#include <stdio.h>
int main() {
    // basically lines 1130-1157 of xmm_power.c, modified a bit to remove
    // irrelevant things

    double x = .3;
    int i = 3;

    //calculate ix = f**i
    long double ix = 1.0, lx = (long double) x;

    //calculate x**i by doing lots of multiplication
    int mask = 1;

    //for each of the bits set in i, multiply ix by x**(2**bit_position)
    while(i != 0)
    {
        if( i & mask )
        {
            ix *= lx;
            i -= mask;
        }
        mask += mask;
        lx *= lx; // In double this might overflow spuriously, but not in long double
    }

    printf("%.40f\n", (double) ix);
}

This prints out 0.0269999999999999962252417162744677625597, which agrees with the results I get for .3 ^ 3 in Matlab and .3 ** 3 in Python (and we know the latter just calls this code). By contrast, .3 * .3 * .3 for me gets 0.0269999999999999996946886682280819513835, which is the same thing that you get if you just ask to print out 0.027 to that many decimal places and so is presumably the closest double.

So there's the algorithm. We could track out exactly what value is set at each step, but it's not too surprising that it would round to a very slightly smaller number given a different algorithm for doing it.

Danica
  • 28,423
  • 6
  • 90
  • 122
3

Thanks to @Dougal I found this:

#include <stdio.h>
int main() {
  double x = 0.3;
  printf("%.40f\n", (x*x*x));

  long double y = 0.3;
  printf("%.40f\n", (double)(y*y*y));
}

which gives:

0.0269999999999999996946886682280819513835
0.0269999999999999962252417162744677625597

The case is strange because the computation with more digits gives a worst result. This is due to the fact that anyway the initial number 0.3 is approximated with few digits and hence we start with a relatively "large" error. In this particular case what happens is that the computation with few digits gives another "large" error but with opposite sign... hence compensating the initial one. Instead the computation with more digits gives a second smaller error but the first one remains.

Emanuele Paolini
  • 9,912
  • 3
  • 38
  • 64
  • 3
    I got it! What happens is a strange case where the superposition of two larger errors with opposite sign give (by chance) a smaller error then the superposition of the same first error with a smaller second error but with same sign. – Emanuele Paolini Jan 23 '13 at 06:26
-7

Read Goldberg's "What Every Computer Scientist Should Know About Floating-Point Arithmetic" (this is a reprint by Oracle). Do understand it. Floating point numbers are not the real numbers of calculus. Sorry, no TL;DR version available.

vonbrand
  • 11,412
  • 8
  • 32
  • 52