C function return value of type float changes when used with COG

319 views
Skip to first unread message

Robert M. Münch

unread,
Feb 1, 2021, 12:14:35 AM2/1/21
to golang-nuts
I have a C library with some test-cases (using googletest) that all pass. I created Go bindings for the C lib and converted the tests to testify format.

Some Go test fails because a return value from the C function (type float) is different than expected. For example:

ASSERT_FLOAT_EQ(10, GetHeight(root)); // C code works

assert.EqualValues(t, 10, root.Height()) // Go code fails
The returned value in Go is: float32(1e+21) instead of float32(10). I'm really wondering how this can happen. It looks like somewhere in the FFI, the value changes. But what puzzles me, I'm using the same Go tests over and over in other tests, and things are working.

Does anyone have an idea what could be the problem?

Ian Lance Taylor

unread,
Feb 1, 2021, 12:33:49 AM2/1/21
to Robert M. Münch, golang-nuts
We need more information. Can you show us a small self-contained
program that demonstrates the problem? Please also tell us details
about the system on which you are running.

Ian

peterGo

unread,
Feb 1, 2021, 12:48:20 AM2/1/21
to golang-nuts
Robert,

Advanced googletest Topics

Floating-Point Comparison

Comparing floating-point numbers is tricky. Due to round-off errors, it is very unlikely that two floating-points will match exactly. Therefore, ASSERT_EQ 's naive comparison usually doesn't work. And since floating-points can have a wide value range, no single fixed error bound works. It's better to compare by a fixed relative error bound, except for values close to 0 due to the loss of precision there.

In general, for floating-point comparison to make sense, the user needs to carefully choose the error bound. If they don't want or care to, comparing in terms of Units in the Last Place (ULPs) is a good default, and googletest provides assertions to do this.

ASSERT_FLOAT_EQ(val1, val2); EXPECT_FLOAT_EQ(val1, val2);   the two float values are almost equal
ASSERT_DOUBLE_EQ(val1, val2);
EXPECT_DOUBLE_EQ(val1, val2);    the two double values are almost equal

By "almost equal" we mean the values are within 4 ULP's from each other.

What does testify do?

Peter

Robert M. Münch

unread,
Feb 1, 2021, 4:51:48 PM2/1/21
to golang-nuts
I know all about the problems of FP comparison, and yes I know that I need to provide more information. What makes me suspicious is, that I have a bunch of equivalent tests before the failing one, and all pass. I still believe in the determinism of programs. Hence, this is all really strange.

I run the C test code (without any Go) in GDB and the values are correctly set. This is how the data looks before the call, setting the correct values:

804       YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR);
(gdb) p root.layout_.dimensions
$9 = {
  _M_elems = {nan(0x400000), nan(0x400000)}
}

and this is after the call:

(gdb) p root.layout_.dimensions
$13 = {
  _M_elems = {320, 10}
}

I write a C wrapper function, that outputs the return value just before it's returned and makes its way through the Go FFI. Here is the output before the call:

YGNodeLayoutGetWidth: 1.#QNAN0  7FC00000
YGNodeLayoutGetHeight: 1.#QNAN0 7FC00000

and after the call

YGNodeLayoutGetWidth: 320.000000        43A00000
YGNodeLayoutGetHeight: 1000000020040877300000.000000    6258D727

So, why are the values before the call are different in C and the C side within the Go program? I mean why once 0x400000 and 0x7FC00000? I would have expected that the C code behaves the same.
And as you can see the 320 is set in both cases, but the expected 10 is a totally screwed up floating-point value.

Could this be an alignment problem? Does CGO add some wrapping code, which does some strange things?

Unfortunately, I didn't find a way, how I can enter the C code from a debugger when using the Go binary. Looks like there is no C debug information available.

Ian Lance Taylor

unread,
Feb 1, 2021, 6:58:01 PM2/1/21
to Robert M. Münch, golang-nuts
On Mon, Feb 1, 2021 at 1:52 PM Robert M. Münch
<robert...@saphirion.com> wrote:
>
> I know all about the problems of FP comparison, and yes I know that I need to provide more information. What makes me suspicious is, that I have a bunch of equivalent tests before the failing one, and all pass. I still believe in the determinism of programs. Hence, this is all really strange.
>
> I run the C test code (without any Go) in GDB and the values are correctly set. This is how the data looks before the call, setting the correct values:
>
> 804 YGNodeCalculateLayout(root, YGUndefined, YGUndefined, YGDirectionLTR);
> (gdb) p root.layout_.dimensions
> $9 = {
> _M_elems = {nan(0x400000), nan(0x400000)}
> }
>
> and this is after the call:
>
> (gdb) p root.layout_.dimensions
> $13 = {
> _M_elems = {320, 10}
> }
>
> I write a C wrapper function, that outputs the return value just before it's returned and makes its way through the Go FFI. Here is the output before the call:
>
> YGNodeLayoutGetWidth: 1.#QNAN0 7FC00000
> YGNodeLayoutGetHeight: 1.#QNAN0 7FC00000
>
> and after the call
>
> YGNodeLayoutGetWidth: 320.000000 43A00000
> YGNodeLayoutGetHeight: 1000000020040877300000.000000 6258D727
>
> So, why are the values before the call are different in C and the C side within the Go program? I mean why once 0x400000 and 0x7FC00000? I would have expected that the C code behaves the same.
> And as you can see the 320 is set in both cases, but the expected 10 is a totally screwed up floating-point value.
>
> Could this be an alignment problem? Does CGO add some wrapping code, which does some strange things?
>
> Unfortunately, I didn't find a way, how I can enter the C code from a debugger when using the Go binary. Looks like there is no C debug information available.

cgo adds multiple wrapping functions.

I don't know what the problem is here. I can imagine several
different possibilities. Show us the code, and we won't have to
guess. Thanks.

Ian

Robert M. Münch

unread,
Feb 2, 2021, 2:58:39 AM2/2/21
to golang-nuts

Here we go: https://pastebin.com/6c6rq92K

The code to access the C lib was generated with c-for-go, so there is a lot of house-keeping stuff going on. However, I don't think this has any influence on the C code data.

I implemented getCHeight (see commented lines 155ff, to access the C data as direct as possible. The functions prints the output as in my previous post.

Alex

unread,
Feb 2, 2021, 3:52:55 PM2/2/21
to golang-nuts
You need to provide a complete runnable example, the sniplet you provided is missing all the interesting parts.

Robert M. Münch

unread,
Feb 3, 2021, 5:34:50 AM2/3/21
to golang-nuts

Well, it's a huge code-base, so I need to figure out how to nail it down to something manageable. What are the interesting parts? Maybe I can provide some more information pretty easily.

Alex

unread,
Feb 3, 2021, 9:10:10 AM2/3/21
to golang-nuts
The interesting parts would be everything needed to run the program and have the same/similar output.
Which would include any C/C++ code.

The only way to be more accurate than that would be to already know what the problem is, sadly.

Robert M. Münch

unread,
Feb 3, 2021, 2:11:14 PM2/3/21
to golang-nuts
So, problem solved!!

The Yoga library uses C float NaN for control flow and as function parameters. The bindings (parts created with C-for-Go, and merged with some other parts I found) defined a totally scrappy Undefined constant.

Setting this constant to math32.NaN() solved the problem.
Reply all
Reply to author
Forward
0 new messages