This document is about: QUANTUM 3
SWITCH TO

This page has not been upgraded to Quantum 3.0 yet. The information may be out of date.

固定小数点数

はじめに

Quantumでは、FP構造体(固定小数点数)ですべてのfloat/doubleの使用を完全に置き換えることで、クロスプラットフォーム決定論性を保証します。また、FPVector2FPVector3FPMatrixFPQuaternionRNGSessionFPBounds2などの一般的な数学データ構造も提供されています。物理やナビゲーションを含むQuantumのすべてのシステムは、計算にFPの値のみを使用します。

Quantumで実装された固定小数点数型はQ48.16です。これは精度とパフォーマンスのバランスを取ったものですが、後者にやや寄っています。

内部的にFPlong1つを使用して、固定小数点数(整数部 + 小数部)を表現します。long値は、FP.RawValueから取得/設定できます。

QuantumのFP数学は、三角関数と平方根関数を高速化するため、入念に調整されたルックアップテーブルを使用しています(Assets/Photon/Quantum/Resources/LUTを参照)。

FPのパース

表現可能なFPの小数部は限られていて、doubleほど精度が高くありません。パースは近似となり、最も近い有効なFP精度に丸められます。

  • float1.1fを、FPにパースした後に再びfloatに変換すると、1.09999999fになる可能性があります。
  • 異なるパースメソッドは、同じ端末で異なる結果を生じる可能性があります。

    C#

    FP.FromFloat_UNSAFE(1.1f).RawValue != FP.FromString("1.1").RawValue
    

要約

  • floatからの変換はエディター上でのみ使用し、シミュレーションやランタイム時には決して使用しないでください。
  • 可能な限りrawから変換する方が良いです。

FP.FromFloat_UNSAFE()

floatからの変換は、丸め誤差のため決定論的ではないため、シミュレーションでは決して行ってはいけません。シミュレーション中にこの変換を行うと、確実に同期ズレが発生します。

ただし、エディター上ビルド時に使用することは可能です。変換されたデータ(FP)が最初に作成され、それから全員に共有されます。重要: この方法で生成されたデータは、異なるマシンでは互換性がない可能性があります。

C#

var v = FP.FromFloat_UNSAFE(1.1f);

FP.FromString_UNSAFE()

これは内部的にstringfloatとしてパースしてFPに変換します。FromFloat_UNSAFE()のすべての注意点は、ここにも適用されます。

C#

var v = FP.FromFloat_UNSAFE("1.1");

FP.FromString()

これは決定論的であるため、どこでも安全に使用できますが、最もパフォーマンスが良い方法ではありません。典型的なユースケースは、クライアントがサーバーからパッチ(バランス調整のデータ)をロードして、Quantumアセットのデータを更新するために使用するものです。

文字列のロケールに注意してください!英語の数値フォーマットのみがパースされるため、ドット(例:1000.01f)が必要です。

C#

var v = FP.FromFloat("1.1");

FP.FromRaw()

内部表現に倣うもののため、安全で高速です。

C#

var v = FP.FromRaw(72089);

このスニペットは、UnityでFP変換ウインドウを作成し、便利な変換を行うために使用できます。

C#

using System;
using UnityEditor;
using Photon.Deterministic;

public class FPConverter : EditorWindow {
  private float _f;
  private FP _fp;

  [MenuItem("Quantum/FP Converter")]
  public static void ShowWindow() {
    GetWindow(typeof(FPConverter), false, "FP Converter");
  }

  public virtual void OnGUI() {
    _f = EditorGUILayout.FloatField("Input", _f);
    try {
      _fp = FP.FromFloat_UNSAFE(_f);
      var f = _fp.AsFloat;
      var rect = EditorGUILayout.GetControlRect(true);
      EditorGUI.FloatField(rect, "Output FP", f);
      QuantumEditorGUI.Overlay(rect, "(FP)");
      EditorGUILayout.LongField("Output Raw", _fp.RawValue);
    }
    catch (OverflowException e) {
      EditorGUILayout.LabelField("Out of range");
    }
  }
}

