12

浮動小数点値を、上限と下限によって定義される特定の浮動小数点範囲に制限する慣用的なスカラ型はありますか?

具体的には、0.0 から 1.0 の間の値しか持てない float 型が必要です。

より具体的には、Int を受け取る関数と、この Int を 0.0 から 1.0 の範囲にマップする別の関数を疑似スカラで記述しようとしています。

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

すでにボードを検索しましたが、適切なものは見つかりませんでした。いくつかの暗黙の魔法またはカスタム typedef も私にとっては問題ありません。

4

3 に答える 3

8

Scala にはない依存型( example )を除いて、静的に行う方法がわかりません。定数のみを扱う場合は、必要なチェックを実行するマクロまたはコンパイラ プラグインを使用できるはずですが、任意の float 型の式がある場合は、実行時チェックに頼らなければならない可能性が非常に高くなります。

ここにアプローチがあります。float 値が必要な範囲内にあることを確認するためにランタイム チェックを実行するクラスを定義します。

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によるコメントのアドレス指定

自然数に依存する型を、従属型を (完全に) サポートしない型システムでエンコードできる理由を直感的に説明します。ペアノ数字を使用したパス依存型として。ただし、実数はもはや列挙できないため、実数の各要素に対応する型を体系的に作成することはできなくなりました。

現在、コンピューターの浮動小数点数と実数は最終的には有限の集合ですが、型システムで合理的に効率的に列挙するにはまだ大きすぎます。もちろん、コンピューターの自然数のセットも非常に大きいため、型としてエンコードされたペアノ数値の算術演算に問題が生じます。この記事の最後の段落を参照してください。ただし、たとえばHListsで証明されているように、最初のn (かなり小さいnの場合) の自然数を使用するだけで十分な場合が多いと主張します。float に対応する主張を行うことは説得力に欠けます.0.0 と 1.0 の間で 10,000 個の float をエンコードする方が良いでしょうか?

于 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 に答える