Scala Cookbook翻译 Chapter 2.Numbers 第二部分

2.4 代替++和–

  • 问题:你想要在其他语言用++和–操作符增长数值或减少数值,但是在Scala中没有这些运算符。

    2.4.1 解决方案

  • 因为val字段时不可改变的,他们不能增加或者减少,但是var整形字段可以通过+=和-=方法改变。

    scala> var a = 1
    a: Int = 1
    
    scala> a += 1
    
    scala> println(a)
    2
    
    scala> a −= 1
    
    scala> println(a)
    1
    
  • 作为额外的好处,可以用同样的方法进行乘法和除法:

    scala> var i = 1
    i: Int = 1
    
    scala> i *= 2
    
    scala> println(i)
    2
    
    scala> i *= 2
    
    scala> println(i)
    4
    
    scala> i /= 2
    
    scala> println(i)
    2
    
  • 注意这些符号不是操作符,它们是在声明为var后的Int字段里存在的方法。如果在val字段里使用就会报错编译时错误:

    scala> val x = 1
    x: Int = 1
    
    scala> x += 1
    <console>:9: error: value += is not a member of Int
            x += 1
              ^
    
  • 如前所述,+=,-+,*=,/=这些符号不是操作符,它们是方法。

    2.4.2 讨论

  • 另一个好处就是可以在除了Int之外的其他类型里调用同样的方法。举例使用Double和Float:

    scala> var x = 1d
    x: Double = 1.0
    
    scala> x += 1
    
    scala> println(x)
    2.0
    
    scala> var x = 1f
    x: Float = 1.0
    
    scala> x += 1
    
    scala> println(x)
    2.0
    
  • 查看更多

    Martin Odersky 讨论Actors如何作为一个库添加到Scala中。drdobbs.com

2.5 浮点数比较

  • 问题:需要比较两个浮点数,但是和在其他语言一样,本该相等的两个浮点数可能不一样。

    2.5.1 解决方案

  • 就像在Java和其他语言一样,通过创建一个指定比较精度的方法来解决这个问题。下面的“约等于”方法进行演示:

    def ~=(x: Double, y: Double, precision: Double) = {
        if ((x - y).abs < precision) true else false
    }
    
    scala> val a = 0.3
    a: Double = 0.3
    
    scala> val b = 0.1 + 0.2
    b: Double = 0.30000000000000004
    
    scala> ~=(a, b, 0.0001)
    res0: Boolean = true
    
    scala> ~=(b, a, 0.0001)
    res1: Boolean = true
    

2.5.2 讨论

  • 当你开始使用浮点数时,会学到0.1加0.1等于0.2:

    scala> 0.1 + 0.1
    res38: Double = 0.2
    
  • 但是0.1加0.2并不精确等于0.3

    scala> 0.1 + 0.2
    res37: Double = 0.30000000000000004
    
  • 这种微妙误差使得浮点数比较是一个真实的问题:

    scala> val a = 0.3
    a: Double = 0.3
    
    scala> val b = 0.1 + 0.2
    b: Double = 0.30000000000000004
    
    scala> a == b
    res0: Boolean = false
    
  • 因此最终编写自己的函数使用精度(或公差)来比较浮点数。
  • 可以使用隐式转换在Double类里添加方法,这会使得下面的代码非常可读:

    if (a ~= b) ...
    
  • 或者可以使用相同的方法添加到公用程序对象

    object MathUtils {
        def ~=(x: Double, y: Double, precision: Double) = {
            if ((x - y).abs < precision) true else false
        }
    }
    
    //调用
    println(MathUtils.~=(a, b, 0.000001))
    
  • 在隐式转换中,~=名字非常可读。但是在共用程序对象中他看起来不太正确,所以叫做approximatelyEqual,equalWithinTolerance或者其他名字更加合适。
  • 查看更多:

    Arbitrary-precision arithmetic
    What every computer scientist should know about floating-point arithmetic

