Scala duplas e precisão

117

Existe uma função que pode truncar ou arredondar um Double? Em um ponto do meu código, gostaria de um número como: 1.23456789ser arredondado para1.23

Richsoni
fonte
9
Depois de olhar para todas as respostas, acho que a resposta curta é não? :)
Marsellus Wallace
4
@Gevorg Você me fez rir. Eu sou novo no Scala por causa de outras linguagens pesadas em números, e meu queixo quase caiu no chão lendo este tópico. Este é um estado de coisas insano para uma linguagem de programação.
ely

Respostas:

150

Você pode usar scala.math.BigDecimal:

BigDecimal(1.23456789).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble

Existem vários outros modos de arredondamento , que infelizmente não estão muito bem documentados no momento (embora seus equivalentes em Java estejam ).

Travis Brown
fonte
5
Muito provável, eu diria. Qualquer coisa que envolva redes ou finanças pode exigir arredondamento e também desempenho.
Rex Kerr
27
Suponho que há pessoas para quem uma longa chamada para uma biblioteca desajeitada é mais compreensível do que a simples matemática. Eu recomendo "%.2f".format(x).toDoublenesse caso. Apenas 2x mais lento e você só precisa usar uma biblioteca que já conhece.
Rex Kerr
6
@RexKerr, você não está arredondando neste caso .. simplesmente truncando.
José Leal
16
@ JoséLeal - Hein? scala> "%.2f".format(0.714999999999).toDouble res13: Double = 0.71mas scala> "%.2f".format(0.715).toDouble res14: Double = 0.72.
Rex Kerr de
5
@RexKerr Eu prefiro seu modo string.format, mas em locales sa mine (finlandês), deve-se tomar cuidado para corrigir o locale ROOT. Por exemplo, "% .2f" .formatLocal (java.util.Locale.ROOT, x) .toDouble. Parece que o formato usa ',' por causa do local, enquanto toDouble não é capaz de aceitá-lo e lança uma NumberFormatException. É claro que isso se baseia em onde seu código está sendo executado , não onde ele é desenvolvido.
akauppi
77

Aqui está outra solução sem BigDecimals

Truncar:

(math floor 1.23456789 * 100) / 100

Volta:

(math rint 1.23456789 * 100) / 100

Ou para qualquer duplo n e precisão p:

def truncateAt(n: Double, p: Int): Double = { val s = math pow (10, p); (math floor n * s) / s }

Semelhante pode ser feito para a função de arredondamento, desta vez usando currying:

def roundAt(p: Int)(n: Double): Double = { val s = math pow (10, p); (math round n * s) / s }

que é mais reutilizável, por exemplo, ao arredondar valores em dinheiro, o seguinte pode ser usado:

def roundAt2(n: Double) = roundAt(2)(n)
Kaito
fonte
8
roundAt2 deve ser def roundAt2 (n: Double) = roundAt (2) (n) no?
C4stor de
isso parece retornar um resultado incorreto para NaN, não é?
jangorecki
o problema com flooré que truncateAt(1.23456789, 8)retornará 1.23456788enquanto roundAt(1.23456789, 8)retornará o valor correto de1.23456789
Todor Kolev
35

Como ninguém mencionou a %operadora ainda, aí vem. Ele apenas faz o truncamento e você não pode contar com o valor de retorno para não ter imprecisões de ponto flutuante, mas às vezes é útil:

scala> 1.23456789 - (1.23456789 % 0.01)
res4: Double = 1.23
akauppi
fonte
2
Não recomendaria isso, embora seja minha própria resposta: os mesmos problemas de imprecisão mencionados por @ryryguy no comentário de outra resposta afetam aqui também. Use string.format com a localidade Java ROOT (comentarei sobre isso lá).
akauppi
isso é perfeito se você precisa apenas renderizar o valor e nunca usá-lo em operações subsequentes. obrigado
Alexander Arendar
3
aqui está algo engraçado: 26.257391515826225 - 0.057391515826223094 = 26.200000000000003
kubudi
12

