BigDecimal e MathContext

16 respostas
I
Fala pessoal! Tenho um código simples pro BU(BrazilUtils) que tá dando uma pequena dor de cabeça.... Tenho uma classe básica de Unidades:
import java.math.*;

public class Unit {
	
	String name;

	BigDecimal valueOnBaseUnit;

	public Unit(String name, BigDecimal valueOnBaseUnit) {
		this.name = name;
		this.valueOnBaseUnit = valueOnBaseUnit;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public BigDecimal getValueOnBaseUnit() {
		return valueOnBaseUnit;
	}

	public void setValueOnBaseUnit(BigDecimal valueOnBaseUnit) {
		this.valueOnBaseUnit = valueOnBaseUnit;
	}

	@Override
	public String toString() {
		return "[name=" + name + ",value=" + valueOnBaseUnit + "]";
	}


}
2 interfaces, sendo uma com o método UnitWithValue convertTo(Unit unit); e a outra com as constantes que eu quero:
public interface AreaMetrics {
 //A base é Metros Quadrados /*Square Metric is the unit base */
	Unit M2 = new Unit("m²", new BigDecimal(String.valueOf(1)));
	Unit ACRE=new Unit( "acre",new BigDecimal(4046.8564224 ));
	Unit ARES=new Unit("ares",new BigDecimal(String.valueOf(100)));
//blablabla
Unit QUILOMETRO_QUADRADO=new Unit("quilometro_quadrado" ,new BigDecimal(String.valueOf(1000000)));	
	Unit BASE=M2;	
}
E a classe principal de Área:
import java.math.BigDecimal;
//import org.brazilutils.metrics.*;

public class Area implements UnitWithValue,AreaMetrics {

	private static final long serialVersionUID = 210046788217078583L;
	private Unit unit;
	private BigDecimal value;
	private BigDecimal base;
	
	
	public Area(BigDecimal value,Unit unit,int roudingMode,int scale){
		this.base = value.multiply(unit.getValueOnBaseUnit());//,roudingMode,scale);
		this.value = value;
		this.unit = unit;
	}
	
	public Area(BigDecimal value,Unit unit){
		this.base = value.multiply(unit.getValueOnBaseUnit());
		this.value = value;
		this.unit = unit;
	}	
	
	public String toString() {
		return "[valor= " + value + ",unidade= " + unit + ",valorNaBase= " + base + "]";
	}

	/*public UnitWithValue convertTo(Unit unit,int scale,int roundingmode) {
		return new Area(base.divide(unit.getValueOnBaseUnit(),scale,roundingmode),unit);
	}**/
	
	public UnitWithValue convertTo(Unit unit){
		return new Area(base.divide(unit.getValueOnBaseUnit()),unit);
	}
	
	public Unit getUnit(){
		return unit;
	}
	
	public void setUnit(Unit u) {
		this.unit = u;
		this.value = base.divide(unit.getValueOnBaseUnit());//, int round);
	}
	
