/** * Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.basics.value; import java.io.ObjectStreamException; import java.io.Serializable; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import org.joda.beans.Bean; import org.joda.beans.BeanBuilder; import org.joda.beans.BeanDefinition; import org.joda.beans.ImmutableBean; import org.joda.beans.ImmutableConstructor; import org.joda.beans.JodaBeanUtils; import org.joda.beans.MetaProperty; import org.joda.beans.Property; import org.joda.beans.PropertyDefinition; import org.joda.beans.impl.direct.DirectFieldsBeanBuilder; import org.joda.beans.impl.direct.DirectMetaBean; import org.joda.beans.impl.direct.DirectMetaProperty; import org.joda.beans.impl.direct.DirectMetaPropertyMap; import com.opengamma.strata.collect.ArgChecker; /** * Standard implementation of {@code Rounding} that uses the half-up convention. * <p> * This class implements {@link Rounding} to provide the ability to round a number. * Rounding follows the normal {@link RoundingMode#HALF_UP} convention. * For example, this could be used to round a price to the appropriate market convention. * <p> * Note that rounding a {@code double} is not straightforward as floating point * numbers are based on a binary representation, not a decimal one. * For example, the value 0.1 cannot be exactly represented in a {@code double}. */ @BeanDefinition(builderScope = "private") public final class HalfUpRounding implements Rounding, ImmutableBean, Serializable { /** * Cache common roundings. * Roundings will be commonly used in trades, which are relatively long-lived, * so some limited caching makes sense. */ private static final HalfUpRounding[] CACHE = new HalfUpRounding[16]; static { for (int i = 0; i < 16; i++) { CACHE[i] = new HalfUpRounding(i, 0); } } /** * The number of decimal places to round to. * <p> * Rounding follows the normal {@link RoundingMode#HALF_UP} convention. * <p> * The value must be from 0 to 255 inclusive. */ @PropertyDefinition private final int decimalPlaces; /** * The fraction of the smallest decimal place to round to. * <p> * If used, this allows the rounding point to be set as a fraction of the smallest decimal place. * For example, setting this field to 32 will round to the nearest 1/32nd of the last decimal place. * <p> * This will not be present if rounding is to an exact number of decimal places and there is no fraction. * The value must be from 2 to 256 inclusive, 0 is used to indicate no fractional part. */ @PropertyDefinition private final int fraction; /** * The fraction, as a {@code BigDecimal}. * Not a Joda-Beans property. */ private final transient BigDecimal fractionDecimal; /** * The hash code. * Uniquely identifies the state of the object. * Not a Joda-Beans property. */ private final transient int uniqueHashCode; //------------------------------------------------------------------------- /** * Obtains an instance that rounds to the specified number of decimal places. * <p> * This returns a convention that rounds to the specified number of decimal places. * Rounding follows the normal {@link RoundingMode#HALF_UP} convention. * * @param decimalPlaces the number of decimal places to round to, from 0 to 255 inclusive * @return the rounding convention * @throws IllegalArgumentException if the decimal places is invalid */ public static HalfUpRounding ofDecimalPlaces(int decimalPlaces) { if (decimalPlaces >= 0 && decimalPlaces < 16) { return CACHE[decimalPlaces]; } return new HalfUpRounding(decimalPlaces, 1); } /** * Obtains an instance from the number of decimal places and fraction. * <p> * This returns a convention that rounds to a fraction of the specified number of decimal places. * Rounding follows the normal {@link RoundingMode#HALF_UP} convention. * <p> * For example, to round to the nearest 1/32nd of the 4th decimal place, call * this method with the arguments 4 and 32. * * @param decimalPlaces the number of decimal places to round to, from 0 to 255 inclusive * @param fraction the fraction of the last decimal place, such as 32 for 1/32, from 0 to 256 inclusive * @return the rounding convention * @throws IllegalArgumentException if the decimal places or fraction is invalid */ public static HalfUpRounding ofFractionalDecimalPlaces(int decimalPlaces, int fraction) { return new HalfUpRounding(decimalPlaces, fraction); } //------------------------------------------------------------------------- // constructor @ImmutableConstructor private HalfUpRounding( int decimalPlaces, int fraction) { if (decimalPlaces < 0 || decimalPlaces > 255) { throw new IllegalArgumentException("Invalid decimal places, must be from 0 to 255 inclusive"); } if (fraction < 0 || fraction > 256) { throw new IllegalArgumentException("Invalid fraction, must be from 0 to 256 inclusive"); } this.decimalPlaces = ArgChecker.notNegative(decimalPlaces, "decimalPlaces"); this.fraction = (fraction <= 1 ? 0 : fraction); this.fractionDecimal = (fraction <= 1 ? null : BigDecimal.valueOf(this.fraction)); this.uniqueHashCode = (this.decimalPlaces << 16) + this.fraction; } // deserialize transient private Object readResolve() throws ObjectStreamException { return new HalfUpRounding(decimalPlaces, fraction); } //------------------------------------------------------------------------- @Override public double round(double value) { return Rounding.super.round(value); } @Override public BigDecimal round(BigDecimal value) { if (fraction > 1) { return value .multiply(fractionDecimal) .setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP) .divide(fractionDecimal); } return value.setScale(decimalPlaces, BigDecimal.ROUND_HALF_UP); } //----------------------------------------------------------------------- @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (obj instanceof HalfUpRounding) { // hash code is unique so can be used to compare return (uniqueHashCode == ((HalfUpRounding) obj).uniqueHashCode); } return false; } @Override public int hashCode() { return uniqueHashCode; } //------------------------------------------------------------------------- @Override public String toString() { return "Round to " + (fraction > 1 ? "1/" + fraction + " of " : "") + decimalPlaces + "dp"; } //------------------------- AUTOGENERATED START ------------------------- ///CLOVER:OFF /** * The meta-bean for {@code HalfUpRounding}. * @return the meta-bean, not null */ public static HalfUpRounding.Meta meta() { return HalfUpRounding.Meta.INSTANCE; } static { JodaBeanUtils.registerMetaBean(HalfUpRounding.Meta.INSTANCE); } /** * The serialization version id. */ private static final long serialVersionUID = 1L; @Override public HalfUpRounding.Meta metaBean() { return HalfUpRounding.Meta.INSTANCE; } @Override public <R> Property<R> property(String propertyName) { return metaBean().<R>metaProperty(propertyName).createProperty(this); } @Override public Set<String> propertyNames() { return metaBean().metaPropertyMap().keySet(); } //----------------------------------------------------------------------- /** * Gets the number of decimal places to round to. * <p> * Rounding follows the normal {@link RoundingMode#HALF_UP} convention. * <p> * The value must be from 0 to 255 inclusive. * @return the value of the property */ public int getDecimalPlaces() { return decimalPlaces; } //----------------------------------------------------------------------- /** * Gets the fraction of the smallest decimal place to round to. * <p> * If used, this allows the rounding point to be set as a fraction of the smallest decimal place. * For example, setting this field to 32 will round to the nearest 1/32nd of the last decimal place. * <p> * This will not be present if rounding is to an exact number of decimal places and there is no fraction. * The value must be from 2 to 256 inclusive, 0 is used to indicate no fractional part. * @return the value of the property */ public int getFraction() { return fraction; } //----------------------------------------------------------------------- /** * The meta-bean for {@code HalfUpRounding}. */ public static final class Meta extends DirectMetaBean { /** * The singleton instance of the meta-bean. */ static final Meta INSTANCE = new Meta(); /** * The meta-property for the {@code decimalPlaces} property. */ private final MetaProperty<Integer> decimalPlaces = DirectMetaProperty.ofImmutable( this, "decimalPlaces", HalfUpRounding.class, Integer.TYPE); /** * The meta-property for the {@code fraction} property. */ private final MetaProperty<Integer> fraction = DirectMetaProperty.ofImmutable( this, "fraction", HalfUpRounding.class, Integer.TYPE); /** * The meta-properties. */ private final Map<String, MetaProperty<?>> metaPropertyMap$ = new DirectMetaPropertyMap( this, null, "decimalPlaces", "fraction"); /** * Restricted constructor. */ private Meta() { } @Override protected MetaProperty<?> metaPropertyGet(String propertyName) { switch (propertyName.hashCode()) { case 1477363453: // decimalPlaces return decimalPlaces; case -1653751294: // fraction return fraction; } return super.metaPropertyGet(propertyName); } @Override public BeanBuilder<? extends HalfUpRounding> builder() { return new HalfUpRounding.Builder(); } @Override public Class<? extends HalfUpRounding> beanType() { return HalfUpRounding.class; } @Override public Map<String, MetaProperty<?>> metaPropertyMap() { return metaPropertyMap$; } //----------------------------------------------------------------------- /** * The meta-property for the {@code decimalPlaces} property. * @return the meta-property, not null */ public MetaProperty<Integer> decimalPlaces() { return decimalPlaces; } /** * The meta-property for the {@code fraction} property. * @return the meta-property, not null */ public MetaProperty<Integer> fraction() { return fraction; } //----------------------------------------------------------------------- @Override protected Object propertyGet(Bean bean, String propertyName, boolean quiet) { switch (propertyName.hashCode()) { case 1477363453: // decimalPlaces return ((HalfUpRounding) bean).getDecimalPlaces(); case -1653751294: // fraction return ((HalfUpRounding) bean).getFraction(); } return super.propertyGet(bean, propertyName, quiet); } @Override protected void propertySet(Bean bean, String propertyName, Object newValue, boolean quiet) { metaProperty(propertyName); if (quiet) { return; } throw new UnsupportedOperationException("Property cannot be written: " + propertyName); } } //----------------------------------------------------------------------- /** * The bean-builder for {@code HalfUpRounding}. */ private static final class Builder extends DirectFieldsBeanBuilder<HalfUpRounding> { private int decimalPlaces; private int fraction; /** * Restricted constructor. */ private Builder() { } //----------------------------------------------------------------------- @Override public Object get(String propertyName) { switch (propertyName.hashCode()) { case 1477363453: // decimalPlaces return decimalPlaces; case -1653751294: // fraction return fraction; default: throw new NoSuchElementException("Unknown property: " + propertyName); } } @Override public Builder set(String propertyName, Object newValue) { switch (propertyName.hashCode()) { case 1477363453: // decimalPlaces this.decimalPlaces = (Integer) newValue; break; case -1653751294: // fraction this.fraction = (Integer) newValue; break; default: throw new NoSuchElementException("Unknown property: " + propertyName); } return this; } @Override public Builder set(MetaProperty<?> property, Object value) { super.set(property, value); return this; } @Override public Builder setString(String propertyName, String value) { setString(meta().metaProperty(propertyName), value); return this; } @Override public Builder setString(MetaProperty<?> property, String value) { super.setString(property, value); return this; } @Override public Builder setAll(Map<String, ? extends Object> propertyValueMap) { super.setAll(propertyValueMap); return this; } @Override public HalfUpRounding build() { return new HalfUpRounding( decimalPlaces, fraction); } //----------------------------------------------------------------------- @Override public String toString() { StringBuilder buf = new StringBuilder(96); buf.append("HalfUpRounding.Builder{"); buf.append("decimalPlaces").append('=').append(JodaBeanUtils.toString(decimalPlaces)).append(',').append(' '); buf.append("fraction").append('=').append(JodaBeanUtils.toString(fraction)); buf.append('}'); return buf.toString(); } } ///CLOVER:ON //-------------------------- AUTOGENERATED END -------------------------- }