/*
* Copyright 2012 Bitcoin Austria
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package at.bitcoin_austria.bitfluids;
import com.google.bitcoin.core.TransactionOutput;
import com.google.common.base.Preconditions;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* a core Bitcoin Value representation, caputuring many domain specific aspects of it.
* introduced to reduce the ambiguity when dealing with double, BigInteger, long, or even worse, integer representations
*
* @author apetersson
*/
public final class Bitcoins implements Serializable {
/**
* 100 000 000 must be long, else MAX_VALUE will overflow
*/
private static final long SATOSHIS_PER_BITCOIN = (long) Math.pow(10, 8);
private static final BigDecimal SATOSHIS_PER_BITCOIN_BD = BigDecimal.valueOf(SATOSHIS_PER_BITCOIN);
private static final long MAX_VALUE = 21000000 * SATOSHIS_PER_BITCOIN;
public static final String BITCOIN_SYMBOL = "฿";
private final long satoshis;
/**
* if used properly, also valueOf(input) should be provided
* ideally, BitcoinJ would already output Bitcoins instead of BigInteger
*
* @param output Object from BitcoiJ transaction
* @return a value prepresentation of a Bitcoin domain object
*/
public static Bitcoins valueOf(TransactionOutput output) {
return Bitcoins.valueOf(output.getValue().longValue());
}
/**
*
* @param btc double Value in full bitcoins. must be an exact represenatation
* @return bitcoin value representation
* @throws IllegalArgumentException if the given double value loses precision when converted to long
*/
public static Bitcoins valueOf(double btc) {
return valueOf(toLongExact(btc));
}
public static Bitcoins nearestValue(double v) {
return new Bitcoins(Math.round(v * SATOSHIS_PER_BITCOIN));
}
public static Bitcoins valueOf(long satoshis) {
return new Bitcoins(satoshis);
}
private static long toLongExact(double origValue) {
double satoshis = origValue * SATOSHIS_PER_BITCOIN; //possible loss of precision here?
long longSatoshis = Math.round(satoshis);
if (satoshis != (double) longSatoshis) {
double error = longSatoshis - satoshis;
throw new IllegalArgumentException("the given double value " + origValue +
" was not convertable to a precise value." +
" error: " + error + " satoshis");
}
return longSatoshis;
}
private Bitcoins(long satoshis) {
Preconditions.checkArgument(satoshis >= 0,
"Bitcoin values must be debt-free and positive, but was %s", satoshis);
Preconditions.checkArgument(satoshis < MAX_VALUE,
"Bitcoin values must be smaller than 21 Million BTC, but was %s", satoshis);
this.satoshis = satoshis;
}
public BigDecimal multiply(BigDecimal pricePerBtc) {
return pricePerBtc.divide(SATOSHIS_PER_BITCOIN_BD).multiply(BigDecimal.valueOf(satoshis));
}
@Override
public String toString() {
//this could surely be implented faster without using BigDecimal. but it is good enough for now.
//this could be cached
return BigDecimal.valueOf(satoshis).divide(SATOSHIS_PER_BITCOIN_BD).toPlainString();
}
@Override
public int hashCode() {
return (int) (satoshis ^ (satoshis >>> 32));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Bitcoins bitcoins = (Bitcoins) o;
if (satoshis != bitcoins.satoshis) return false;
return true;
}
public BigInteger toBigInteger() {
return BigInteger.valueOf(satoshis);
}
public String toCurrencyString() {
return toString() + " " + BITCOIN_SYMBOL;
}
public Bitcoins roundToSignificantFigures(int n) {
return Bitcoins.valueOf(roundToSignificantFigures(satoshis,n));
}
private static long roundToSignificantFigures(long num, int n) { //todo optimize for long
if(num == 0) {
return 0;
}
final double d = Math.ceil(Math.log10(num < 0 ? -num: num));
final int power = n - (int) d;
final double magnitude = Math.pow(10, power);
final long shifted = Math.round(num*magnitude);
long ret = (long) (shifted / magnitude);
return ret;
}
}