Fan mail with Java code!

So I mentioned a while back that I write science fiction, and I’m increasingly identifying as a science-fiction writer. This week, I was very proud to have my story Horatius and Clodia published at StrangeHorizons.com, one of the coolest science fiction publications (online or in print) these days. The story is about sentient electronic cash, which becomes maybe a little more self-aware than its creators had in mind.

For some reason, I’ve gotten more fan mail and props for this story than any other in recent memory. Maybe it’s just that StrangeHorizons has grown in popularity since the last time they published my fiction, a few years ago. One piece of fan mail came from Julia, who codes financial systems software for “a major university” in Nashville. She wrote:

Your short story was a uniquely satisfying way to goof off at work, THANKS! Here’s a token of my esteem, computer code in the Java programming language for handling money :-)

Julia Reynolds

7 package com.domainlanguage.money;
8
9 import java.io.Serializable;
10 import java.math.BigDecimal;
11 import java.util.Collection;
12 import java.util.Currency;
13 import java.util.Iterator;
14 import java.util.Locale;
15 import com.domainlanguage.base.Ratio;
16 import com.domainlanguage.base.Rounding;
17 import com.domainlanguage.time.Duration;
18
19 public class Money implements Comparable, Serializable {
20     private static final Currency USD = Currency.getInstance(”USD”);
21     private static final Currency EUR = Currency.getInstance(”EUR”);
22     private static final int DEFAULT_ROUNDING_MODE = Rounding.HALF_EVEN;
23
24     private BigDecimal amount;
25     private Currency currency;
26
27     /**
28      * The constructor does not complex computations and
requires simple, inputs
29      * consistent with the class invariant. Other creation
methods are available
30      * for convenience.
31      */
32     public Money(BigDecimal amount, Currency currency) {
33         if (amount.scale() != currency.getDefaultFractionDigits())
throw new IllegalArgumentException(”Scale of amount does not match currency”);
34         this.currency = currency;
35         this.amount = amount;
36     }
37
38     /**
39      * This creation method is safe to use. It will adjust
scale, but will not
40      * round off the amount.
41      */
42     public static Money valueOf (BigDecimal amount, Currency currency) {
43         return Money.valueOf (amount, currency, Rounding.UNNECESSARY);
44     }
45
46     /**
47      * For convenience, an amount can be rounded to create a Money.
48      */
49     public static Money valueOf(BigDecimal rawAmount, Currency
currency, int roundingMode) {
50         BigDecimal amount =
rawAmount.setScale(currency.getDefaultFractionDigits(), roundingMode);
51         return new Money(amount, currency);
52     }
53
54     /**
55      * WARNING: Because of the indefinite precision of double,
this method must
56      * round off the value.
57      */
58     public static Money valueOf(double dblAmount, Currency currency) {
59         return Money.valueOf(dblAmount, currency, DEFAULT_ROUNDING_MODE);
60     }
61
62     /**
63      * Because of the indefinite precision of double, this
method must round off
64      * the value. This method gives the client control of the
rounding mode.
65      */
66     public static Money valueOf(double dblAmount, Currency
currency, int roundingMode) {
67         BigDecimal rawAmount = new BigDecimal(dblAmount);
68         return Money.valueOf(rawAmount, currency, roundingMode);
69     }
70
71     /**
72      * WARNING: Because of the indefinite precision of double,
thismethod must
73      * round off the value.
74      */
75     public static Money dollars(double amount) {
76         return Money.valueOf(amount, USD);
77     }
78
79     /**
80      * This creation method is safe to use. It will adjust
scale, but will not
81      * round off the amount.
82      */
83     public static Money dollars (BigDecimal amount) {
84         return Money.valueOf(amount, USD);
85     }
86
87     /**
88      * WARNING: Because of the indefinite precision of double,
this method must
89      * round off the value.
90      */
91     public static Money euros(double amount) {
92         return Money.valueOf(amount, EUR);
93     }
94
95     /**
96      * This creation method is safe to use. It will adjust
scale, but will not
97      * round off the amount.
98      */
99     public static Money euros (BigDecimal amount) {
100         return Money.valueOf(amount, EUR);
101     }
102
103     public static Money sum(Collection monies) {
104         //TODO Return Default Currency
105         if (monies.isEmpty())
106             return Money.dollars(0.00);
107         Iterator iterator = monies.iterator();
108         Money sum = (Money) iterator.next();
109         while (iterator.hasNext()){
110             Money each = (Money) iterator.next();
111             sum = sum.plus(each);
112         }
113         return sum;
114     }
115
116     /**
117      * How best to handle access to the internals? It is needed for
118      * database mapping, UI presentation, and perhaps a few other
119      * uses. Yet giving public access invites people to do the
120      * real work of the Money object elsewhere.
121      * Here is an experimental approach, giving access with a
122      * warning label of sorts. Let us know how you like it.
123      */
124     public BigDecimal breachEncapsulationOfAmount() {
125         return amount;
126     }
127
128     public Currency breachEncapsulationOfCurrency() {
129         return currency;
130     }
131
132
133     /**
134      * This probably should be Currency responsibility. Even
then, it may need
135      * to be customized for specialty apps because there are
other cases, where
136      * the smallest increment is not the smallest unit.
137      */
138     Money minimumIncrement() {
139         BigDecimal one = new BigDecimal(1);
140         BigDecimal increment =
one.movePointLeft(currency.getDefaultFractionDigits());
141         return Money.valueOf(increment, currency);
142     }
143
144     Money incremented() {
145         return this.plus(minimumIncrement());
146     }
147
148     boolean hasSameCurrencyAs(Money arg) {
149         return currency.equals(arg.currency);
150     }
151
152     public Money negated() {
153         return Money.valueOf(amount.negate(), currency);
154     }
155
156     public Money abs() {
157         return Money.valueOf(amount.abs(), currency);
158     }
159
160     public boolean isNegative() {
161         return amount.compareTo(new BigDecimal(0)) < 0;
162     }
163
164     public boolean isPositive() {
165         return amount.compareTo(new BigDecimal(0)) > 0;
166     }
167
168     public boolean isZero() {
169         return this.equals(Money.valueOf(0.0, currency));
170     }
171
172     public Money plus(Money other) {
173         assertHasSameCurrencyAs(other);
174         return Money.valueOf(amount.add(other.amount), currency);
175     }
176
177     public Money minus(Money other) {
178         return this.plus(other.negated());
179     }
180
181     public Money dividedBy (BigDecimal divisor, int roundingMode) {
182         BigDecimal newAmount = amount.divide(divisor, roundingMode);
183         return Money.valueOf(newAmount,currency);
184     }
185
186     public Money dividedBy (double divisor) {
187         return dividedBy(divisor, DEFAULT_ROUNDING_MODE);
188     }
189
190     public Money dividedBy (double divisor, int roundingMode) {
191         return dividedBy(new BigDecimal(divisor), roundingMode);
192     }
193
194     public Ratio dividedBy (Money divisor) {
195         assertHasSameCurrencyAs(divisor);
196         return Ratio.of(amount, divisor.amount);
197     }
198
199     public Money applying (Ratio ratio, int roundingRule) {
200         return applying(ratio, currency.getDefaultFractionDigits(),
roundingRule);
201     }
202
203     public Money applying (Ratio ratio, int scale, int roundingRule) {
204         BigDecimal newAmount = ratio.times(amount).decimalValue(scale,
roundingRule);
205         return Money.valueOf(newAmount, currency);
206     }
207
208     /**
209      * TODO: Many apps require carrying extra precision in intermediate
210      * calculations. The use of Ratio is a beginning, but need a
comprehensive
211      * solution. Currently, an invariant of Money is that the scale is the
212      * currencies standard scale, but this will probably have to
be suspended or
213      * elaborated in intermediate calcs, or handled with defered
calculations
214      * like Ratio.
215      */
216
217     public Money times(BigDecimal factor) {
218         return times(factor, DEFAULT_ROUNDING_MODE);
219     }
220
221     /**
222      * TODO: BigDecimal.multiply() scale is sum of scales of two
multiplied
223      * numbers. So what is scale of times?
224      */
225     public Money times(BigDecimal factor, int roundingMode) {
226         return Money.valueOf(amount.multiply(factor), currency, roundingMode);
227     }
228
229     public Money times (double amount, int roundingMode) {
230         return times(new BigDecimal(amount), roundingMode);
231     }
232
233     public Money times(double amount) {
234         return times(new BigDecimal(amount));
235     }
236
237     public Money times(int i) {
238         return times(new BigDecimal(i));
239     }
240
241     public int compareTo(Object other) {
242         return compareTo((Money)other);
243     }
244
245     public int compareTo(Money other) {
246         if (!hasSameCurrencyAs(other))
247             throw new IllegalArgumentException(”Compare is not
defined between different currencies”);
248         return amount.compareTo(other.amount);
249     }
250
251     public boolean isGreaterThan(Money other) {
252         return (compareTo(other) > 0);
253     }
254
255     public boolean isLessThan(Money other) {
256         return (compareTo(other) < 0);
257     }
258
259     public boolean equals(Object other) {
260         try {
261             return equals((Money)other);
262         } catch(ClassCastException ex) {
263             return false;
264         }
265     }
266
267     public boolean equals(Money other) {
268         return
269             other != null &&
270             hasSameCurrencyAs(other) &&
271             amount.equals(other.amount);
272     }
273
274     public int hashCode() {
275         return amount.hashCode();
276     }
277
278     public String toString() {
279         return currency.getSymbol() + " " + amount;
280     }
281
282     public String toString(Locale locale) {
283         return currency.getSymbol(locale) + " " + amount;
284     }
285
286     public MoneyTimeRate per(Duration duration) {
287        return new MoneyTimeRate(this, duration);
288     }
289
290 //  TODO: Provide some currency-dependent formatting. Java 1.4 Currency doesn't
291 //  do it.
292 //  public String formatString() {
293 //      return currency.formatString(amount());
294 //  }
295 //  public String localString() {
296 //      return currency.getFormat().format(amount());
297 //  }
298
299     BigDecimal getAmount() {
300         return amount;
301     }
302
303     Currency getCurrency() {
304         return currency;
305     }
306
307     private void assertHasSameCurrencyAs(Money aMoney) {
308         if (!hasSameCurrencyAs(aMoney))
309             throw new IllegalArgumentException(aMoney.toString()
+ " is not same currency as " + this.toString());
310     }
311
312     //Only for use by persistence mapping frameworks
313     //These methods break encapsulation and we put them in
here begrudgingly

314     Money() {
315     }
316     //Only for use by persistence mapping frameworks
317     //These methods break encapsulation and we put them in
here begrudgingly

318     private BigDecimal getForPersistentMapping_Amount() {
319         return amount;
320     }
321     //Only for use by persistence mapping frameworks
322     //These methods break encapsulation and we put them in
here begrudgingly

323     private void setForPersistentMapping_Amount(BigDecimal amount) {
324         this.amount = amount;
325     }
326     //Only for use by persistence mapping frameworks
327     //These methods break encapsulation and we put them in
here begrudgingly

328     private Currency getForPersistentMapping_Currency() {
329         return currency;
330     }
331     //Only for use by persistence mapping frameworks
332     //These methods break encapsulation and we put them in
here begrudgingly

333     private void setForPersistentMapping_Currency(Currency currency) {
334         this.currency = currency;
335     }
336
337
338 }

I’ve never gotten fan mail with Java code in it before. This made my day!

3 Responses to “Fan mail with Java code!”

  1. Kristin A. says:

    Charlie,

    I’m not ordinarily a science fiction fan, but I checked out your story, and it’s definitely pulling me in! I love unusual perspectives in art and writing. Readers don’t often get such abstract protagonists, though I do remember the color red narrating one of the chapters in Orhan Pamuk’s “My Name Is Red.”

  2. Julia says:

    I should have mentioned, that code is from the excellent Java time and money library: http://sourceforge.net/projects/timeandmoney

  3. Or, just possibly, it’s a really excellent piece that inspires people to pass it around. I’ve linked quite a few people to it since the day it went online, and everyone has thanked me for doing so.

    I found the story to be both thought-provoking and beautiful. I love the final line. :)

Leave a Reply