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!
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.”
I should have mentioned, that code is from the excellent Java time and money library: http://sourceforge.net/projects/timeandmoney
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.