From 983003a5629e5c492604f89bf0a5076806ed3d38 Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Tue, 3 Nov 2015 14:45:53 +0100 Subject: [PATCH] Amount: split out of Structures.kt and into a separate file, add operator overloads. --- src/Amounts.kt | 49 +++++++++++++++++++++++++++++++++++++++++++++++ src/Structures.kt | 19 ------------------ 2 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 src/Amounts.kt diff --git a/src/Amounts.kt b/src/Amounts.kt new file mode 100644 index 0000000000..bdde70df4b --- /dev/null +++ b/src/Amounts.kt @@ -0,0 +1,49 @@ +import java.math.BigDecimal +import java.util.* +import kotlin.math.div + +/** + * Amount represents a positive quantity of currency, measured in pennies, which are the smallest representable units. + * Note that "pennies" are not necessarily 1/100ths of a currency unit, but are the actual smallest amount used in + * whatever currency the amount represents. + * + * Amounts of different currencies *do not mix* and attempting to add or subtract two amounts of different currencies + * will throw [IllegalArgumentException]. Amounts may not be negative. + */ +data class Amount(val pennies: Int, val currency: Currency) : Comparable { + init { + // Negative amounts are of course a vital part of any ledger, but negative values are only valid in certain + // contexts: you cannot send a negative amount of cash, but you can (sometimes) have a negative balance. + require(pennies >= 0) { "Negative amounts are not allowed: $pennies" } + } + + operator fun plus(other: Amount): Amount { + checkCurrency(other) + return Amount(pennies + other.pennies, currency) + } + + operator fun minus(other: Amount): Amount { + checkCurrency(other) + return Amount(pennies - other.pennies, currency) + } + + private fun checkCurrency(other: Amount) { + require(other.currency == currency) { "Currency mismatch: ${other.currency} vs $currency" } + } + + operator fun div(other: Int): Amount = Amount(pennies / other, currency) + operator fun times(other: Int): Amount = Amount(pennies * other, currency) + + override fun toString(): String = currency.currencyCode + " " + (BigDecimal(pennies) / BigDecimal(100)).toPlainString() + + override fun compareTo(other: Amount): Int { + checkCurrency(other) + return pennies.compareTo(other.pennies) + } +} + +// Note: this will throw an exception if the iterable is empty. +fun Iterable.sum() = reduce { left, right -> left + right } +fun Iterable.sumOrZero(currency: Currency) = if (iterator().hasNext()) sum() else Amount(0, currency) + +// TODO: Think about how positive-only vs positive-or-negative amounts can be represented in the type system. diff --git a/src/Structures.kt b/src/Structures.kt index 1d195206f3..82192f9247 100644 --- a/src/Structures.kt +++ b/src/Structures.kt @@ -2,25 +2,6 @@ import java.security.PublicKey import java.security.Timestamp import java.util.* -data class Amount(val pennies: Int, val currency: Currency) { - init { - // Negative amounts are of course a vital part of any ledger, but negative values are only valid in certain - // contexts: you cannot send a negative amount of cash, but you can (sometimes) have a negative balance. - // TODO: Think about how positive-only vs positive-or-negative amounts can be represented in the type system. - require(pennies >= 0) { "Negative amounts are not allowed: $pennies" } - } - - operator fun plus(other: Amount): Amount { - require(other.currency == currency) - return Amount(pennies + other.pennies, currency) - } - - operator fun minus(other: Amount): Amount { - require(other.currency == currency) - return Amount(pennies - other.pennies, currency) - } -} - /** * A contract state (or just "state") contains opaque data used by a contract program. It can be thought of as a disk * file that the program can use to persist data across transactions. States are immutable: once created they are never