I would like to print a slice of float64 values line by line having all decimal points at the same position in each line. I could print the integer part right justified, the decimal point and then the fractional part left justified. I assume, there is a better way, but I could not find it so far. Any proposals?
2 Answers
We have two cases here: either we want to keep the original precision of the float (which imho does not make sense, more on that later), or we have a fixed precision.
Printf in general
You can do a fmt.Printf("%<width>.<precision>f\n",someFloat), where <width> would denote the overall length of the printed result and precision the number of decimal places, right-padded with 0 if there are less decimal places in someFloat.
Original precision
What you want to do is to calculate the length of the longest decimal digit in your slice and calculate <width> accordingly, since the decimal places really do not matter much once the decimal point is aligned. We only have to ensure that we are aware of the number of decimal places in order to not have them padded.
That would look something like this:
package main import ( "fmt" "strings" ) func printJustifyFloats(floats []float64) { indent := 0 // Find the longest decimal digit. // We need to do this beforehand, as the longest decimal dictates // our padding, since in "%n.mf" the n would be all the places, including // the decimal point. for _, f := range floats { if l := len(strings.Split(fmt.Sprintf("%f", f), ".")[0]); l > indent { indent = l } } for _, p := range floats { // In order to prevent unnecessary trailing 0, we calculate the precision actually needed. precision := len(strings.TrimRight(strings.Split(fmt.Sprintf("%f", p), ".")[1], "0")) if precision == 0 { // We need to do this, otherwise a float with only a 0 in decimal places would // only have its decimal digit printed precision = 1 } // Here is where the magic happens: // We plainly use the calculated length of the decimal digit plus the precision // specific to the current p and add 2 (one for the decimal point, one for the leading space), // and print the current p according to the resulting format string. fmt.Printf("% *.*f\n", indent+precision+2, precision, p) } } func main() { printJustifyFloats([]float64{1.0, 11.9, 111.001, 1111.01}) } Result:
1.0 11.9 111.001 1111.01 Fixed precision
That being said: The above approach does not make much sense, imho. I cannot think of a use case where 0-padding to the right would be harmful and where the order of magnitude of the maximum results are unknown. With fixed width and precision, floats are automatically printed right justified:
package main import "fmt" var floats = []float64{ 1.0, 11.9, 111.001, 1111.01, } func main() { for _, f := range floats { fmt.Printf("%9.3f\n", f) } } Result:
1.000 11.900 111.001 1111.010 Comments
f := []float64{ 1.0, 11.9, 111.001, 1111.01, } // To determine how much we want to ident. stringIdent := 0 for i := range f { r := int64(math.Floor(f[i])) integerPart := fmt.Sprintf("%d", r) if stringIdent < len(integerPart) { stringIdent = len(integerPart) } } format := "%d: %" + fmt.Sprintf("%d", stringIdent) + "d.%s\n" for i := range f { d := int64(f[i]) r := f[i] - math.Floor(f[i]) reminderCleaned := fmt.Sprintf("%f", r) reminderCleaned = strings.TrimRight(reminderCleaned[2:], "0") if len(reminderCleaned) == 0 { reminderCleaned = "0" } fmt.Printf(format, i, d, reminderCleaned) } Technically fmt.SprintF("%5s","abc") would result in abc (space space "abc").