12

是否有一些惯用的 scala 类型将浮点值限制为由上限和下限定义的给定浮点范围?

具体我想要一个浮点类型,它只允许具有 0.0 到 1.0 之间的值。

更具体地说,我将编写一个函数,该函数接受一个 Int 和另一个函数,该函数将这个 Int 映射到 0.0 到 1.0 之间的范围,在伪 scala 中:

def foo(x : Int, f : (Int => {0.0,...,1.0})) {
    // ....
}

已经搜索了董事会,但没有找到合适的。一些隐式魔术或自定义类型定义对我来说也可以。

4

3 回答 3

8

我不知道如何静态地做到这一点,除了依赖类型example),这是 Scala 没有的。如果您只处理常量,则应该可以使用宏或编译器插件来执行必要的检查,但如果您有任意浮点类型的表达式,您很可能不得不求助于运行时检查。

这是一种方法。定义一个执行运行时检查以确保浮点值在所需范围内的类:

abstract class AbstractRangedFloat(lb: Float, ub: Float) {
  require (lb <= value && value <= ub, s"Requires $lb <= $value <= $ub to hold")

  def value: Float
}

您可以按如下方式使用它:

case class NormalisedFloat(val value: Float)
  extends AbstractRangedFloat(0.0f, 1.0f)

NormalisedFloat(0.99f)
NormalisedFloat(-0.1f) // Exception

或者作为:

case class RangedFloat(val lb: Float, val ub: Float)(val value: Float)
  extends AbstractRangedFloat(lb, ub)

val RF = RangedFloat(-0.1f, 0.1f) _
RF(0.0f)
RF(0.2f) // Exception

如果可以使用值类来获得一些性能,那就太好了,但是requires构造函数中的调用(当前)禁止这样做。


编辑: @paradigmatic 发表评论

这是一个直观的论点,为什么可以在不(完全)支持依赖类型的类型系统中对依赖于自然数的类型进行编码,但范围浮点数可能不能: 自然数是可枚举的集合,这使得对每个元素进行编码成为可能作为使用 Peano 数字的路径相关类型。然而,实数不再是可枚举的,因此不再可能系统地创建对应于实数的每个元素的类型。

现在,计算机浮点数和实数最终都是有限集,但仍然要大到可以在类型系统中合理有效地枚举。计算机自然数的集合当然也非常大,因此对编码为类型的 Peano 数字的算术提出了问题,请参阅本文的最后一段。但是,我声称使用前n(对于相当小的n)自然数通常就足够了,例如,HLists证明了这一点。对浮点数做出相应的声明不太令人信服——在 0.0 和 1.0 之间编码 10,000 个浮点数,或者在 0.0 和 100.0 之间编码 10,000 个浮点数会更好吗?

于 2013-05-16T13:27:28.900 回答
2

这是使用隐式类的另一种方法:

object ImplicitMyFloatClassContainer {

  implicit class MyFloat(val f: Float) {
    check(f)

    val checksEnabled = true

    override def toString: String = {
      // The "*" is just to show that this method gets called actually
      f.toString() + "*"
    }

    @inline
    def check(f: Float) {
      if (checksEnabled) {
        print(s"Checking $f")
        assert(0.0 <= f && f <= 1.0, "Out of range")
        println(" OK")
      }
    }

    @inline
    def add(f2: Float): MyFloat = {
      check(f2)

      val result = f + f2
      check(result)

      result
    }

    @inline
    def +(f2: Float): MyFloat = add(f2)
  }

}

object MyFloatDemo {
  def main(args: Array[String]) {
    import ImplicitMyFloatClassContainer._

    println("= Checked =")

    val a: MyFloat = 0.3f
    val b = a + 0.4f
    println(s"Result 1: $b")

    val c = 0.3f add 0.5f
    println("Result 2: " + c)

    println("= Unchecked =")

    val x = 0.3f + 0.8f
    println(x)

    val f = 0.5f
    val r = f + 0.3f
    println(r)

    println("= Check applied =")

    try {
      println(0.3f add 0.9f)
    } catch {
      case e: IllegalArgumentException => println("Failed as expected")
    }
  }
}

它需要编译器使用隐式类的提示,通过显式键入和或选择 Scala 的 Float 未提供的方法。

这样至少检查是集中的,所以如果性能是一个问题,你可以关闭它。正如 mhs 所指出的,如果将此类转换为隐式值 class,则必须从构造函数中删除检查。

我添加了@inline 注释,但我不确定这是否对隐式类有用/必要。

最后,我没有成功取消导入Scala Float“+”

import scala.{Float => RealFloat}
import scala.Predef.{float2Float => _}
import scala.Predef.{Float2float => _}

可能还有另一种方法可以实现这一点,以推动编译器使用隐式类

于 2013-05-16T14:54:43.990 回答
2

您可以使用mhs指出的值类:

case class Prob private( val x: Double ) extends AnyVal {
  def *( that: Prob ) = Prob( this.x * that.x )
  def opposite = Prob( 1-x )
}

object Prob {
  def make( x: Double ) = 
    if( x >=0 && x <= 1 ) 
      Prob(x) 
    else 
      throw new RuntimeException( "X must be between 0 and 1" )
}

它们必须使用伴随对象中的工厂方法创建,这将检查范围是否正确:

scala> val x = Prob.make(0.5)
x: Prob = Prob(0.5)

scala> val y = Prob.make(1.1)
java.lang.RuntimeException: X must be between 0 and 1

但是,使用永远不会产生超出范围的数字的操作不需要有效性检查。例如*opposite

于 2013-05-17T09:42:04.483 回答