-1

I have a struct type like this:

type Vertex struct {
    X, Y, Z float32
}

I intend to create a map with key of Vertex type:

var vertices map[Vertex]struct{}

The problem is Go would try to compare float32 with == and != which are not suitable for float32. Instead, I prefer an approximate comparison with an epsilon.

I have studied this post, but I don't know how to implement it, since my key struct has 3 fields. Can anybody help me use my struct as map key? Thanks.

Megidd
  • 7,089
  • 6
  • 65
  • 142
  • 6
    Note that approximate comparison is not transitive and cannot really be used in a map. All your keys are equal, if you compare them in close enough intervals. – Piotr Praszmo Jun 13 '20 at 09:52
  • 1
    Find another way to generate key float approximation produces different results based on operations performed, 0.1 may be 2 different values based on how you got it; a non recommended way will be to explicitly just store the float values up-to n decimal places in a string and use that maybe if it works. – Shubham Srivastava Jun 13 '20 at 09:59
  • 3
    If the builtin == is not suitable for your purpose you cannot use a Go `map`. It is really that simple. – Volker Jun 13 '20 at 13:03

1 Answers1

0

Idea

Inspired by @ShubhamSrivastava comment, the idea is:

  • float32 coordinates would be rounded to a specific decimal point

Rationale

Since exact == and != comparisons (required by map key) are NOT proper for float, such comparisons make more sense when float32 is rounded consistently to a limited decimal point.

Implementation

I round my float32 fields inside Vertex type by these methods to a specific decimal point:

// round floating point fields of my type to a specific decimal point
func vertRoundTo(vi Vertex, decimals uint32) (vo Vertex, err error) {
    if vo.X, err = roundTo(vi.X, decimals); err != nil {
        return Vertex{}, err
    }
    if vo.Y, err = roundTo(vi.Y, decimals); err != nil {
        return Vertex{}, err
    }
    if vo.Z, err = roundTo(vi.Z, decimals); err != nil {
        return Vertex{}, err
    }
    return
}

// round float32 to a specific decimal point
// https://stackoverflow.com/a/52048478/3405291
func roundTo(fi float32, decimals uint32) (fo float32, err error) {
    if decimals < 1 {
        err = errors.New("minimum decimal point is exceeded")
        return
    }
    fo = float32(math.Round(float64(fi)*float64(decimals)) / float64(decimals))
    return
}

I use the above methods like this:

// "cluster" is already filled with rounded floats
func (c *cluster) DoesClusterContainVertex(v Vertex) (does bool, err error) {
    // coordinates would be rounded to a specific decimal point
    // since exact == and != comparison is NOT proper for float
    // such comparisons are required for map key
    var vRound Vertex
    if vRound, err = vertRoundTo(v, 4); err != nil {
        return
    }
    if _, ok := c.verts[vRound]; ok {
        // cluster contains vertex
        does = true
    }
    return
}
Megidd
  • 7,089
  • 6
  • 65
  • 142