	public static void main(String[] args) {
		Area area1 = new Area(new BigDecimal(10000), Area.M2);
		System.out.println(area1);
		System.out.println(area1.convertTo(Area.HECTARE));
		System.out.println(area1.convertTo(Area.ACRE));
	}
}
Meu problema: Eu não consigo dividir corretamente(recebo uma AritmeticException) porque eu não consigo precisar o resultado de forma decimal.Não posso usar MathContext(Aí seria mole...) porquê o código TEM que ser compatível com o Java 1.4.Alguém tem uma idéia de como eu poderia evitar isso(de preferência de forma elegante...)

16 Respostas

R

Iron, ao invés de

public UnitWithValue convertTo(Unit unit) { return new Area(base.divide(unit.getValueOnBaseUnit()),unit); }
use

public UnitWithValue convertTo(Unit unit) { return new Area(base.divide(unit.getValueOnBaseUnit(), 2, RoundingMode.HALF_EVEN), unit); }

Aqui, funcionou.

Testa aí.

I

Só que assim é “sopa com mel” Rafael! :smiley:
Vc está decidindo pelo usuário ao usar RoundingMode.HALF_EVEN,
e se ele quiser ROUND_HALF_DOWN, sacou?
Talvez seja melhor permitir que o usuario esoclha o roundmode e a escala via construtor…

R

Tranqüilo, Iron! A classe é sua! hehe.

Se rodou, agora é só definir os parâmetro que você quer no método e passar pro método divide. Certo?

Sugestão: incializa as variáveis BigDecimals com String, ao invés de double.

Olha o porquê: http://rfiume.blogspot.com/2006/12/bigdecimal-com-string-para-melhor.html

E me fala se deu tudo certo aí, beleza?

T+!

I

Sem essa, todas as classes são nossas!!!

Certo?

Não.Pq o usuário não consegue passar esse parâmetro até esse divide.Se for para usar o Rounding Mode, deveremos deixar o usuário escolher o dele, e talvez a escala também.Mas daí eu pergunto, como fazer isso de uma forma elegante?Reescrevemos o multiply?
Realmente o MathContext quebra um galhão…

incializa as variáveis BigDecimals com String, ao invés de double.

Eu tô usando double justamente para testes, pq eu sei que perde a precisão…E eu acho que acabo deixando tudo String até pq diminui a necessidade dos testes…

R

Você não quer mudar a assinatura do método convertTo, certo?

O que acha de criar duas propriedades para a sua classe, scale e roundingMode, e inicializá-las com valores padrão: 2 e RoundingMode.HALF_EVEN.

Nos casos em que o usuário da classe precisar de outra escala ou modo de arredondamento, ele ajusta através dos set’s.

I

Humm, não saquei bem… posta um exemplo!

I

Mas eu passando esses valores padrão?
Temos que pensar que podem rolar números com 30 casas decimais, o cara (o usuário) pode querer decidir isso…

R

Ele muda a escala partir dos modificadores:

public void setScale(int num);

A mesma coisa para RoundingMode.

Acredito que escala == 2, e RoundingMode == HALF_EVEN, sejam os valores mais comuns para operações com unidade de medidas, então esses valores podem ser estabelecidos como padrão.

Quando o cara precisar de escala 30, ele usa:

setScale(30)

O usuário tem como ajustar essas propriedades a vontade, mas só vai ter que lidar com elas caso precise de valores diferentes do padrão. O método convertTo também vai ficar com a assinatura que você quer:

converTo(Unit unit)
I

Mas aí tá rolando uma suposição nossa… talvez o ideal, seria ver o código de MathContext e simularmos a constante DECIMAL128, o que garante 34 casas de precisão.Dá uma olhada:
http://java.sun.com/j2se/1.5.0/docs/api/java/math/MathContext.html

Essa classe realmente é o que precisamos:
[b]
The base-independent settings are:

  1. precision: the number of digits to be used for an operation; results are rounded to this precision
  2. roundingMode: a RoundingMode object which specifies the algorithm to be used for rounding. [/b]
R

É suposição! 8)

E como toda suposição, pode ser que não seja o melhor, por isso tem que ser validada pelos usuários da classe. Conforme o feedback deles, isso pode ser mudado no futuro.

Se entendi bem, você quer um Ctrl+C / Ctrl+V na classe MathContext. Já fiz algo assim uma vez e não gostei por vários motivos. Entre eles, gerou confusão pra mim mesmo. Imagine então numa API pública!!

Não resolvemos ficar no Java 1.4? :roll: É o preço a pagar.

[Editado] Detalhe: o RoundingMode do DECIMAL128 é HALF_EVEN. [/Editado]

I

Não… é rewrite mesmo, pegando o que nos for útil…

Não resta dúvidas quanto a isso…

I

Rafael, vou te falar de algumas modificações que eu fiz:
Parece que RoundingMode.HALF_EVEN é java 1.5, logo tive que usar BigDecimal.ROUND_HALF_EVEN quando necessário.

Penso seriamente em deixa o convertTo ao gosto do usuário, pois assim ele escolhe:
public UnitWithValue convertTo(Unit unit,int scale,int roundingmode) {
return new Area(base.divide(unit.getValueOnBaseUnit(),scale,roundingmode),unit);
}

Não se preocupe que eu uso o String.valueOf para manter a retrocompatibilidade nas interfaces:
public interface AreaMetrics {
 //A base é Metros Quadrados /*Square Metric is the unit base */
	Unit M2 = new Unit("m²", new BigDecimal(String.valueOf(1)));
	Unit ACRE=new Unit( "acre",new BigDecimal(String.valueOf(4046.8564224) ));
	Unit ARES=new Unit("ares",new BigDecimal(String.valueOf(100)));
Vc preferiria a manutenção do convertTo isolado?Exemplo com as 2 formas:
import java.math.BigDecimal;
//import org.brazilutils.metrics.*;

public class Area implements UnitWithValue,AreaMetrics {

	private static final long serialVersionUID = 210046788217078583L;
	private Unit unit;
	private BigDecimal value;
	private BigDecimal base;
	
	
	public Area(BigDecimal value,Unit unit,int roudingMode,int scale){
		this.base = value.multiply(unit.getValueOnBaseUnit());//,roudingMode,scale);
		this.value = value;
		this.unit = unit;
	}
	
	public Area(BigDecimal value,Unit unit){
		this.base = value.multiply(unit.getValueOnBaseUnit());
		this.value = value;
		this.unit = unit;
	}	
	
