Suppose you are gambling at a casino where you can bet any amount between and dollars. If you win the bet (which happens with probability ) you get back the amount you had staked plus more. If you lose the bet you get nothing back.
We assume that you enter the casino with a positive wealth of less than a dollar. You consider the outing at the casino a success if you manage to come away with exactly dollar in total wealth. “Bold Play” is the strategy of betting your entire wealth whenever you current wealth is less that half a dollar and otherwise betting just enough that a win will make your wealth exactly one dollar. You walk away a success as soon as your wealth reaches a dollar and you are thrown out as a failure as soon as your wealth reaches zero.
It is a celebrated result that when Bold Play is an optimal strategy if your aim is to maximise your probability of success. But that is not our subject today. See Billingsely’s Probability and Measure and his article “The Singular Function of Bold Play” (American Scientist, 71(4), 392–397) for more of the story.
For a given value of and , the so-called “Singular Function of Bold Play” is the function on which gives the probability of success when starting Bold Play with wealth . If you consider as the state of the gambler and realize that in an ideal casino the past does not affect the future, you will see that this function is described by the relation:
For students of mathematical analysis this function is interesting because it is an example of a function arising out of a natural problem which is everywhere continuous and strictly increasing, possesses a derivative at “almost every’” point, and yet wherever the derivative exists.
The graph of has a fractal character:
We present here a program in the programming language Haskell to calculate the value of this function on a grid of dyadic rationals (i.e. points of the form ).
Haskell is a language characterised by “lazy evaluation”: expressions are evaluated only when their values are needed. This allows us to define potentially infinite data structures in this language. A program definining such a data structure does not go into an endless loop as long as it is careful to use only a finite part of this infinite structure. This allows us to separate the production of an potentially infinite stream of data from the details of how much of it is to be consumed and how.
In our program we produce the values of for for without end and then separately choose how many of these values to use depending on a command-line argument provided by the user.
This program also illustrates how the idioms of functional programming can be used to naturally express the recursive definition of a function like .
Imports
module Main where
import Text.Printf (printf)
import Control.Monad (forM_)
import System.Environment (getArgs)
The data types
We represent the value by constructor whose arguments are . This facilitates the iterative calculation. The function converts to the representation. is a simple helper function used later in choosing which of the computed values to keep.
data FValue = FValue !Int !Int !Double
deriving Show
toXY::FValue -> (Double,Double)
FValue n k y) = ((fromIntegral k)*(2.0 ^^ (-n)),y)
toXY (
depth::FValue -> Int
FValue n _ _) = n depth (
Computing the values of
The function computes the values of given the probability parameter . This is the crux of the program. Note how is defined in terms of itself, thus expressing the natural recursion in the definition of where value of allows us to calculate and .
To start off the calculation we provide the value .
fvalues::Double->[FValue]
= start:(concatMap next $ fvalues p)
fvalues p where
= FValue 1 1 p
start FValue n k y) = [left,right]
next (where
= FValue (n+1) k (p*y)
left = FValue (n+1) ((2^n) + k) (p+(1-p)*y) right
The Driver
We want to invoke the command as singular n p
where is
the probability parameter and is the maximum value of
for which values of the form are
considered.
The program prints the and values separated by tabs. Warning: a total of lines of output are printed, so be careful with your choice of .
parseArgs::[String] -> (Int,Double)
:p:[]) = (read n,read p)
parseArgs (n= error "Usage: singular n p"
parseArgs _
main::IO()
= do
main <- getArgs
args let (n,p) = parseArgs args
let tuples = map toXY . takeWhile ((<= n) . depth) . fvalues $ p
let printtuple(x,y) = printf "%g\t%g\n" x y
forM_ tuples printtuple
Source Code
The full source of the program is available here.