定数変数

`FP._1_10`構文を、拡張/生成することはできません。

FPは構造体なので、定数として使用することはできませんが、const変数にFPの値をハードコードして使用することは可能です。

  1. 定義済みのFP._1staticなゲッター)や、FP.Raw._1const変数)を組み合わせる

C#

FP foo = FP._1 + FP._0_10;
// または
foo.RawValue = FP.Raw._1 + FP.Raw._0_10;

C#

const long MagicNumber = FP.Raw._1 + FP.Raw._0_10;

FP foo = default;
foo.RawValue = MagicNumber;
// または
foo = FP.FromRaw(MagicNumber);
  1. 特定のfloatを一度FPに変換し、生の値を定数として保存する

C#

const long MagicNumber = 72089; // 1.1

var foo = FP.FromRaw(MagicNumber);
// または
foo.RawValue = MagicNumber;
  1. Quantum DSL内で定数を定義する

#define FPConst 1.1

これを以下のように使用できます。

C#

var foo = default(FP);
foo += Constants.FPConst;
// または
foo.RawValue += Constants.Raw.FPConst;

以下のような方法で、定数を表すコードを生成します。

C#

public static unsafe partial class Constants {
  public const Int32 PLAYER_COUNT = 8;
  /// <summary>1.100006</summary>
  
  public static FP FPConst {
    [MethodImpl(MethodImplOptions.AggressiveInlining)] get {
      FP result;
      result.RawValue = 72090;
      return result;
    }
  }
  public static unsafe partial class Raw {
    /// <summary>1.100006</summary>
    public const Int64 FPConst = 72090;
  }
}
  1. クラス内でreadonly static変数を定義する

    C#

    private readonly static FP MagicNumber = FP._1 +  FP._0_10;
    
    const変数に比べてパフォーマンスが落ちるため、readonlyを付けるのを忘れないでください。実行時に値を変更すると、同期ズレを起こす可能性があります。

キャスト

intuintshortushortbytesbyteからFPへの暗黙的キャストは許可されており、安全です。

C#

FP v = (FP)1;
FP v = 1;

FPからfloat/doubleへの明示的キャストは可能ですが、当然ながらシミュレーション中に使用すべきではありません。

C#

var v = (double)FP._1;
var v = (float)FP._1;

整数へのキャストは相互に安全です。

C#

FP v = (FP)(int)FP._1;

unsafeなキャストは[obsolate]になっていて、InvalidOperationExceptionが発生します。

C#

FP v = 1.1f;  // ERROR
FP v = 1.1d;  // ERROR

インライン化

すべての低水準なQuantumのシステムは、可能な限りパフォーマンスを引き出すため、手動でインライン化されたFP演算を使用します。固定小数点数学では、整数の除算と乗算を使用するため、計算結果や被除数は、計算前後にPRECISION(16)でビットシフトされます。

C#

var v = parameter * FP._0_01;

// インライン化された整数演算
FP v = default;
v.RawValue = (parameter.RawValue * FP._0_01.RawValue) >> FPLut.PRECISION;

C#

var v = parameter / FP._0_01;

// インライン化された整数演算
FP v = default;
v.RawValue = (parameter.RawValue << FPLut.PRECISION) / FP._0_01.RawValue;

オーバーフロー

FP.UseableMaxは、自身と乗算してオーバーフローを引き起こさない(long範囲を超えない)最大のFP値を表します。

FP.UseableMax
    Decimal: 32767.9999847412
    Raw: 2147483647
    Binary: 1111111111111111111111111111111 = 31 bit

FP.UseableMin
    Decimal: -32768
    Raw: -2147483648
    Binary: 10000000000000000000000000000000 = 32 bit

精度