	public String toString() {
		return "[value= " + value + ",unit= " + unit + ",base= " + base + "]";
	}

	public UnitWithValue convertTo(Unit unit,int scale,int roundingmode) {
		return new Area(base.divide(unit.getValueOnBaseUnit(),scale,roundingmode),unit);
	}
	
	public UnitWithValue convertTo(Unit unit){
	    return new Area(base.divide(unit.getValueOnBaseUnit(), 34, BigDecimal.ROUND_HALF_EVEN), unit);
	}
	
	public Unit getUnit(){
		return unit;
	}
	
	public void setUnit(Unit u) {
		this.unit = u;
		this.value = base.divide(unit.getValueOnBaseUnit());//, int round);
	}
	
	public static void main(String[] args) {
		Area area1 = new Area(new BigDecimal(10000), Area.M2);
		System.out.println(area1);
		System.out.println(area1.convertTo(Area.HECTARE,34, BigDecimal.ROUND_HALF_EVEN));
		System.out.println(area1.convertTo(Area.ACRE));
	}	

}
R

Por que não as duas formas, com sobrecarga:

public UnitWithValue convertTo(Unit unit)
public UnitWithValue convertTo(Unit unit,int scale,int roundingmode)

No primeiro caso, o método tem que ter uma documentação precisa para que o usuário saiba exatamente qual o resultado da operação.

public UnitWithValue convertTo(Unit unit){ return new Area(base.divide(unit.getValueOnBaseUnit(), 34, BigDecimal.ROUND_HALF_EVEN), unit); }
Resolveu esquecer a re-implementação do MathContext?

Ironlynx:

Não se preocupe que eu uso o String.valueOf para manter a retrocompatibilidade nas interfaces

Não entendi Iron. Pra que String.valueOf ? O que vai influenciar na retrocompatibilidade?

I

Noop, é que com o convertTo definido pelo usuário, seria desnecessário.
Olha como é MathContext:

import java.io.Serializable;

/**
 * Immutable objects describing settings such as rounding mode and digit
 * precision for numerical operations such as those in the BigDecimal class.
 * @author Anthony Balkissoon abalkiss at redhat dot com
 *
 */
public final class MathContext implements Serializable
{
  /** A MathContext for unlimited precision arithmetic * */
  public static final MathContext UNLIMITED = 
    new MathContext(0, RoundingMode.HALF_UP);
  
  /**
   * A MathContext for the IEEE 754R Decimal32 format - 7 digit preicision and
   * HALF_EVEN rounding.
   */
  public static final MathContext DECIMAL32 = 
    new MathContext(7, RoundingMode.HALF_EVEN);
  
  /**
   * A MathContext for the IEEE 754R Decimal64 format - 16 digit preicision and
   * HALF_EVEN rounding.
   */
  public static final MathContext DECIMAL64 = 
    new MathContext(16, RoundingMode.HALF_EVEN);
  
  /**
   * A MathContext for the IEEE 754R Decimal128 format - 34 digit preicision and
   * HALF_EVEN rounding.
   */
  public static final MathContext DECIMAL128 = 
    new MathContext(34, RoundingMode.HALF_EVEN);
  
  /**
   * This is the serialVersionUID reported here:
   * java.sun.com/j2se/1.5.0/docs/api/serialized-form.html#java.math.MathContext
   */
  private static final long serialVersionUID = 5579720004786848255L;
  
  private int precision;
  
  private RoundingMode roundMode;
  
  /**
   * Constructs a new MathContext with the specified precision and with HALF_UP
   * rounding.
   * @param setPrecision the precision for the new MathContext
   * 
   * @throws IllegalArgumentException if precision is &lt 0.
   */
  public MathContext(int setPrecision)
  {
    this(setPrecision, RoundingMode.HALF_UP);
  }
  
  /**
   * Constructs a new MathContext with the specified precision and rounding
   * mode.
   * @param setPrecision the precision
   * @param setRoundingMode the rounding mode
   * 
   * @throws IllegalArgumentException if precision is &lt 0.
   */
  public MathContext(int setPrecision, RoundingMode setRoundingMode)
  {
    if (setPrecision &lt 0)
      throw new IllegalArgumentException("Precision cannot be less than zero.");
    precision = setPrecision;
    roundMode = setRoundingMode;
  }
  
  /**
   * Constructs a MathContext from a String that has the same form as one
   * produced by the toString() method.
   * @param val
   * 
   * @throws IllegalArgumentException if the String is not in the correct
   * format or if the precision specified is &lt 0.
   */
  public MathContext(String val)
  {
    try
    {
      int roundingModeIndex = val.indexOf("roundingMode", 10);
      precision = Integer.parseInt(val.substring(10, roundingModeIndex - 1));
      roundMode = RoundingMode.valueOf(val.substring(roundingModeIndex + 13));
    }
    catch (NumberFormatException nfe)
    {
      throw new IllegalArgumentException("String not in correct format");
    }
    catch (IllegalArgumentException iae)
    {
      throw new IllegalArgumentException("String not in correct format");
    }
    if (precision &lt 0)
      throw new IllegalArgumentException("Precision cannot be less than 0.");
  }
  
