Skip to content

math/rand: Float32() and Float64() are not uniformly distributed #4965

@gopherbot

Description

@gopherbot

by arnehormann:

What steps will reproduce the problem?

See http://play.golang.org/p/uZw1CbxcMx
It uses a constant source (returns seed, sets seed before each call to Float64) to
inspect the distance between uint64 seeds and the generated float64 values.


What is the expected output?

The distance between all adjacent generated float64 values in range [0,1) should be the
same and two adjacent uint64 should not be mapped to the same float64.
The output of the referenced program should not print anything below the line
"======="


What do you see instead?

Intent: illustrate problems with http://golang.org/src/pkg/math/rand/rand.go?#L92
f0  (0) :=  0p-1074 (0)
f1  (1) :=  4503599627370496p-115   (1.0842021724855044e-19)
high    (9223372036854775295) :=    9007199254740991p-53    (0.9999999999999999)
higher  (9223372036854775296) :=    4503599627370496p-52    (1)
max (9223372036854775807) :=    4503599627370496p-52    (1)
=======
max == higher, but the source uint64 difference is 511
 - the mapping uint63 to float64 has 'holes' for adjacent numbers
distances between adjacent float64s are different
 - the largest difference is 8998403161718784p-106 (1.109138822452671e-16)
   which is more than f1 - f0: 4503599627370496p-115 (1.0842021724855044e-19)


Which compiler are you using (5g, 6g, 8g, gccgo)?

play.golang.org and locally 6g


Which operating system are you using?

OS X Mountain Lion


Which version are you using?  (run 'go version')

go version devel +b0c8d47acfc5 Sat Mar 02 10:41:53 2013 +0400 darwin/amd64


Please provide any additional information below.

The calculation used in rand.Float64 (and Float32) uses 63 (31) bits of the passed
integer although the mantissa is only 52 (23) bits long. The binary representation is
not able to contain all possible values and creates a "kind of" logarithmic
distribution with an increasing amount of ints mapped to the same float the nearer it
gets to 1.0. Not having an uniform distribution and this bias probably also influence
the creation of numbers in different (e.g. normal) distributions.

I propose using one of these ways:

either create a number in [1,2) (which are always in uniform distribution) and subtract
1:

func UniformFloat64(b uint64) float64 {
    const range_1_2Exp = 0x3ff0000000000000
    const mantissaMask = 0x000fffffffffffff
    return math.Float64frombits(range_1_2Exp|(mantissaMask&b)) - 1.0
}

or create the smallest legal "step" and multiply it with a number masked to be
in range [0, max mantissa]:

func UniformFloat64(b uint64) float64 {
    const mantissaMask = 0x000fffffffffffff
    step := math.Float64frombits(0x3cb0000000000000)
    return step * float64(mantissaMask&b)
}

Both create the same float for the same input, I didn't benchmark them yet.

I have some tests and code ready if you are interested, I only need to know if the
existing functions can safely be replaced or there may be some weird dependency on their
behavior.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions