Converting from []u8 to a f64

Hi,

I have converted an f64 to a slice of bytes. This has then been passed over the network to a server. The server now need to convert it back, but how ever I do it I end up with a different number than what I started with. I am now trying the whole process on the client side to exclude possible network issues. I still get the wrong result.

The way I convert f64 → u8 is:
bytes = slice.bytes_from_ptr(&value, 8)

how would you tell me to convert it back? I can not even remember all the ways I have tried, but one way was:

slice.to_type(f64, bytes)

and

temp := &bytes;
 f64_ptr := transmute(^f64)temp;
value: f64 = f64_ptr^;

Both compiles, but the input and resulting f64 are completely different.
Thank you!

The pointer-cast approach won’t work, because &bytes doesn’t point at the actual byte data, it points at the control data for the slice (effectively a Raw_Slice). What you’ll actually get is the pointer to the byte data, re-interpreted as a f64.

The slice.to_type approach should work, though; that’s exactly what it’s for. However, keep in mind that a slice is just a pointer, and slice.bytes_from_ptr returns a slice that just points at the data. So if that value variable no longer exists (the proc it’s a local variable in has returned, for instance) then the data in the slice may not contain the float anymore.

You may need to give a bit more complete example as to what you’re trying to do.

Thank you @Barinzaya for your quick response. I will provide a full program that demonstrate the issue.


package main

import "core:fmt"
import "core:slice"

f64_to_bytes_1 :: proc(bytes: ^[]u8, value: f64) {
    value := value
    bytes^ = slice.bytes_from_ptr(&value, 8)
}

f64_to_bytes_2 :: proc(value: f64) -> []u8 {
    value := value
    bytes := slice.bytes_from_ptr(&value, 8)
    return bytes
}


bytes_to_f64 :: proc(bytes: []u8) -> f64 {
    return slice.to_type(bytes, f64)
}

main :: proc() {
    float: f64 = 2.0
 
    //First attempt
    bytes_1 := []u8{}
    f64_to_bytes_1(&bytes_1, float)

    // Second attempt
    bytes_2 := f64_to_bytes_2(float)

    new_float_1 := bytes_to_f64(bytes_1)
    new_float_2 := bytes_to_f64(bytes_2)

    fmt.println("1:", new_float_1, " 2:", new_float_2)

}

The output from this is:
1: 0 2: 6.95303035376143e-310

None of these are correct.

However! I do see that when I use slice.bytes_from_ptr() in the main function it works! I just don’t know how to make it work in a separate procedure. And that is what I need… Thank you again for your support.

So yeah, exactly as I said:

So if that value variable no longer exists (the proc it’s a local variable in has returned, for instance) then the data in the slice may not contain the float anymore.

That’s exactly what’s happening in both of your f64_to_bytes procs; the value you’re taking a pointer to is only valid for the lifetime of the proc since it’s a local variable. The slice you return points to that local variable, and the memory for that variable may be reused after the proc exits, in which case the slice will end up pointing to something different.

Remember: a slice is just a pointer and a length. What you’re doing is effectively this, but with a slice instead of a raw pointer:

bad_pointer :: proc () -> ^f64 {
  x := 123.0
  return &x
}

The compiler will in fact error on this, precisely because it’s incorrect. But the compiler’s checking is very basic, so it’s not catching it in your case. Making this checking more thorough isn’t planned for the compiler, either.

Some solutions are:

  • Return an [8]u8 instead of a slice; unlike a slice, a fixed array is its data, it doesn’t point to something else. So returning [8]u8 actually returns a copy of the 8 bytes that make up the f64, rather than a pointer to bytes somewhere else. You can do this with justtransmute([8]u8)value–you may not even need a proc for it.
  • Pass in a pointer to the float, so that you can point to it wherever it is.
f64_to_bytes :: proc (value: ^f64) -> []u8 {
  return slice.bytes_from_ptr(value, size_of(f64))
}

main :: proc () {
  value := 123.0
  value_bytes := f64_to_bytes(&value)
  // value_bytes is valid as long as value exists--so until main returns
  // additionally, if you modify value, value_bytes will show those changes as well
}

This works because the actual memory for value doesn’t become invalid after f64_to_bytes exits, since it exists in main. You do still have to be careful about its lifetime if you had more procs, mind you–it’s just not limited to the f64_to_bytes proc, so you get a chance to actually use the bytes.

Thank you! This was very educational for me. I learned a lot about slices that I had not realized.

I fixed the code, but I still have issues. This time it is a lot stranger.

Most of the time my code now behaves as expected. I get back the float I put in, and there is no issues. But for some, specific, numbers I get the wrong answer.

Code:

package main

import "core:fmt"
import "core:slice"

f64_to_bytes :: proc(value: f64) -> [8]u8 {
    value := value
    bytes := slice.bytes_from_ptr(&value, 8)
    result := [8]u8{}

    for i in 0..<8 {
        result[i] = bytes[i]
    }
    return result
}

bytes_to_f64 :: proc(bytes: [8]u8) -> f64 {
    bytes := bytes
    result := bytes[:]
    return slice.to_type(result, f64)
}

main :: proc() {
    float: f64 = 3.4
    bytes := f64_to_bytes(float)
    new_float := bytes_to_f64(bytes)
    fmt.println("Result:", new_float)
}

if float is set to most numbers, all is well, but if it is set to 3.4 I get:
Result: 3.3999999999999999
What might cause this?

That looks like a floating point error. Because of how floats are stored in memory as binary data some values are simply not accurately representable.

1 Like

Note that you can just do:

f64_to_bytes :: proc(value: f64) -> [8]u8 {
    return transmute([8]u8)value
}

You can also use transmute in the bytes_to_f64 case, though you’re probably going to be coming from a []u8 instead since you’ll be pulling parts out of a larger buffer, in which case slice.to_type is still going to be the easier option.

But yeah, that’s just a matter of floating-point precision. You’ll get the same if you just fmt.println(3.4), it’s not an issue with the conversion to bytes and back, but just a limitation of the floating-point format that computers use. It’s base 2, not base 10, so it’s not exact with decimal fractions.

Roign’s link gives a lot of detail, but for a more succinct description of this particular issue, and to see how many languages this really applies to, see also: https://0.30000000000000004.com. Even among those that do show “0.3”, it’s still possible that they may be just masking the imprecision when they format the number–unless they use a more complex (and slower) arbitrary-precision or decimal number format, they’re going to have the same behavior. It’s not really a language-specific thing–that’s just how the hardware works.

1 Like

Thank you so much everybody.