  /**
   * Returns true if x is a MathContext and has the same precision setting
   * and rounding mode as this MathContext.
   * 
   * @return true if the above conditions hold
   */
  public boolean equals(Object x)
  {
    if (!(x instanceof MathContext))
      return false;
    MathContext mc = (MathContext)x;
    return mc.precision == this.precision
           && mc.roundMode.equals(this.roundMode);
  }
  
  /**
   * Returns the precision setting.
   * @return the precision setting.
   */
  public int getPrecision()
  {
    return precision;
  }
  
  /**
   * Returns the rounding mode setting.  This will be one of 
   * RoundingMode.CEILING, RoundingMode.DOWN, RoundingMode.FLOOR, 
   * RoundingMode.HALF_DOWN, RoundingMode.HALF_EVEN, RoundingMode.HALF_UP, 
   * RoundingMode.UNNECESSARY, or RoundingMode.UP.
   * @return the rounding mode setting.
   */
  public RoundingMode getRoundingMode()
  {
    return roundMode;
  }
  
  /**
   * Returns "precision=p roundingMode=MODE" where p is an int giving the 
   * precision and MODE is UP, DOWN, HALF_UP, HALF_DOWN, HALF_EVEN, CEILING,
   * FLOOR, or UNNECESSARY corresponding to rounding modes.
   * 
   * @return a String describing this MathContext
   */
  public String toString()
  {
    return "precision="+precision+" roundingMode="+roundMode;
  }
  
  /**
   * Returns the hashcode for this MathContext.
   * @return the hashcode for this MathContext.
   */
  public int hashCode()
  {
    return precision ^ roundMode.hashCode();
  }
}

Se for usá-la, chamo de BigDecimalContext???Eu vou ter que substituir a Enumeração (o RoundingMode), e as constantes por BigDecimal.RoundingQualquerCoisa

Eu pensei que já havia lhe dito… é que no java 1.4, BigDecimal tinha apenas 4 construtores(para String,double, e BigInteger com e sem escala), e no Tiger(1.5) são 14!!!Vc pode tacar um valor BigDecimal que não dará erro de compilação.Mas no 1.4, o construtor não será reconhecido… por isso o String.valueOf…

R
Ironlynx:
com o convertTo definido pelo usuário, seria desnecessário.
É vero! Não me ative a esse detalhe e olha que eu sugeri isso antes.
RafaelRio:
Iron, ao invés de
public UnitWithValue convertTo(Unit unit) {
     return new Area(base.divide(unit.getValueOnBaseUnit()),unit);
 }
use
public UnitWithValue convertTo(Unit unit) {
     return new Area(base.divide(unit.getValueOnBaseUnit(), 2, RoundingMode.HALF_EVEN), unit);
 }
E depois,
RafaelRio:
Se rodou, agora é só definir os parâmetro que você quer no método e passar pro método divide.
Por mim, deixa assim para a versão 0.1, daí não seria preciso "re-implementar" o MathContext.
public UnitWithValue convertTo(Unit unit,int scale,int roundingmode)
Ironlynx:
Olha como é MathContext:
Mas isso eu vejo no código-fonte. Você podia ter mandado o seu MathContext.
Ironlynx:
Se for usá-la, chamo de BigDecimalContext???
Que tal BrazilUtilsMathContext? :lol:
Ironlynx:
Eu pensei que já havia lhe dito.... é que no java 1.4...
Não cheguei a lidar com Java 1.4, já fui direto pro 5. No dia-a-dia também não uso a versão 1.4. A última coisa que você deve esperar que eu saiba é esse tipo de detalhe. Aliás, agora já estou no Java 6! :D
Ironlynx:
Noop
What hell is Noop? Será que eu não posso mais me considerar um nerds de carteirinha por não saber o que é isso?
I
Por mim, deixa assim para a versão 0.1, daí não seria preciso "re-implementar" o MathContext.

Blz então!A única diferença era encher de constantes BigDecimal.Rounding…

NOP ou NOOP (abreviatura de No OPeration) que é uma instrução em assembly, mas na verdade, pode ser usado apenas para dizer que “Não é assim”, ou não funciona!Sacou?

O garoto tá moderno… :smiley:
Mas eu tb jah tô no Mustang, mas tá longe de eu sacar a maioria das features dele…

Criado 14 de dezembro de 2006
Ultima resposta 13 de jan. de 2007
Respostas 16
Participantes 2