gogofmt

Go fmt producing inconsistently formatted results for math expressions


My understanding is that go fmt is supposed to produce code that is readable and consistently formatted. However I'm not finding this to be the case.

I typed in the commented code and go fmt returned the uncommented code. Why did it collapse 0.5*(y3-y0), but not 0.5 * (y2 - y0) ? How is that consistent? And, IMO, the return line with nearly every space collapsed out is a readability disaster.

Is the inconsistency a bug? Is there a way to get go fmt to leave some lines (like the return line) alone?

func cubicInterpolate(x, y0, y1, y2, y3 float64) float64 {
    // 4-point, 3rd-order Hermite (x-form)
    // c0 := y1
    // c1 := 0.5 * (y2 - y0)
    // c2 := y0 - 2.5 * y1 + 2. * y2 - 0.5 * y3
    // c3 := 1.5 * (y1 - y2) + 0.5 * (y3 - y0)
    //
    // return ((c3 * x + c2) * x + c1) * x + c0

    c0 := y1
    c1 := 0.5 * (y2 - y0)
    c2 := y0 - 2.5*y1 + 2.*y2 - 0.5*y3
    c3 := 1.5*(y1-y2) + 0.5*(y3-y0)

    return ((c3*x+c2)*x+c1)*x + c0
}

Solution

  • This is documented in the go source code; the aim is to make operator precedence clear. For example in your example y0 - 2.5*y1 + 2.*y2 - 0.5*y3 the multiplications will be performed before the subtractions and the format chosen makes this clear at a glance.

    In answer to your question; this is not a bug; indeed quite a bit of effort was put into the formatting. You cannot exclude a line from formatting; this is by design, as mentioned in the FAQ, the aim is to enforce layont rules:

    gofmt is a pretty-printer whose purpose is to enforce layout rules; it replaces the usual compendium of do's and don'ts that allows interpretation.

    Below is a formatted extract from go/printer/nodes.go that details the formatting rules:

    Format the binary expression: decide the cutoff and then format. Let's call depth == 1 Normal mode, and depth > 1 Compact mode. (Algorithm suggestion by Russ Cox.)

    The precedences are: 5 * / % << >> & &^ 4 + - | ^ 3 == != < <= > >= 2 && 1 ||

    The only decision is whether there will be spaces around levels 4 and 5. There are never spaces at level 6 (unary), and always spaces at levels 3 and below.

    To choose the cutoff, look at the whole expression but excluding primary expressions (function calls, parenthesized exprs), and apply these rules:

    1. If there is a binary operator with a right side unary operand that would clash without a space, the cutoff must be (in order):
       /* 6   
       && 6   
       &^ 6   
       ++ 5   
       -- 5 
    

    (Comparison operators always have spaces around them.)

    1. If there is a mix of level 5 and level 4 operators, then the cutoff is 5 (use spaces to distinguish precedence) in Normal mode and 4 (never use spaces) in Compact mode.

    2. If there are no level 4 operators or no level 5 operators, then the cutoff is 6 (always use spaces) in Normal mode and 4 (never use spaces) in Compact mode.