E se :

 val value = 1.4142135623730951

//3 decimal places
println((value * 1000).round / 1000.toDouble)

//4 decimal places
println((value * 10000).round / 10000.toDouble)
céu azul
fonte
solução muito limpa. Aqui está o meu truncamento: ((1.949 * 1000).toInt - ((1.949 * 1000).toInt % 10)) / 1000.toDoublemas não testei muito. Este código faria 2 casas decimais.
robert
7

Edit: corrigido o problema que @ryryguy apontou. (Obrigado!)

Se você quer que seja rápido, Kaito tem a ideia certa. math.powé lento, no entanto. Para qualquer uso padrão, é melhor você usar uma função recursiva:

def trunc(x: Double, n: Int) = {
  def p10(n: Int, pow: Long = 10): Long = if (n==0) pow else p10(n-1,pow*10)
  if (n < 0) {
    val m = p10(-n).toDouble
    math.round(x/m) * m
  }
  else {
    val m = p10(n).toDouble
    math.round(x*m) / m
  }
}

Isso é cerca de 10x mais rápido se você estiver na faixa de Long(ou seja, 18 dígitos), então você pode arredondar em qualquer lugar entre 10 ^ 18 e 10 ^ -18.

Rex Kerr
fonte
3
Cuidado, multiplicar pelo recíproco não funciona de forma confiável, porque pode não ser representável de forma confiável como um duplo: scala> def r5(x:Double) = math.round(x*100000)*0.000001; r5(0.23515)==> res12: Double = 0.023514999999999998. Em vez disso, divida pelo significado:math.round(x*100000)/100000.0
ryryguy
Também pode ser útil substituir a p10função recursiva por uma pesquisa de array: o array aumentará o consumo de memória em cerca de 200 bytes, mas provavelmente salvará várias iterações por chamada.
Levi Ramsey
4

Você pode usar classes implícitas:

import scala.math._

object ExtNumber extends App {
  implicit class ExtendedDouble(n: Double) {
    def rounded(x: Int) = {
      val w = pow(10, x)
      (n * w).toLong.toDouble / w
    }
  }

  // usage
  val a = 1.23456789
  println(a.rounded(2))
}
Mitrakov Artem
fonte
1
Especifique que este método é apenas para truncar e não para arredondar corretamente.
bobo32
4

Para os interessados, aqui ficam alguns horários para as soluções sugeridas ...

Rounding
Java Formatter: Elapsed Time: 105
Scala Formatter: Elapsed Time: 167
BigDecimal Formatter: Elapsed Time: 27

Truncation
Scala custom Formatter: Elapsed Time: 3 

O truncamento é o mais rápido, seguido por BigDecimal. Lembre-se de que esses testes foram feitos executando a execução do norma scala, sem usar nenhuma ferramenta de benchmarking.

object TestFormatters {

  val r = scala.util.Random

  def textFormatter(x: Double) = new java.text.DecimalFormat("0.##").format(x)

  def scalaFormatter(x: Double) = "$pi%1.2f".format(x)

  def bigDecimalFormatter(x: Double) = BigDecimal(x).setScale(2, BigDecimal.RoundingMode.HALF_UP).toDouble

  def scalaCustom(x: Double) = {
    val roundBy = 2
    val w = math.pow(10, roundBy)
    (x * w).toLong.toDouble / w
  }

  def timed(f: => Unit) = {
    val start = System.currentTimeMillis()
    f
    val end = System.currentTimeMillis()
    println("Elapsed Time: " + (end - start))
  }

