# this program copyright 1995-2005 Philip Greenspun (philg@mit.edu)
# redistribution and reuse permitted under
# the GNU General Public License, http://www.gnu.org/copyleft/gpl.html
# this is the source code for the Bill Gates Personal Wealth Clock, operating
# at http://philip.greenspun.com/WealthClock and requiring the AOLserver
# Web server program as well as some components of the ArsDigita Community
# System version 3.x.
# this is an ArsDigita Community System feature in which the loading
# of these procedures is recorded in the server error log at startup
util_report_library_entry
# tell AOLserver to associate requests for "/WealthClock" with calls to the procedure wealth_Top
ad_register_proc GET /WealthClock wealth_Top
ad_register_proc GET /WealthClockRealTime wealth_TopRealTime
ad_register_proc GET /WealthClockIntl wealth_IntlTop
# helper procedures
proc wealth_USPopulation {} {
# use the AOLserver API procedure ns_httpget to fetch a Web page from another server
set population_html [ns_httpget "http://www.census.gov/cgi-bin/popclock"]
# use the Tcl regular expression facility to pull just the population figure out of the
# page (the Census folks have surrounded it in an H2 tag)
regexp {
[^0-9]*([0-9]+),([0-9]+),([0-9]+).*
} $population_html match millions thousands units
# we have to trim the leading zeros because Tcl thinks anything
# starting with a "0" is an octal number, which means that
# something like "039" will raise an error
set trimmed_millions [string trimleft $millions 0]
set trimmed_thousands [string trimleft $thousands 0]
set trimmed_units [string trimleft $units 0]
return [list [expr ($trimmed_millions * 1000000) + ($trimmed_thousands * 1000) + $trimmed_units] "$millions,$thousands,$units"]
}
# this procedure uses ns_httpget to grab a page from Yahoo! Finance. We ask for the
# quote in comma-separated values format (".csv"), which makes it very easy to parse
# (the regular expression just looks for a comma followed by some combination of digits
# and decimal point
proc wealth_get_msft_price {} {
catch { ns_httpget "http://finance.yahoo.com/d/quotes.csv?f=sl1&e=.csv&s=MSFT" } quote_html
if [regexp {,([0-9\.]+)} $quote_html match the_price] {
# Yahoo is good; they give it to us in decimal
return $the_price
}
}
# this procedure adjusts a roughly calculation of Gates's wealth for extraordinary
# dividends paid out by Microsoft, notably the $3.29 billion dividend Bill Gates
# received at the end of 2004.
proc wealth_adjust_for_extraordinary_dividends {basic_stock_price_times_shares} {
return [expr $basic_stock_price_times_shares + double(3.29e9)]
}
# This procedure returns an HTML string to its caller rather than
# a page to the user. This will enable use to build a caching mechanism
# so that if we get thousands of requests per hour we aren't making
# thousands of requests of the population and stock quote services.
proc wealth_ReturnWholePage {} {
set msft_stock_price [wealth_get_msft_price]
set population_list [wealth_USPopulation]
set population [lindex $population_list 0]
set pretty_population [lindex $population_list 1]
# Tcl is NOT Lisp and therefore if the stock price and shares are
# both integers, you get silent overflow and Bill Gates comes out as a
# pauper (< $1 billion). We hammer the problem by converting to double
# precision floating point right here. Can someone please take us
# back to the 1960s so that we can use a computer language with a decent
# model of arithmetic?
set gates_shares_pre_split [expr double(141159990)]
# adjusted for four splits since 1995
set gates_shares [expr $gates_shares_pre_split * 16]
set gates_wealth [wealth_adjust_for_extraordinary_dividends [expr $gates_shares * $msft_stock_price]]
set gates_wealth_billions [format "%0.6f" [expr $gates_wealth / 1.0e9]]
set personal_share [format "%0.2f" [expr $gates_wealth / $population]]
set pretty_date [exec /bin/date]
return "
Bill Gates Personal Wealth Clock
Bill Gates Personal Wealth Clock
just a small portion of Why Bill Gates is Richer than You
by
Philip Greenspun
$pretty_date |
Microsoft Stock Price: | \$$msft_stock_price
|
Bill Gates's Wealth: | \$$gates_wealth_billions billion
|
U.S. Population: | $pretty_population
|
Your Personal Contribution: | \$$personal_share
|
\"If you want to know what God thinks about money, just look at the people He gives it to.\"
-- Old Irish Saying
Sources
- Population: U.S. Census Bureau
- N shares of Microsoft owned by Bill Gates: 1995 Microsoft Proxy Statement (141,159,990 shares adjusted for splits in December 1996, February 1998, March 1999, and February 2003)
- Microsoft Stock Price:
Yahoo! Finance
The Clock attempts to accurately display Bill Gates's wealth,
not the value of his current holdings of Microsoft stock. We take as
a baseline of his wealth the shares of Microsoft that he held in 1995.
This is an understatement because it doesn't include the multi-million
dollar trust funds he received at birth from his grandparents, houses,
stock, and other gifts from his wealthy parents, or investments he
purchased with sales of Microsoft shares sold prior to 1995. The
clock adjusts for the extraodinary \$3.29 billion dividend that Bill
Gates received from Microsoft at the end of 2004.
What about shares sold subsequent to 1995? Don't they balance out
this understatement of wealth? No. If Gates sold Microsoft shares to
purchase shares in cable TV companies, Corbis, or whatever, we assume
that these investments have performed about as well as Microsoft.
What about charity? There are two ways to look at this. One is that
Bill Gates is directly involved in managing his charitable foundation.
So he still controls the money, though of course it will be used only
for certain kinds of purposes. If you were a real cynic you might
note that Bill's charitable inclinations remained, uh, undiscovered
until the Federal Government began to file anti-trust lawsuits. You
would then see his charitable contributions as investments in the
maintenance of Microsoft's monopoly and not reductions in wealth.
Multi-Nationalism
As the author of such books as Canada: More than Just a Brand
Name?, I am well aware of the importance of multi-nationalism.
You are invited to try an international
version of the Clock.
How it Works
... is explained in somewhat simplified form, including source code,
in Chapter 10 of Philip and Alex's Guide
to Web Publishing. The program
took about one hour from start to finish and was built back
in 1995 as an example for MIT students of the future of Web service
design: servers that combine information and services from other
servers
(see http://philip.greenspun.com/teaching/teaching-software-engineering). Ironically this approach to distributed computing over the
Internet was ignored by most of the rest of the world except for one
company: Microsoft! If you look at Microsoft .NET you'll see that
it provides extensive support for building applications like this
wealth clock.
In order to provide you with faster service, and to reduce the load on
the subsidiary Web sites, the program caches the page. However, you
can also operate the clock in real
time mode, which will update the cache for everyone else.
The actual source
code is available and is intended primarily for Computer Science
majors working through the textbook Software
Engineering for Internet Applications.
Index
philg@mit.edu
"
}
proc wealth_BrokenMessage {} {
return "
Wealth Clock -- We're Broken
We're Broken
here at the Bill Gates Personal Wealth Clock
Probably the Wealth Clock is broken because something has gone wrong
with the underlying real-time sources of data:
In the meantime, you can amuse yourself
by reading
Why Bill Gates is Richer than You and/or
the perennially popular
Travels with Samantha
philg@mit.edu
"
}
# here is the procedure that is invoked when someone asks AOLserver
# for the "/WealthClock" URI. wealth_Top tries to construct the page,
# wrapping the attempt in a Tcl catch because the availability of the
# underlying data sources cannot be guaranteed.
# one interesting point is that the construction of the page is wrapped
# in a "util_memoize" procedure, which keeps a record of Tcl statements that
# it has seen and their return values. If it has already seen a statement
# since the server started up (midnight), it will returned the cached value
# rather than reevaluating the statement. Note that the procedure
# wealth_TopRealTime calls util_memoize_flush so that the next time util_memoize
# is called it will call wealth_ReturnWholePage again.
proc wealth_Top {ignore} {
if [catch {set moby_string [util_memoize wealth_ReturnWholePage]} errmsg] {
# something went wrong with our sources
# the catch means that the failure will never be recorded in the error log
# so if we're debugging it might be very helpful to write what happened
# into the log as a "notice"
# ns_log Notice "Wealth clock got $errmsg"
ns_return 200 text/html [wealth_BrokenMessage]
} else {
ns_return 200 text/html $moby_string
}
}
proc wealth_TopRealTime {ignore} {
# flush the cached value for the page (a big HTML string)
util_memoize_flush wealth_ReturnWholePage
# recalculate the page, which will populate the cache for the next
# person, then return the big HTML string to the user
ns_return 200 text/html [util_memoize wealth_ReturnWholePage]
}
### international
# see comments on wealth_USPopulation for explanations
proc wealth_IntlPopulation {} {
set population_html [ns_httpget "http://www.census.gov/cgi-bin/ipc/popclockw"]
regexp {[^0-9]*([0-9]+),([0-9]+),([0-9]+),([0-9]+).*
} $population_html match billions millions thousands units
set trimmed_billions [string trimleft $billions 0]
set trimmed_millions [string trimleft $millions 0]
set trimmed_thousands [string trimleft $thousands 0]
set trimmed_units [string trimleft $units 0]
return [list [expr ($trimmed_billions * 1e+09) + ($trimmed_millions * 1000000) + ($trimmed_thousands * 1000) + $trimmed_units] "$billions,$millions,$thousands,$units"]
}
proc wealth_ReturnIntlPage {} {
set msft_stock_price [wealth_get_msft_price]
set population_list [wealth_IntlPopulation]
set population [lindex $population_list 0]
set pretty_population [lindex $population_list 1]
set gates_shares_pre_split [expr double(141159990)]
set gates_shares [expr $gates_shares_pre_split * 16]
set gates_wealth [wealth_adjust_for_extraordinary_dividends [expr $gates_shares * $msft_stock_price]]
set gates_wealth_billions [format "%0.6f" [expr $gates_wealth / 1.0e9]]
set personal_share [format "%0.2f" [expr $gates_wealth / $population]]
set pretty_date [exec /bin/date]
return "
Bill Gates Personal Wealth Clock (International Version)
Bill Gates Personal Wealth Clock
(International Version)
just a small portion of Why Bill Gates is Richer than You
by
Philip Greenspun
$pretty_date |
Microsoft Stock Price: | \$$msft_stock_price
|
Bill Gates's Wealth: | \$$gates_wealth_billions billion
|
World Population: | $pretty_population
|
Your Personal Contribution: | \$$personal_share
|
Sources
- World Population: U.S. Census Bureau
- N shares of Microsoft owned by Bill Gates: 1995 Microsoft Proxy Statement (141,159,990 shares adjusted for splits in December 1996, February 1998, March 1999, and February 2003)
- Microsoft Stock Price: Yahoo! Finance
If you are a U.S. resident, you might be interested in viewing the original Americentric version.
Index
philg@mit.edu
"
}
# we memoize the page for 1800 seconds (30 minutes) because there is no real-time version
# of this clock
proc wealth_IntlTop {ignore} {
if [catch {set moby_string [util_memoize wealth_ReturnIntlPage 1800]} errmsg] {
# something went wrong with our sources
ns_log Notice "Wealth clock got $errmsg"
ns_return 200 text/html [wealth_BrokenMessage]
} else {
ns_return 200 text/html $moby_string
}
}
# old procedure, no longer needed because we're able to get quotes in decimal rather
# than "34 and 7/8ths"
# proc wealth_RawQuoteToDecimal {raw_quote} {
# if { [regexp {(.*) (.*)} $raw_quote match whole fraction] } {
# # there was a space
# if { [regexp {(.*)/(.*)} $fraction match num denom] } {
# # there was a "/"
# set extra [expr double($num) / $denom]
# return [expr $whole + $extra]
# }
# # we couldn't parse the fraction
# return $whole
# } else {
# # we couldn't find a space, assume integer
# return $raw_quote
# }
# }
util_report_successful_library_load