2.6 处理非常大的数

  • 问题:写一个程序需要处理非常大的整数或者小数。

    2.6.1 解决方案

  • 使用Scala的BigInt和BigDecimal类。

    scala> var b = BigInt(1234567890)
    b: scala.math.BigInt = 1234567890
    
    scala> var b = BigDecimal(123456.789)
    b: scala.math.BigDecimal = 123456.789
    
  • 和Java中不同,这些类支持所使用的数值类型的所有操作。

    scala> b + b
    res0: scala.math.BigInt = 2469135780
    
    scala> b * b
    res1: scala.math.BigInt = 1524157875019052100
    
    scala> b += 1
    
    scala> println(b)
    1234567891
    
  • 数值类型转换:

    scala> b.toInt
    res2: Int = 1234567891
    
    scala> b.toLong
    res3: Long = 1234567891
    
    scala> b.toFloat
    res4: Float = 1.23456794E9
    
    scala> b.toDouble
    res5: Double = 1.234567891E9
    
  • 为避免发生错误,可先测试是否可以转换成其他类型

    scala> b.isValidByte
    res6: Boolean = false
    
    scala> b.isValidChar
    res7: Boolean = false
    
    scala> b.isValidShort
    res8: Boolean = false
    
    scala> if (b.isValidInt) b.toInt
    res9: AnyVal = 1234567890
    

    2.6.2 讨论

    • Scala的BigInt和BigDecimal比Java中的BigInteger和BigDecimal更容易使用,就像其他数值类型一样工作,并且是精确的,比Java改善很多。
    • 使用之前可以检查其他数值类型的最大值:

      scala> Byte.MaxValue
      res0: Byte = 127
      
      scala> Short.MaxValue
      res1: Short = 32767
      
      scala> Int.MaxValue
      res2: Int = 2147483647
      
      scala> Long.MaxValue
      res3: Long = 9223372036854775807
      
      scala> Double.MaxValue
      res4: Double = 1.7976931348623157E308
      
  • 在基本数值类型中使用无穷大(PositiveInfinity)和无穷小(NegativeInfinity)

    scala> Double.PositiveInfinity
    res0: Double = Infinity
    
    scala> Double.NegativeInfinity
    res1: Double = -Infinity
    
    scala> 1.7976931348623157E308 > Double.PositiveInfinity
    res45: Boolean = false
    
  • 查看更多

    The Scala BigInt class
    The Scala BigDecimal class

2.7 随机数生成

  • 问题:需要创建随机数,比如当测试应用时进行模拟,或者其他的使用情况。

    2.7.1 解决方案

  • 通过scala.util.Random类创建随机数:

    scala> val r = scala.util.Random
    r: scala.util.Random = scala.util.Random@13eb41e5
    
    scala> r.nextInt
    res0: Int = −1323477914
    
  • 限制随机数的最大值:

    scala> r.nextInt(100)
    res1: Int = 58
    
  • Int返回值在0(包括)和指定值(不包括)之前,所以指定100返回整形从0到99。
  • 创建Float随机数:

    // returns a value between 0.0 and 1.0
    scala> r.nextFloat
    res2: Float = 0.50317204
    
  • 创建Double随机数:

    // returns a value between 0.0 and 1.0
    scala> r.nextDouble
    res3: Double = 0.6946000981900997
    
  • 可以在创建随机数对象的时候指定最大的Int或Long:

    scala> val r = new scala.util.Random(100)
    r: scala.util.Random = scala.util.Random@bbf4061
    
  • 也可以在随机数对象创建之后设置seed.

    r.setSeed(1000L)
    

    2.7.2 讨论

  • 除上述用法外,还可以生成随机字符:

    // random characters
    scala> r.nextPrintableChar
    res0: Char = H
    
    scala> r.nextPrintableChar
    res1: Char = r
    
  • Scala可创建随机长度的数字,对于测试非常有用:

    // create a random length range
    scala> var range = 0 to r.nextInt(10)
    range: scala.collection.immutable.Range.Inclusive = Range(0, 1, 2, 3)
    
    scala> range = 0 to r.nextInt(10)
    range: scala.collection.immutable.Range.Inclusive = Range(0, 1)
    
  • 添加for/yield循环修改数值:

    scala> for (i <- 0 to r.nextInt(10)) yield i * 2
    res0: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 2, 4)
    
  • 创建随机长度集合的其他类型,下面是Float型:

    scala> for (i <- 0 to r.nextInt(10)) yield (i * r.nextFloat)
    res1: scala.collection.immutable.IndexedSeq[Float] =
        Vector(0.0, 0.71370363, 1.0783684)
    
  • 随机参数集合的可打印字符:

    scala> for (i <- 0 to r.nextInt(10)) yield r.nextPrintableChar
    res2: scala.collection.immutable.IndexedSeq[Char] = Vector(x, K, ^, z, w)
    
  • 使用nextPrintableChar方法需要注意,更好的方法是去控制使用的字符,查看更多“如何创建一个字母或字母数字字符的集合”
  • 相反,可以创建一个已知长度的序列,填满随机数:

    scala> for (i <- 1 to 5) yield r.nextInt(100)
    res3: scala.collection.immutable.IndexedSeq[Int] = Vector(88, 94, 58, 96, 82)
    
  • 查看更多:

    The Scala Random class
    11.29章 “Using a Range”
    Scala: How to create a list of alpha or alphanumeric characters
    Different ways to create random strings in Scala