  def main(args: Array[String]): Unit = {

    print("Java Formatter: ")
    val iters = 10000
    timed {
      (0 until iters) foreach { _ =>
        textFormatter(r.nextDouble())
      }
    }

    print("Scala Formatter: ")
    timed {
      (0 until iters) foreach { _ =>
        scalaFormatter(r.nextDouble())
      }
    }

    print("BigDecimal Formatter: ")
    timed {
      (0 until iters) foreach { _ =>
        bigDecimalFormatter(r.nextDouble())
      }
    }

    print("Scala custom Formatter (truncation): ")
    timed {
      (0 until iters) foreach { _ =>
        scalaCustom(r.nextDouble())
      }
    }
  }

}
cevaris
fonte
1
Caro scalaCustom não está terminando, está apenas truncando
Ravinder Payal
hmm, OP não era específico para arredondamento ou truncamento; ...truncate or round a Double.
cevaris
Mas, na minha opinião, comparar a velocidade / tempo de execução da função de truncamento com as funções de arredondamento é inadequado. É por isso que pedi para você esclarecer ao leitor que o recurso personalizado apenas trunca. E a função truncar / customizada mencionada por você pode ser simplificada ainda mais. val doubleParts = double. toString.split (".") Agora pegue os primeiros dois caracteres de doubleParts.taile concat com as strings "." and doubleParts. headand parse para dobrar.
Ravinder Payal
1
atualizado, parece melhor? também sua sugestão toString.split(".")e doubleParts.head/tailsugestão podem sofrer de alocação de array extra mais concatenação de string. precisaria testar para ter certeza.
cevaris
3

Recentemente, enfrentei um problema semelhante e resolvi usando a seguinte abordagem

def round(value: Either[Double, Float], places: Int) = {
  if (places < 0) 0
  else {
    val factor = Math.pow(10, places)
    value match {
      case Left(d) => (Math.round(d * factor) / factor)
      case Right(f) => (Math.round(f * factor) / factor)
    }
  }
}

def round(value: Double): Double = round(Left(value), 0)
def round(value: Double, places: Int): Double = round(Left(value), places)
def round(value: Float): Double = round(Right(value), 0)
def round(value: Float, places: Int): Double = round(Right(value), places)

Eu usei esse problema do SO. Eu tenho algumas funções sobrecarregadas para opções Float \ Double e implícita \ explícita. Observe que você precisa mencionar explicitamente o tipo de retorno no caso de funções sobrecarregadas.

Khalid Saifullah
fonte
Além disso, você pode usar a abordagem de @ rex-kerr para o poder em vez de Math.pow
Khalid Saifullah
1

Eu não usaria BigDecimal se você se preocupa com desempenho. BigDecimal converte números em string e, em seguida, analisa novamente:

  /** Constructs a `BigDecimal` using the decimal text representation of `Double` value `d`, rounding if necessary. */
  def decimal(d: Double, mc: MathContext): BigDecimal = new BigDecimal(new BigDec(java.lang.Double.toString(d), mc), mc)

Vou me limitar às manipulações matemáticas, conforme sugerido por Kaito .

bigonazzi
fonte
0

Um pouco estranho, mas legal. Eu uso String e não BigDecimal

def round(x: Double)(p: Int): Double = {
    var A = x.toString().split('.')
    (A(0) + "." + A(1).substring(0, if (p > A(1).length()) A(1).length() else p)).toDouble
}
jafed
fonte
0

Você pode fazer: Math.round(<double precision value> * 100.0) / 100.0 Mas Math.round é mais rápido, mas se decompõe mal em casos de canto com um número muito alto de casas decimais (por exemplo, redondo (1000.0d, 17)) ou parte inteira grande (por exemplo, redondo (90080070060.1d, 9) )

Use Bigdecimal, é um pouco ineficiente, pois converte os valores em string, mas mais alívio: BigDecimal(<value>).setScale(<places>, RoundingMode.HALF_UP).doubleValue() use o modo de arredondamento de sua preferência.

Se você estiver curioso e quiser saber mais detalhes por que isso acontece, você pode ler isto: insira a descrição da imagem aqui

Frostcs
fonte