数値が(0.01..1000)の範囲に収まるなら、一般的なFPの精度で十分です。固定小数点数学における精度問題は通常、不正確な結果を生み出し、(数学に基づく)システムを不安定にする傾向があります。非常に一般的なケースとして、非常に大きい/小さい数値を乗算した後に除算して元の範囲に戻すと、その数値の精度は失われます。

次の例はClosestDistanceToTriangleメソッドからの抜粋です。ここでt0は2つのドット積を乗算して計算され、そのドット積も乗算から求められているため、非常に正確な結果を期待する場合には問題が発生してしまいます。この問題を軽減する方法は、計算前に人為的に値をシフトし、その結果をシフトして戻すことです。これは、入力の範囲がある程度わかっている場合に有効です。

C#

var diff = p - v0;
var edge0 = v1 - v0;
var edge1 = v2 - v0;
var a00 = Dot(edge0, edge0);
var a01 = Dot(edge0, edge1);
var a11 = Dot(edge1, edge1);
var b0 = -Dot(diff, edge0);
var b1 = -Dot(diff, edge1);
var t0 = a01 * b1 - a11 * b0;
var t1 = a01 * b0 - a00 * b1;
// ...
closestPoint = v0 + t0 * edge0 + t1 * edge1;

FPAnimationCurves

Unityには、値を曲線形で表すための便利なツールが備わっています。これらの曲線はカスタムエディターが付属していて、その後シリアライズされ、実行時に特定の場所で曲線の値を評価するために使用できます。

曲線が使用される状況は多く、車両を実装する際のステアリング情報や、AIエージェントの意思決定の効用値(Bot SDKの効用理論で行われる)、攻撃ダメージの乗算値の取得など様々です。

Quantum SDKには、FPAnimationCurveと呼ばれる独自のアニメーション曲線の実装が既に付属していて、FPで評価されます。この型は、Unityのデータアセットやコンポーネントから直接編集できて、Unity標準のアニメーション曲線エディターで描画され、そのデータは内部的に決定論的な型にベイクされます。

FPAnimationCurveデータのポーリング

曲線からデータをポーリングするために必要なQuantumのコードは、Unityと非常に似ています。

C#

// 曲線の設定(キーポイント・曲線の分解能・接線モードなど)に応じて補間されたベイク済みの値を返す
FP myValue = myCurve.Evaluate(FP._0_50);

シミュレーションでFPAnimationCurvesを直接作成する

シミュレーションで決定論的なアニメーション曲線を一から直接作成するためのスニペットは以下の通りです。

C#

// 5つのキーポイントからなる単純な線形曲線を作成する
// 好みに応じてパラメーターを変更できる
public static class FPAnimationCurveUtils
{
    public static FPAnimationCurve CreateLinearCurve(FPAnimationCurve.WrapMode preWrapMode, FPAnimationCurve.WrapMode postWrapMode)
    {
        return new FPAnimationCurve
        {
            Samples = new FP[5] { FP._0, FP._0_25, FP._0_50, FP._0_75, FP._1 },
            PostWrapMode = (int)postWrapMode,
            PreWrapMode = (int)preWrapMode,
            StartTime = 0,
            EndTime = 1,
            Resolution = 32
        };
    }
}

C#

// ローカル変数に曲線を格納
var curve = FPAnimationCurveUtils.CreateLinearCurve(FPAnimationCurve.WrapMode.Clamp, FPAnimationCurve.WrapMode.Clamp);

// アセット内の初期化済み曲線を直接使用することもできる
public unsafe partial class CollectibleData
{
    public FPAnimationCurve myCurve = FPAnimationCurveUtils.CreateLinearCurve(FPAnimationCurve.WrapMode.Clamp, FPAnimationCurve.WrapMode.Clamp);
}

UnityのAnimationCurveをFPAnimationCurveにベイクする

Unity標準のAnimationCurveFPAnimationCurveに変換するために必要なコードスニペットは、こちら です。

Back to top