是否有一些惯用的scala类型将浮点值限制为由上限和下限定义的给定浮点范围?
具体我希望浮点类型只允许值介于0.0和1.0之间。
更具体我将要编写一个函数,它接受一个Int和另一个函数,将这个Int映射到0.0到1.0之间的范围,在伪scala中:
def foo(x : Int, f : (Int => {0.0,...,1.0})) {
// ....
}
已经搜查过电路板,但没有找到合适的电话。一些隐式魔法或自定义typedef也适合我。
答案 0 :(得分: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
如果可以使用value classes来获得某些性能,那将是很好的,但是在构造函数(当前)中调用requires
会禁止这样做。
编辑:解决@paradigmatic的评论
这是一个直观的论证,为什么依赖于自然数的类型可以在不完全支持依赖类型的类型系统中编码,但是范围浮点数可能不能:自然数是可枚举的set,这使得可以将每个元素编码为path-dependent types using Peano numerals。然而,实数不再是可枚举的,因此不再可能系统地创建与实数的每个元素相对应的类型。
现在,计算机浮点数和实数最终都是有限集,但仍然可以在类型系统中合理有效地枚举。这组计算机自然数当然也非常大,因此对于编码为类型的Peano数字进行算术会产生问题,请参阅this article的最后一段。但是,我声称使用第一个 n (对于一个相当小的 n )自然数通常就足够了,例如,由{{3}证明}。对浮点数做出相应的声明是不太令人信服的 - 编辑10,000浮点数在0.0和1.0之间,或者更确切地说10,000在0.0和100.0之间编码会更好吗?
答案 1 :(得分: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")
}
}
}
它需要提示编译器使用隐式类,方法是显式键入summands或选择Scala的Float不提供的方法。
这种方式至少检查是集中的,因此如果性能有问题,可以将其关闭。正如mhs所指出的,如果将此类转换为隐式值类,则必须从构造函数中删除检查。
我添加了@inline注释,但我不确定,如果这对隐式类有用/必要。
最后,我没有成功用
取消Scala Float“+”import scala.{Float => RealFloat}
import scala.Predef.{float2Float => _}
import scala.Predef.{Float2float => _}
可能有另一种方法来实现这一点,以推动编译器使用 implict类
答案 2 :(得分: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
。