Here's the sample code in go:
package main
import "fmt"
func mult32(a, b float32) float32 { return a*b }
func mult64(a, b float64) float64 { return a*b }
func main() {
fmt.Println(3*4.3) // A1, 12.9
fmt.Println(mult32(3, 4.3)) // B1, 12.900001
fmt.Println(mult64(3, 4.3)) // C1, 12.899999999999999
fmt.Println(12.9 - 3*4.3) // A2, 1.8033161362862765e-130
fmt.Println(12.9 - mult32(3, 4.3)) // B2, -9.536743e-07
fmt.Println(12.9 - mult64(3, 4.3)) // C2, 1.7763568394002505e-15
fmt.Println(12.9 - 3*4.3) // A4, 1.8033161362862765e-130
fmt.Println(float32(12.9) - float32(3)*float32(4.3)) // B4, -9.536743e-07
fmt.Println(float64(12.9) - float64(3)*float64(4.3)) // C4, 1.7763568394002505e-15
}
Results differences between lines A1, B1 and C1 are understandable. However, starting from A2 to C2 magic comes. Result from neither of B2 nor C2 matches the result from A2 line. The same is true for lines x2 (x = A, B or C) - but the outputs of x2 and x4 are the same.
Just to be sure let's print the results in the binary form.
fmt.Printf("%b
", 3*4.3) // A11, 7262054399134925p-49
fmt.Printf("%b
", mult32(3, 4.3)) // B11, 13526631p-20
fmt.Printf("%b
", mult64(3, 4.3)) // C11, 7262054399134924p-49
fmt.Printf("%b
", 12.9 - 3*4.3) // A12, 4503599627370496p-483
fmt.Printf("%b
", 12.9 - mult32(3, 4.3)) // B12, -8388608p-43
fmt.Printf("%b
", 12.9 - mult64(3, 4.3)) // C12, 4503599627370496p-101
fmt.Printf("%b
", 12.9 - 3*4.3) // A14, 4503599627370496p-483
fmt.Printf("%b
", float32(12.9) - float32(3)*float32(4.3)) // B14, -8388608p-43
fmt.Printf("%b
", float64(12.9) - float64(3)*float64(4.3)) // C14, 4503599627370496p-101
Some facts from the code above (one in the bin form):
- There is difference between line A11 and C11 (last digit - just before the exponent).
- Lines A12 and C12 are almost the same (except the exponen!!!), the same can be observed between line A14 and C14.
And here the questions come:
- How computations of bare (naked :)) numbers are performed? (computations in every Axx line)
- Are they performed by compiler/whatever?
- If yes, then why are they different? Optimisation?
- Are they computed in some system which differs from IEE-754?
- If yes, why so?
- Is achieving more accurate precision justifiest such approach?
Code has been tested on 64bit linux under both "go run" and "go build" (go1.0.3), and also on that site: http://tour.golang.org/