インターシステムズ・アプリケーションでの数値の計算
ここでは、InterSystems IRIS® データ・プラットフォームによってサポートされている数値形式について詳しく説明します。
概要
InterSystems IRIS で数値を表現する方法には 2 種類あります。
-
1 つは InterSystems IRIS オリジナルの実装当初からあったもので、10 進形式と呼ばれます。
クラス定義では、プロパティに小数点形式の数値を含めるときに、%Library.DecimalOpens in a new tab データ型クラスを使用します。
-
もう 1 つは IEEE Binary Floating-Point Arithmetic 標準 (#754–2019Opens in a new tab) に準拠した形式で、近年サポートされるようになりました。この後者の形式は $DOUBLE 形式と呼ばれ、値をこの形式で作成するために使用される ObjectScript 関数 ($DOUBLE) にちなんで名付けられたものです。
クラス定義では、プロパティに $DOUBLE 形式の数値を含めるときに、%Library.DoubleOpens in a new tab データ型クラスを使用します。
SQL 表現
InterSystems SQL データ型の DOUBLE と DOUBLE PRECISION は、IEEE 浮動小数点数 (つまり $DOUBLE) を表します。SQL FLOAT データ型は、標準の InterSystems IRIS の10 進数を表します。
10 進形式
InterSystems IRIS では、10 進数は内部的に 2 つの部分で表されます。最初の部分は仮数、2 番目の部分は指数と呼ばれます。
-
仮数は、対象となる値の有効桁数で構成されます。符号付 64 ビット整数として保存され、その値の右側には小数点があると見なされます。精度を失うことなく表現できる指数 0 の最大の正の整数は 9,223,372,036,854,775,807、最大の負の整数は -9,223,372,036,854,775,808 です。
-
指数は、符号付バイトとして内部的に保存されます。値の範囲は 127 ~ -128 です。
これは、値の 10 進数の指数です。つまり、その数の値は、仮数に 10 の指数のべき乗を乗算したものになります。
例えば、ObjectScript リテラル値 1.23 の場合、仮数は 123 であり、指数は -2 です。
したがって、InterSystems IRIS がネイティブ形式で表現できる数値の範囲は、約 1.0E-128 ~ 9.22E145 になります。(最初の値は最小の指数を持つ最小の整数になります。2 番目の値は最も大きい整数で、小数点が左に移動して、それに従って指数が増加する形式で表されます。)
小数桁数が 18 の数値はすべて正確に表現できます。また、仮数が表現範囲内にある数値は、19 桁の値として正確に表現できます。
InterSystems IRIS では、数値を 10 進形式にする必要がない限り、仮数を正規化することはしません。したがって、仮数が 123 で指数が 1 の数と、仮数が 1230 で指数が 0 の数は同じものを表します。
$DOUBLE 形式
インターシステムズの $DOUBLE 形式は、IEEE-754–2019Opens in a new tab、具体的には 64 ビットのバイナリ (倍精度) 表現に準拠します。つまり、以下の 3 つの部分で構成されます。
-
符号ビット
-
2 つの指数 の 11 ビット乗。指数値には 1023 のバイアス分が含まれるので、$DOUBLE(1.0) の指数の内部値は 0 ではなく 1023 になります。
-
正の 52 ビット小数の仮数部仮数部は常に正の数値として処理され正規化されるため、仮数部として表されていなくても、1 ビットは先頭のバイナリ桁であると見なされます。したがって、仮数部は数値としては 53 ビット長になり、値 1 の後に暗黙の 2 進小数点、その後に小数の仮数部が続きます。これは、暗黙的に 2**52 で除算された整数として考えることができます。
整数として、0 ~ 9,007,199,254,740,992 のすべての値は正しく表現できます。大きな整数を正しく表現できるかどうかは、そのビット・パターンによります。
このデータ表現には、InterSystems IRIS のネイティブ形式にはない以下の 3 つのオプション機能があります。
-
(負の数の平方根を取るなど) 不正な計算結果を NaN (非数) として表現する機能。
-
+0 および -0 の両方を表現する機能。
-
無限大を表現する機能。
-
この標準は、2 ** -1022 より小さい数の表現を提供します。これは、緩やかな精度の損失と呼ばれる手法で行われます。詳細は、標準Opens in a new tabを参照してください。
これらの機能は、個別プロセスの場合は %SYSTEM.ProcessOpens in a new tab クラスの IEEEError()Opens in a new tab メソッド、システム全般の場合は Config.MiscellaneousOpens in a new tab クラスの IEEEError()Opens in a new tab メソッドを介してプログラム制御されます。
IEEE バイナリ浮動小数点表現を使用して計算すると、同じ IEEE 演算でも異なる結果が得られる場合があります。インターシステムズでは、以下に対して独自の実装を記述しています。
-
$DOUBLE バイナリ浮動小数点と 10 進数との間の変換。
-
$DOUBLE と数値文字列との間の変換。
-
$DOUBLE とその他の数値型との間の比較。
これにより、$DOUBLE 値を InterSystems IRIS データベースに挿入したり、InterSystems IRIS データベースからフェッチしたりするときに、すべてのハードウェア・プラットフォームで結果が同じになることが保証されます。
ただし、$DOUBLE 型を含むその他すべての計算では、InterSystems IRIS はベンダが提供する浮動小数点ライブラリのサブルーチンを使用します。そのため、同じ演算のセットでも、プラットフォームによってわずかな差異が生じる可能性があります。ただし、すべてのケースにおいて、インターシステムズの $DOUBLE の計算は、C の double 型で実行するローカルの計算と等しくなります。つまり、インターシステムズの $DOUBLE の計算に対するプラットフォーム間の差異は、最大でも、それぞれ同じプラットフォーム上で実行される C プログラムの IEEE 値の計算結果の差異に留まります。
数値形式の選択
使用する形式の選択は、計算の要件に大きく依存します。InterSystems IRIS の 10 進形式では 18 桁を超える精度が許可されますが、$DOUBLE では 15桁しか保証されません。
多くの場合、10 進形式は使用法がより簡単で、結果も正確です。予想どおりの結果が返されるため、通常、(通貨計算などの) 10 進値の計算に使用されます。ほとんどの 10 進数の小数は、バイナリの小数ほど正確には表現できません。
それに対して、$DOUBLE 形式の数値の範囲は、ネイティブ形式で許可される 1.0E145 よりもはるかに大きい 1.0E308 になります。数値の範囲が重要となるアプリケーションでは、$DOUBLE を使用する必要があります。
データを外部で共有するアプリケーションでは、暗黙の変換対象とならないよう、データを $DOUBLE 形式で維持することも検討できます。他のほとんどのシステムでは、基礎となるハードウェア・アーキテクチャによって直接サポートされることから、バイナリ浮動小数点の表現に IEEE 標準を使用しています。したがって 10 進形式の値は、(例えば ODBC/JDBC、SQL、または言語バインディング・インタフェースを使用して) 情報交換前に変換する必要があります。
InterSystems IRIS の 10 進数に定義された範囲内に $DOUBLE 値がある場合、10 進数に変換して再度 $DOUBLE 値に戻すと、常に同じ数値が生成されます。$DOUBLE 値の精度は 10 進値よりも低いため、逆の場合はこのようにはなりません。
その理由から、可能であれば、いずれか 1 つのデータ表現のみを使用して計算を実行することをお勧めします。データ表現間で変換を繰り返すと、精度が落ちる場合があります。多くのアプリケーションでは、すべての計算に InterSystems IRIS の 10 進形式を使用できます。$DOUBLE 形式は、IEEE 形式を使用するシステムとデータを交換するアプリケーションのサポートを目的としています。
$DOUBLE ではなく、InterSystems IRIS の 10 進形式をお勧めする理由は、以下のとおりです。
-
InterSystems IRIS の 10 進形式は精度が高く、$DOUBLE の桁数が 16 未満であるのに対し、ほぼ 19 桁の精度を持っています。
-
InterSystems IRIS の 10 進形式では、正確に 10 進数の小数を表現できます。例えば、0.1 という値は InterSystems IRIS の 10 進形式では正確に 0.1 になりますが、バイナリの浮動小数点では同値となる値がないので、$DOUBLE 形式では語義的な近似値で 0.1 を表現する必要があります。
科学的数値では、以下の点でインターシステムズの 10 進形式よりもインターシステムズの $DOUBLE の方が有利です。
-
$DOUBLE は、ほとんどの計算ハードウェアで使用されている IEEE 倍精度浮動小数点とまったく同じデータ表現を使用します。
-
$DOUBLE の方が範囲が広く、$DOUBLE による最大値は 1.7E308 であるのに対し、インターシステムズの 10 進形式による最大値は 9.2E145 です。
変換 : 文字列
値を文字列から数値に変換する場合や、記述された定数をプログラムのコンパイル時に処理する際、最初の 38 桁の有効桁数のみが仮数の値に影響します。それ以降のすべての桁はゼロとして処理されます。つまり、指数値の決定に使用され、仮数値には影響しません。
数値としての文字列
InterSystems IRIS では、式に文字列が使用されている場合、その文字列の値は、その最初の文字で開始される文字列内の最も長い数値リテラルになります。そのようなリテラルが存在しない場合、文字列の計算値は 0 になります。
添え字としての数値文字列
計算では、“04” と “4” の文字列間に違いはありません。しかし、このような文字列がローカル配列またはグローバル配列の添え字として使用される場合、InterSystems IRIS ではこれらの文字列が区別されます。
InterSystems IRIS では、先頭 (存在する場合はマイナス記号の後) や 10 進小数の末尾に 0 を含む数値文字列は、添え字として使用する場合、文字列であるかのように処理されます。これらは、文字列として、数値を含んでいるため、計算に使用できます。しかし、ローカル変数またはグローバル変数の添え字としては、文字列として扱われ、文字列として照合されます。以下に、例をいくつか示します。
-
4 と 04
-
10 と 10.0
-
.001 と 0.001
-
-.3 と -0.3
-
1 と +01
左側は添え字として使用した場合、数値と見なされ、右側は文字列として扱われます。(無関係な先頭と末尾の 0 の部分を除く左側の形式は、キャノニック形式とも呼ばれます。)
通常の照合では、以下の例のように、数値は文字列の前にソートされます。
SET ^||TEST("2") = "standard"
SET ^||TEST("01") = "not standard"
SET NF = "Not Found"
WRITE """2""", ": ", $GET(^||TEST("2"),NF), !
WRITE 2, ": ", $GET(^||TEST(2),NF), !
WRITE """01""", ": ", $GET(^||TEST("01"),NF), !
WRITE 1, ": ", $GET(^||TEST(1),NF), !, !
SET SUBS=$ORDER(^||TEST(""))
WRITE "Subscript Order:", !
WHILE (SUBS '= "") {
WRITE SUBS, !
SET SUBS=$ORDER(^||TEST(SUBS))
}
変換 : $DOUBLE へ
インターシステムズでは、10 進形式と $DOUBLE 形式間の変換は、アプリケーションで明示的に制御することを推奨しています。
任意の数値を $DOUBLE 形式に変換するには、$DOUBLE 関数を使用します。また、この関数は、$DOUBLE(<S>) 式を使用して、非数および無限大に対する IEEE 表現の明示的な構築を許可します。ここで <S> には、以下のいずれかを指定できます (大文字小文字は区別されません)。
-
非数を生成する文字列 "nan"
-
無限大を生成する文字列 inf、+inf、-inf、infinity、+infinity、または -infinity のいずれか
-
リテラル -0 または "-0" (同等)
変換 : 10 進数へ
インターシステムズでは、10 進形式と $DOUBLE 形式間の変換は、アプリケーションで明示的に制御することを推奨しています。
数値 (あらゆる種類) を 10 進形式に変換するには、$DECIMAL 関数を使用します。
$DECIMAL(x)
引数を 1 つ使用した形式の $DECIMAL 関数では、指定した引数が 10 進形式に変換され、数値の小数部分が 19 桁に丸められます。$DECIMAL では常に最も近い 10 進数値に丸められます。
$DECIMAL(x, n)
引数を 2 つ使用した形式の $DECIMAL では、返される桁数を正確に制御できます。n が 38 より大きい場合、<ILLEGAL VALUE> エラーが生成されます。n が 0 より大きい場合、n 桁の有効桁数に丸められた x の値が返されます。
n が 0 の場合、値の決定には、以下のルールが使用されます。
-
x が無限大の場合、必要に応じて INF または -INF を返します。
-
x が非数の場合、NAN を返します。
-
x が正または負の 0 の場合、0 を返します。
-
x を 20 桁以下の有効桁数で正確に表現できる場合、その有効桁数のキャノニック形式の数値文字列を返します。
-
それ以外の場合は、10 進表現を 20 桁の有効桁数に切り捨てます。さらに
-
20 番目の桁が 0 の場合、1 に置き換えます。
-
20 番目の桁が 5 の場合、6 に置き換えます。
その後、結果の文字列を返します。
-
20 桁目が不正確に 0 または 5 になる場合を除き、20 桁目を 0 に切り捨てるこの丸めのルールには、以下の特性があります。
-
ある $DOUBLE 値が、ある 10 進数値と異なる場合、これらの 2 つの値は常に、等しくない表現文字列を持ちます。
-
<MAXNUMBER> エラーを生成せずに $DOUBLE 値を 10 進数に変換できる場合、その結果は $DOUBLE 値を文字列に変換してから、その文字列を 10 進数値に変換する場合と同じです。 2 つの変換を行う場合、double round エラーは発生しません。
変換 : 10 進数から文字列へ
10 進数値は、例えば連結演算子のオペランドの 1 つとして使用した場合、既定で文字列に変換できます。変換を詳細に制御する必要がある場合には、$FNUMBER 関数を使用します。
算術演算
同種の表現
10 進数値のみを含む式では、常に 10 進数の結果が生成されます。同様に、$DOUBLE 値のみを含む式では、常に $DOUBLE の結果が生成されます。さらに、
-
10 進数値を含む計算結果がオーバーフローする場合、<MAXNUMBER> エラーとなります。リテラルの場合のように、$DOUBLE への自動変換は行われません。
-
10 進数式がアンダーフローする場合、式の結果として 0 が生成されます。
-
既定では、オーバーフロー、0 による除算、および無効な演算の IEEE エラーは、無限大や非数の結果を生成するのではなく、それぞれ <MAXNUMBER>、<DIVIDE>、および <ILLEGAL VALUE> エラーとして示されます。この動作は、個別プロセスの場合は %SYSTEM.ProcessOpens in a new tab クラスの IEEEError()Opens in a new tab メソッド、システム全般の場合は Config.MiscellaneousOpens in a new tab クラスの IEEEError()Opens in a new tab メソッドで変更できます。
-
0 ** 0 (10 進数) という式では 10 進数値の 0 が生成されますが、$DOUBLE(0) ** $DOUBLE(0) という式では $DOUBLE 値の 1 が生成されます。前者は InterSystems IRIS では常に当てはまっています。後者は IEEE 標準の要件です。
異種の表現
10 進数表現と $DOUBLE 表現の両方を含む式では、常に $DOUBLE 値が生成されます。値の変換は、使用時に実行されます。したがって、次のような式の場合、
1 + 2 * $DOUBLE(4.0)
InterSystems IRIS は最初に、10 進数値として 1 と 2 を加算します。その後、結果の 3 を $DOUBLE 形式に変換し、乗算を実行します。結果は $DOUBLE(12) です。
丸め
必要に応じて、数値は最も近い表現可能な値に丸められます。丸められる値が、2 つの使用可能な値と同等に近い場合、以下が実行されます。
-
$DOUBLE 値は、IEEE 標準で定義されているとおりに、偶数に丸められます。
-
10 進数値は、0 から離れた値、つまり (絶対値で) 大きい方の値に丸められます。
比較演算
同種の表現
$DOUBLE(+0) と $DOUBLE(-0) の比較では、これらの値は同等に扱われます。 これは IEEE 標準に準拠しています。 $DOUBLE(+0) または $DOUBLE(-0) が文字列に変換される場合、両方とも結果が “0” になるので、これは InterSystems IRIS の 10 進数の場合と同じです。
$DOUBLE(nan) と他の数値 ($DOUBLE(nan) を含む) の比較では、これらの値は、より大きくない、等しくない、より小さくない、になります。 これは IEEE 標準に準拠しています。 これは、文字列に変換してからそれらの文字列が等しいかどうかを確認することにより等値比較を行うという、通常の ObjectScript のルールから逸脱しています。
式 nan は、$DOUBLE(nan) と同等です。これは、比較が文字列の比較として行われるためです。
異種の表現
10 進数値と $DOUBLE 値の比較は、完全に正確です。 比較は、いずれの値も丸められることなく実行されます。 有限値のみが含まれる場合、これらの比較の答えは、両方の値を文字列に変換し、これらの文字列を既定の照合ルールに基づいて比較した場合に得られる結果と同じになります。
演算子 <、<=、>、および => を含む比較では常に、ブーリアン値、0 または 1 を 10 進数値として生成します。いずれかのオペランドが文字列である場合、そのオペランドは、比較を実行する前に 10 進数値に変換されます。 他の数値オペランドは変換されません。 前述のとおり、異なる数値型の比較は、完全に正確に実行され、変換は行われません。
文字列の比較演算子 (=、'=、]、']、[,、'[、]]、']] など) では、すべての数値オペランドは、比較を行う前にまず文字列に変換されます。
以下、以上
InterSystems IRIS では、演算子 <= および >= はそれぞれ、演算子 '> および '< と同様に扱われます。
オペランドのいずれかまたは両方が非数である可能性がある比較で、演算子 <= または >= が使用される場合、IEEE 標準の要求とは異なる結果になります。
A と B のいずれか (または両方) が非数の場合、A >= B という式は、以下のように解釈されます。
-
式は A '> B に変換されます。
-
さらに '(A > B) に変換されます。
-
前述のように、非数を含む比較では、(a) 等しくない、(b) より大きくない、(c) より小さくない、という結果が得られます。したがって、括弧の中の式の結果は、False の値になります。
-
その値を反転させると、True の値が得られます。
A >= B という式は、これを ((A > B) | (A = B)) と表現すれば、書き換えて IEEE で要求される結果を得ることができます。
ブーリアン演算
ブーリアン演算 (and、or、not、nor、nand など) では、すべての文字列オペランドが 10 進数に変換されます。数値オペランド (10 進数または $DOUBLE) は変更されません。
数値 0 は FALSE として扱われ、その他のすべての数値 ($DOUBLE(nan を含む) および $DOUBLE(inf)) は TRUE として扱われます。 結果は 0 または 1 (10 進数) です。
$DOUBLE(-0) も False です。
関連項目
-
$DECIMAL 関数
-
$DOUBLE 関数
-
$FNUMBER 関数
-
Computing Surveys の David Goldberg 氏著による What Every Computer Scientist Should Know About Floating-Point ArithmeticOpens in a new tab (1991 年 3 月発行)