Using float32 as map key returns unexpected result
package main
import "fmt"
func main() {
result := make(map[float32]map[float32]float32)
var t1 float32 = 1586238540
var t2 float32 = 1586238600
result[t1] = map[float32]float32{1:1,2:2}
result[t2] = map[float32]float32{3:3,4:4}
fmt.Println(result[t1])
fmt.Println(result[t2])
}
map[3:3 4:4]
map[3:3 4:4]
Go version: go version go1.14 linux/amd64
Changing result to map[float64]map[float32]float32 and t1, t2 accordingly gives the right result.
What could be a reason for this weird behavior?
A 32 bit float has a 23 bit mantissa, with an implicit preceeding highest 1 bit. So the maximum value representable by the mantissa is 2²⁴-1 = 16777215. In other words only integer numbers between -16777215 to 16777215 can be exactly represented as a 32 bit float.
Your two values 1586238540 and 1586238600 are outside that range and both get truncated to the same value 1586238592. And it's that truncated value that's being used as key for the map.
https://play.golang.org/p/Fx78BbmnXIE, 1586238540 and 1586238600 are same in memory
if you add this to your code
fmt.Println(t1)
fmt.Println(t2)
you'll see 1.5862386e+09 as result for both because the value is too big for a float32. with float64 you'll see the proper value printed
1.58623854e+09
1.5862386e+09
for more info wikipedia
Related
I have a scenario where I receive a float64 value, but must send it down the wire to another service as a float32 value. We know the received value should always fit into a float32. However, to be safe I want to log the case where we are losing data by converting to float32.
This code block does not compile, since you can't compare float32 to float64 directly.
func convert(input float64) (output float32, err error) {
const tolerance = 0.001
output = float32(input)
if output > input+tolerance || output < input-tolerance {
return 0, errors.New("lost too much precision")
}
return output, nil
}
Is there an easy way to check that I am hitting this condition? This check will happen at high frequency, so I want to avoid doing string conversions.
You can convert back the float32 value to float64, just for the validation.
To check if the converted value represents the same value, simply compare it to the original value (the input). It's also enough / idiomatic to just return an ok bool info (instead of an error):
func convert(input float64) (output float32, ok bool) {
output = float32(input)
ok = float64(output) == input
return
}
(Note: edge cases like NaN are not checked.)
Testing it:
fmt.Println(convert(1))
fmt.Println(convert(1.5))
fmt.Println(convert(0.123456789))
fmt.Println(convert(math.MaxFloat32))
Output (try it on the Go Playground):
1 true
1.5 true
0.12345679 false
3.4028235e+38 true
Note that this will often give ok = false result because the precision of float32 is less than that of float64, even though the converted value may be very close to the input.
So in practice it would be more useful to check the difference of the converted value. Your proposed solution checks for the absolute difference value which is not so useful: for example 1000000.1 and 1000000 are very close numbers, even though the difference is 0.1. 0.0001 and 0.00011 have much less difference: 0.00001, yet the difference compared to the numbers is much bigger.
So you should check the relative difference, for example:
func convert(input float64) (output float32, ok bool) {
const maxRelDiff = 1e-8
output = float32(input)
diff := math.Abs(float64(output) - input)
ok = diff <= math.Abs(input)*maxRelDiff
return
}
Testing it:
fmt.Println(convert(1))
fmt.Println(convert(1.5))
fmt.Println(convert(1e20))
fmt.Println(convert(math.Pi))
fmt.Println(convert(0.123456789))
fmt.Println(convert(math.MaxFloat32))
Output (try it on the Go Playground):
1 true
1.5 true
1e+20 false
3.1415927 false
0.12345679 false
3.4028235e+38 true
Yes. Check that the value does not exceed the upper or lower value limit. Then ensure the 52 - 23 least significant bits are 0. (in a nutshell)
package main
import (
"fmt"
)
func main(){
//float to int
fmt.Println(int64(1.9))
}
I got syntax error "Cannot convert expression of type 'float64' to type 'int64'", how to rectify it?
Type conversions have special rules for constants:
A constant value x can be converted to type T if x is representable by
a value of T.
And even gives the following example:
int(1.2) // illegal: 1.2 cannot be represented as an int
If you really insist on truncating your float into an int, use a variable as an intermediary turning it into a non-constant conversion. Go will happily do the conversion and drop the fractional part, as mentioned further down in the spec:
When converting a floating-point number to an integer, the fraction is
discarded (truncation towards zero).
So you can use the following:
package main
import (
"fmt"
)
func main() {
//float to int
f := 1.9
fmt.Println(int64(f))
}
Which outputs 1 as expected.
Or use one of the functions in the math package if you want finer control over rounding vs truncation.
Jut use the math package:
int64(math.Floor(1.9))
playground
I am trying to grasp Golang, in one of the tutorial example it says that An untyped constant takes the type needed by its context.
package main
import "fmt"
const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100
// Shift it right again 99 places, so we end up with 1<<1, or 2.
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
// Here Big is too large of a number but can be handled as a float64.
// No compilation error is thrown here.
fmt.Println(needFloat(Big))
// The below line throws the following compilation error
// constant 1267650600228229401496703205376 overflows int
fmt.Println(Big)
}
When calling fmt.Println(Big) why is Golang treating Big as an int where as by context it should be float64?
What am I missing?
What is the context for fmt.Println? In other words, what does fmt.Println expect Big to be? An interface{}.
From the Go Blog on Constants:
What happens when fmt.Printf is called with an untyped constant is that an interface value is created to pass as an argument, and the concrete type stored for that argument is the default type of the constant.
So the default type of the constant must be an int. The page goes on to talk about how the defaults get determined based on syntax, not necessarily the value of the const.
Big in fmt.Println(Big) has type integer which is more than max int value 9223372036854775807
you can find max int from this logic
const MaxUint = ^uint(0)
const MaxInt = int(MaxUint >> 1)
fmt.Println(MaxInt) // print 922337
2036854775807
To fix it, you need to cast it to float64 like this
fmt.Println(float64(Big))
I'm just learning golang using the official tour/tutorial. In one of the examples, I see a note that says An untyped constant takes the type needed by its context.
I'm trying this:
package main
import "fmt"
const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100
)
func main() {
fmt.Printf("Big is of type %T\n", Big)
}
But this fails when I run it, with:
# command-line-arguments
./compile63.go:12:13: constant 1267650600228229401496703205376 overflows int
Why am I unable to discover the type of the constant this way? (Please note I'm a total noob and quite possibly haven't yet discovered enough about the language to be able to solve this myself).
func Printf
func Printf(format string, a ...interface{}) (n int, err error)
Printf formats according to a format specifier and writes to standard
output. It returns the number of bytes written and any write error
encountered.
The Go Programming Language
Specification
Variable
declarations
A variable declaration creates one or more variables, binds
corresponding identifiers to them, and gives each a type and an
initial value.
If a list of expressions is given, the variables are initialized with
the expressions following the rules for assignments. Otherwise, each
variable is initialized to its zero value.
If a type is present, each variable is given that type. Otherwise,
each variable is given the type of the corresponding initialization
value in the assignment. If that value is an untyped constant, it is
first converted to its default type; if it is an untyped boolean
value, it is first converted to type bool. The predeclared value nil
cannot be used to initialize a variable with no explicit type.
Constants
Constants may be typed or untyped. Literal constants, true, false,
iota, and certain constant expressions containing only untyped
constant operands are untyped.
A constant may be given a type explicitly by a constant declaration or
conversion, or implicitly when used in a variable declaration or an
assignment or as an operand in an expression. It is an error if the
constant value cannot be represented as a value of the respective
type.
An untyped constant has a default type which is the type to which the
constant is implicitly converted in contexts where a typed value is
required, for instance, in a short variable declaration such as i := 0
where there is no explicit type. The default type of an untyped
constant is bool, rune, int, float64, complex128 or string
respectively, depending on whether it is a boolean, rune, integer,
floating-point, complex, or string constant.
Numeric types
A numeric type represents sets of integer or floating-point values.
Some predeclared architecture-independent numeric types:
int32 the set of all signed 32-bit integers (-2147483648 to 2147483647)
int64 the set of all signed 64-bit integers (-9223372036854775808 to 9223372036854775807)
The value of an n-bit integer is n bits wide and represented using
two's complement arithmetic.
There are also some predeclared numeric types with
implementation-specific sizes:
uint either 32 or 64 bits
int same size as uint
Big is an untyped constant. There is no type to discover. It is given a type when used in a variable or assignment. The default type for the untyped constant Big is int.
const Big = 1 << 100
In Go, all arguments are passed by value as if by assignment. For fmt.Printf, the second argument is of type interface{}. Therefore, equivalently,
var arg2 interface{} = Big // constant 1267650600228229401496703205376 overflows int
fmt.Printf("Big is of type %T\n", arg2)
The default type for an untyped integer constant is int. Overflow is a compile-time error.
For example,
package main
import "fmt"
const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100
)
func main() {
var arg2 interface{} = Big
fmt.Printf("Big is of type %T\n", arg2)
fmt.Printf("Big is of type %T\n", Big)
}
Playground: https://play.golang.org/p/9tynPTek3wN
Output:
prog.go:12:6: constant 1267650600228229401496703205376 overflows int
prog.go:15:13: constant 1267650600228229401496703205376 overflows int
Reference: The Go Blog: Constants
While it is true that an untyped constant takes the type needed by its context, the type it can assume is limited by the primitives of the language, so a constant that big is not actually usable anywhere in the code, due to the fact that it does not fit even in an uint64. The only use it could have would be that of using it in another constant expression, because otherwise that error will always be thrown.
Note that in Printf (and similar functions), the constant is converted to an interface{}, and thus it takes a type of int by default. For 32-bit machines, you need to do a type conversion first if you have a constant expression which overflows int32.
const i = 1 << 50
fmt.Println(i) // => constant 1125899906842624 overflows int
fmt.Println(int64(i)) // => 1125899906842624
If you want to do proper arithmetic on arbitrarily big numbers, there is a handy package: math/big.
i := big.NewInt(1)
i.Lsh(i, 100)
fmt.Println(i.String())
Playground
I am currently doing the Go Lang tutorial, "Numeric Constants" to be precise. The example code starts with the following statement:
const (
// Create a huge number by shifting a 1 bit left 100 places.
// In other words, the binary number that is 1 followed by 100 zeroes.
Big = 1 << 100
// Shift it right again 99 places, so we end up with 1<<1, or 2.
Small = Big >> 99
)
The constant Big is obviously huge, and I am trying to print it and its type, like this:
fmt.Printf("%T", Big)
fmt.Println(Big)
However, I get the following error for both lines:
# command-line-arguments ./compile26.go:19: constant 1267650600228229401496703205376 overflows int
I would try casting Big to some other type, such as uint64, which it overflowed with the same error, or just convert it to a string, but when trying Big.String() I get the following error:
Big.String undefined (type int has no field or method String)
It appears that its type is int, yet I can't print it or cast it to anything and it overflows all methods. What do I do with this number/object and how do I print it?
That value is larger than any 64 bit numeric type can hold, so you have no way of manipulating it directly.
If you need to write a numeric constant that can only be manipulated with the math/big package, you need to store it serialized in a format that package can consume. Easiest way is probably to use a base 10 string:
https://play.golang.org/p/Mzwox3I2SL
bigNum := "1267650600228229401496703205376"
b, ok := big.NewInt(0).SetString(bigNum, 10)
fmt.Println(ok, b)
// true 1267650600228229401496703205376