2.8 创建数值范围,集合或者数组

  • 问题:在一个for循环中或者因为测试目的,需要创建一个数值范围,集合或者数组。

    2.8.1 解决方案

  • 使用Int类中的方法创建需要元素的范围:

    scala> val r = 1 to 10
    r: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5,
        6, 7, 8, 9, 10)
    
  • 设置步长:

    scala> val r = 1 to 10 by 2
    r: scala.collection.immutable.Range = Range(1, 3, 5, 7, 9)
    
    scala> val r = 1 to 10 by 3
    r: scala.collection.immutable.Range = Range(1, 4, 7, 10)
    
  • 通常使用在for循环中:

    scala> for (i <- 1 to 5) println(i)
    1 
    2 
    3
    4 
    5
    
  • 创建范围的时候,可以用until代替to

    scala> for (i <- 1 until 5) println(i)
    1 
    2 
    3 
    4
    

    2.8.2 讨论

  • 转换Range成其他序列,比如Array或者List:

    scala> val x = 1 to 10 toArray
    x: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    
    scala> val x = 1 to 10 toList
    x: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    
  • 虽然中缀表达式语法在很多情况下是清晰的(比如for循环),但它最好使用下面这种方法:

    scala> val x = (1 to 10).toList
    x: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    
    scala> val x = (1 to 10).toArray
    x: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    
  • 使得其工作的主要是to和until方法,这个可在RichInt类找到。当键入以下代码时,其实是在调用RichInt类的方法:

    1 to
    
  • 在REPL通过在一个整型上使用这个语法证明to是一个方法:

    1.to(10)
    
  • 与上章结合,创建一个随机长度范围,对于测试非常有用。

    scala> var range = 0 to scala.util.Random.nextInt(10)
    range: scala.collection.immutable.Range.Inclusive = Range(0, 1, 2, 3)
    
  • 在for/yield结构使用一个范围,你没必要限制你的范围到一个序列化数字:

    scala> for (i <- 1 to 5) yield i * 2
    res0: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10)
    
  • 也不必限制你的范围只是整数:

    scala> for (i <- 1 to 5) yield i.toDouble
    res1: scala.collection.immutable.IndexedSeq[Double] =
        Vector(1.0, 2.0, 3.0, 4.0, 5.0)
    
  • 查看更多

    The Scala RichInt class
    Scala - infix vs dot notation

2.9 格式化数字和货币

  • 格式化数字控制小数位和逗号,尤其是在打印输出时。

    2.9.1 解决方案

  • 对于基本数值转换,可用1.4章说的使用f插值器:

    scala> val pi = scala.math.Pi
    pi: Double = 3.141592653589793
    
    scala> println(f"$pi%1.5f")
    3.14159
    
  • 更多例子:

    scala> f"$pi%1.5f"
    res0: String = 3.14159
    
    scala> f"$pi%1.2f"
    res1: String = 3.14
    
    scala> f"$pi%06.2f" //6表示6位,包括小数位和小数点,不足补0,和C不一样
    res2: String = 003.14
    
  • 使用2.10之前的版本,或者更喜欢显式调用format方法,使用以下方法:

    scala> "%06.2f".format(pi)
    res3: String = 003.14
    
  • 通过使用java.text.NumberFormat类的getIntegerInstance方法添加逗号:

    scala> val formatter = java.text.NumberFormat.getIntegerInstance
    formatter: java.text.NumberFormat = java.text.DecimalFormat@674dc
    
    scala> formatter.format(10000)
    res0: String = 10,000
    
    scala> formatter.format(1000000)
    res1: String = 1,000,000
    
  • 可以通过getIntegerInstance方法设置locale符号:

    scala> val locale = new java.util.Locale("de", "DE")
    locale: java.util.Locale = de_DE
    
    scala> val formatter = java.text.NumberFormat.getIntegerInstance(locale)
    formatter: java.text.NumberFormat = java.text.DecimalFormat@674dc
    
    scala> formatter.format(1000000)
    res2: String = 1.000.000
    
  • 通过getInstance格式化处理浮点数:

    scala> val formatter = java.text.NumberFormat.getInstance
    formatter: java.text.NumberFormat = java.text.DecimalFormat@674dc
    
    scala> formatter.format(10000.33)
    res0: String = 10,000.33
    
  • 使用getCurrencyInstance格式化输入货币:

    scala> val formatter = java.text.NumberFormat.getCurrencyInstance
    formatter: java.text.NumberFormat = java.text.DecimalFormat@67500
    
    scala> println(formatter.format(123.456789))
    $123.46
    
    scala> println(formatter.format(1234.56789))
    $1,234.57
    
    scala> println(formatter.format(12345.6789))
    $12,345.68
    
    scala> println(formatter.format(123456.789))
    $123,456.79
    
  • 这种方法处理国际货币:

    scala> import java.util.{Currency, Locale}
    import java.util.{Currency, Locale}
    
    scala> val de = Currency.getInstance(new Locale("de", "DE"))
    de: java.util.Currency = EUR
    
    scala> formatter.setCurrency(de)
    
    scala> println(formatter.format(123456.789))
    EUR123,456.79
    
  • 查看更多

    My printf cheat sheet
    Joda Money library
    JSR 354: Money and Currency API