Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

math: all_test.go: test failures in TestLog2 on ppc64 (and s390) #9066

Closed
gopherbot opened this issue Nov 6, 2014 · 4 comments
Closed

math: all_test.go: test failures in TestLog2 on ppc64 (and s390) #9066

gopherbot opened this issue Nov 6, 2014 · 4 comments
Milestone

Comments

@gopherbot
Copy link

by vogt@linux.vnet.ibm.com:

This is a copy of the bug report from the gcc bugtracker.  See
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63269 for the mentioned patch.

--


Dominik Vogt 2014-09-15 14:10:37 UTC
------------------------------------
There are several flaws in the math library test code.  On s390x, the test TestLog2
fails for three reasons:

1) This part of the test is too strict for i == 0:

for i := -1074; i <= 1023; i++ { 
  f := Ldexp(1, i) 
  l := Log2(f) 
  if l != float64(i) { 
    t.Errorf("Log2(2**%d) = %g, want %d", i, l, i) 
  } 
} 

It should really use the veryclose function() as there is no guarantee that the result
of this calculation from log10.go is exact:

  return Log(frac)*(1/Ln2) + float64(exp)

With frac == 0.5 and exp == 1, the result may be just close to 0, not exactly zero:

* If the compiler generated two instructions, one to mulitply and one to add, the result
is (with a small error delta):

  Product: Log(0.5)*(1/Ln2) = -1 + delta -> rounded to -1
  Sum: -1 + 1 = 0 -> rounded to 0
  Result: 0

* If the compiler generates a single multiply and add instruction, only the final result
is rouced, i.e.

  Product: Log(0.5)*(1/Ln2) = -1 + delta (not rounded)
  Sum: -1 + delta + 1 = delta -> rounded to delta'
  Result: delta' != 0

So, the fix is to use the "veryclose" error tolerance.

2) There's a bug in the tolerance() function:

        if a != 0 { 
                e = e * a 
                if e < 0 { 
                        e = -e 
                } 
        } 

This snippet uses the defective value a but should use the expected value b instead:

        if b != 0 { 
                e = e * b 
                if e < 0 { 
                        e = -e 
                } 
        } 

Otherwise, bad things happen for an expected value 0 when the defective value is very
close, i.e. they never match.

3) The alike() function does allow an error tolerance.  This kicks in on s390x in the
case that the expected value is 0 and the defective value is not identical.

        switch { 
        case IsNaN(a) && IsNaN(b): 
                return true 
        case a == b: 
                return Signbit(a) == Signbit(b) 
        } 


A possible fix would be to detect this one situation and return true, and rely on more
specific tests to chack that the error is small ebough.

        switch { 
        case IsNaN(a) && IsNaN(b): 
                return true 
        case a == 0 && !IsNaN(b) && !IsInf(b, 0): 
                // allow deviations when the expected value is zero
                return true 
        case a == b: 
                return Signbit(a) == Signbit(b) 
        }


Comment 3 Ian Lance Taylor 2014-11-05 03:54:14 UTC
--------------------------------------------------
First, let me say that this code is in the Go master library and must be fixed there. 
It might be more effective to discuss it on the Go issue tracker at
http://golang.org/issue.

I don't agree with your argument for item 1.  You say that the wrong result happens when
using a fused multiply-add instruction.  The math library is compiled with
-ffp-contract=off (see MATH_FLAG in configure.ac and Makefile.am), so the compiler
should not be generating a fused multiply-add instruction.

I'm not entirely persuaded by your argument for item 2.  Zero is a special value.  When
we expect a zero, we should get a zero, not something close to zero.  I don't think this
change is correct in general.  It may be correct for some specific cases, but then we
need to investigate those.

Item 3 is the same sort of thing: when we expect zero, we should, in general, get
exactly zero.


Comment 4 Dominik Vogt 2014-11-05 14:34:38 UTC
----------------------------------------------
regarding 2)

> I'm not entirely persuaded by your argument for item 2. ...

Hm, good that you doubted it, because the actual mistake is somehwere else:  The
unpatched code has

  if l != float64(i)

but if you want to use a tolerance here this must become

  if !veryclose(float64(i), l) {

With the argument reversed.  This could/should be cleaned up by renaming the arguments
of the tolerance() function, e.g. a -> expected, b -> result, e -> maxerr.

> Zero is a special
> value.  When we expect a zero, we should get a zero, not something close to
> zero.  I don't think this change is correct in general.  It may be correct for
> some specific cases, but then we need to investigate those.

Actually, this has nothing to do with 0 being special here, abut with scaling of the
allowed error: Multiplying it by 0 yields zero error tolerance, so the tolerance()
function does not do that.

--> This chunk is not necessary, but a (separate) cleanup patch might help to avoid
future confusion.


Comment 5 Dominik Vogt 2014-11-05 16:47:03 UTC
----------------------------------------------

regarding 1)

My earlier explanation of the problem was wrong.  Multiply and add is not generated; it
probably only was in the artificial test case that I made and certainly did not compile
with -ffp-contract=off.

In this calculation in log2(),

  Log(frac)*(1/Ln2) + float64(exp)

Gcc does constant folding for (1/Ln2) and generates a multiply instruction and then adds
the second term.  Same result if you write "*Log2E" instead of
"*(1/Ln2)").  But with

  Log(frac)/Ln2 + float64(exp)

it generates a divide instruction.  The multiplication and the division yield results
that differ in the least significant bit, and I don't see how this could be prevented in
general; it's just an artifact of the floating point format.  I've verified that the
constants Ln2, 1/Ln2 and Log2E are bit correct.

The "easy" way to fix this is increasing the allowed tolerance as my patch
does (note that the arguments of the veryclose() call need to be swapped, see previous
comment).

The "right" way to fix this is to calculate platform specific ULPs for all the
algorithms from the math library and use these.  That's what glibc does.
@gopherbot
Copy link
Author

Comment 1 by vogt@linux.vnet.ibm.com:

regarding 3)
I think, the alike() function is broken by design for the same reason as (1).  It cannot
reasonably be expected that the result of the Log... functions is bit exact for all
input values (unless the math library would use a higher precision internally, which it
doesn't).  The test for equality in the alike() function is too strict.  Assuming thet
library code has been handmade to be bit exact given specific compiler, platform and
test cases it's not surprising that it fails with other platforms or compiler, e.g.
depending on optimisation choices.

@ianlancetaylor
Copy link
Contributor

Comment 2:

Labels changed: added repo-main, release-go1.5.

@gopherbot
Copy link
Author

Comment 3 by laboger@linux.vnet.ibm.com:

This testcase fails on ppc64 and ppc64le as well.

@bradfitz bradfitz modified the milestone: Go1.5 Dec 16, 2014
@rsc rsc removed the repo-main label Apr 14, 2015
@rsc
Copy link
Contributor

rsc commented Jul 15, 2015

(1) It seems reasonable to expect Log2 on powers of two to get exactly the right answer. Will fix the implementation.

(2) Thanks, will fix tolerance.

(3) alike is for exact matching. It's a stricter form of ==, for testing the outputs on special cases. We definitely do not want a tolerance there. I believe fixing Log2 on powers of two (in particular, Log2(1)) will make alike a non-issue.

@rsc rsc changed the title math: all_test.go: test failures in TestLog2 on s390 math: all_test.go: test failures in TestLog2 on ppc64 (and s390) Jul 15, 2015
@rsc rsc closed this as completed in bed6326 Jul 15, 2015
@golang golang locked and limited conversation to collaborators Jul 18, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants