mirror of
https://github.com/corda/corda.git
synced 2025-02-07 03:29:19 +00:00
Make Amount use 64 bit precision and overflow checked operations.
This commit is contained in:
parent
f7e04e1078
commit
67c810e315
@ -87,7 +87,7 @@ class Cash : Contract {
|
|||||||
|
|
||||||
for ((inputs, outputs) in groups) {
|
for ((inputs, outputs) in groups) {
|
||||||
requireThat {
|
requireThat {
|
||||||
"all outputs represent at least one penny" by outputs.none { it.amount.pennies == 0 }
|
"all outputs represent at least one penny" by outputs.none { it.amount.pennies == 0L }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -120,8 +120,8 @@ class Cash : Contract {
|
|||||||
|
|
||||||
requireThat {
|
requireThat {
|
||||||
"there is at least one cash input" by inputs.isNotEmpty()
|
"there is at least one cash input" by inputs.isNotEmpty()
|
||||||
"there are no zero sized inputs" by inputs.none { it.amount.pennies == 0 }
|
"there are no zero sized inputs" by inputs.none { it.amount.pennies == 0L }
|
||||||
"there are no zero sized outputs" by outputs.none { it.amount.pennies == 0 }
|
"there are no zero sized outputs" by outputs.none { it.amount.pennies == 0L }
|
||||||
"all outputs in this group use the currency of the inputs" by
|
"all outputs in this group use the currency of the inputs" by
|
||||||
outputs.all { it.amount.currency == inputAmount.currency }
|
outputs.all { it.amount.currency == inputAmount.currency }
|
||||||
}
|
}
|
||||||
|
@ -33,9 +33,9 @@ val USD = currency("USD")
|
|||||||
val GBP = currency("GBP")
|
val GBP = currency("GBP")
|
||||||
val CHF = currency("CHF")
|
val CHF = currency("CHF")
|
||||||
|
|
||||||
val Int.DOLLARS: Amount get() = Amount(this * 100, USD)
|
val Int.DOLLARS: Amount get() = Amount(this.toLong() * 100, USD)
|
||||||
val Int.POUNDS: Amount get() = Amount(this * 100, GBP)
|
val Int.POUNDS: Amount get() = Amount(this.toLong() * 100, GBP)
|
||||||
val Int.SWISS_FRANCS: Amount get() = Amount(this * 100, CHF)
|
val Int.SWISS_FRANCS: Amount get() = Amount(this.toLong() * 100, CHF)
|
||||||
|
|
||||||
//// Requirements /////////////////////////////////////////////////////////////////////////////////////////////////////
|
//// Requirements /////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@ -53,40 +53,44 @@ inline fun requireThat(body: Requirements.() -> Unit) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Amount represents a positive quantity of currency, measured in pennies, which are the smallest representable units.
|
* 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
|
* 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.
|
* whatever currency the amount represents.
|
||||||
*
|
*
|
||||||
* Amounts of different currencies *do not mix* and attempting to add or subtract two amounts of different currencies
|
* 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.
|
* will throw [IllegalArgumentException]. Amounts may not be negative. Amounts are represented internally using a signed
|
||||||
*
|
* 64 bit value, therefore, the maximum expressable amount is 2^63 - 1 == Long.MAX_VALUE. Addition, subtraction and
|
||||||
* It probably makes sense to replace this with convenience extensions over the JSR 354 MonetaryAmount interface, if
|
* multiplication are overflow checked and will throw [ArithmeticException] if the operation would have caused integer
|
||||||
* that spec doesn't turn out to be too heavy (it looks fairly complicated).
|
* overflow.
|
||||||
*
|
*
|
||||||
|
* TODO: It may make sense to replace this with convenience extensions over the JSR 354 MonetaryAmount interface
|
||||||
|
* TODO: Should amount be abstracted to cover things like quantities of a stock, bond, commercial paper etc? Probably.
|
||||||
* TODO: Think about how positive-only vs positive-or-negative amounts can be represented in the type system.
|
* TODO: Think about how positive-only vs positive-or-negative amounts can be represented in the type system.
|
||||||
*/
|
*/
|
||||||
data class Amount(val pennies: Int, val currency: Currency) : Comparable<Amount>, SerializeableWithKryo {
|
data class Amount(val pennies: Long, val currency: Currency) : Comparable<Amount>, SerializeableWithKryo {
|
||||||
init {
|
init {
|
||||||
// Negative amounts are of course a vital part of any ledger, but negative values are only valid in certain
|
// 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.
|
// contexts: you cannot send a negative amount of cash, but you can (sometimes) have a negative balance.
|
||||||
|
// If you want to express a negative amount, for now, use a long.
|
||||||
require(pennies >= 0) { "Negative amounts are not allowed: $pennies" }
|
require(pennies >= 0) { "Negative amounts are not allowed: $pennies" }
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun plus(other: Amount): Amount {
|
operator fun plus(other: Amount): Amount {
|
||||||
checkCurrency(other)
|
checkCurrency(other)
|
||||||
return Amount(pennies + other.pennies, currency)
|
return Amount(Math.addExact(pennies, other.pennies), currency)
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun minus(other: Amount): Amount {
|
operator fun minus(other: Amount): Amount {
|
||||||
checkCurrency(other)
|
checkCurrency(other)
|
||||||
return Amount(pennies - other.pennies, currency)
|
return Amount(Math.subtractExact(pennies, other.pennies), currency)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkCurrency(other: Amount) {
|
private fun checkCurrency(other: Amount) {
|
||||||
require(other.currency == currency) { "Currency mismatch: ${other.currency} vs $currency" }
|
require(other.currency == currency) { "Currency mismatch: ${other.currency} vs $currency" }
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun div(other: Int): Amount = Amount(pennies / other, currency)
|
operator fun div(other: Long): Amount = Amount(pennies / other, currency)
|
||||||
operator fun times(other: Int): Amount = Amount(pennies * other, currency)
|
operator fun times(other: Long): Amount = Amount(Math.multiplyExact(pennies, other), currency)
|
||||||
|
|
||||||
override fun toString(): String = currency.currencyCode + " " + (BigDecimal(pennies) / BigDecimal(100)).toPlainString()
|
override fun toString(): String = currency.currencyCode + " " + (BigDecimal(pennies) / BigDecimal(100)).toPlainString()
|
||||||
|
|
||||||
|
@ -365,7 +365,7 @@ class CashTests {
|
|||||||
val e: InsufficientBalanceException = assertFailsWith("balance") {
|
val e: InsufficientBalanceException = assertFailsWith("balance") {
|
||||||
makeSpend(1000.DOLLARS, THEIR_PUBKEY_1)
|
makeSpend(1000.DOLLARS, THEIR_PUBKEY_1)
|
||||||
}
|
}
|
||||||
assertEquals(1000 - 580, e.amountMissing.pennies / 100)
|
assertEquals((1000 - 580).DOLLARS, e.amountMissing)
|
||||||
|
|
||||||
assertFailsWith(InsufficientBalanceException::class) {
|
assertFailsWith(InsufficientBalanceException::class) {
|
||||||
makeSpend(81.SWISS_FRANCS, THEIR_PUBKEY_1)
|
makeSpend(81.SWISS_FRANCS, THEIR_PUBKEY_1)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user