インターシステムズ・アプリケーションでの数値の計算
このページでは、InterSystems IRIS® によってサポートされている数値形式について、詳しく説明します。
数値の表現
InterSystems IRIS で数値を表現する方法には 2 種類あります。
-
1 つは InterSystems IRIS オリジナルの実装当初からあったもので、10 進形式と呼ばれます。
クラス定義では、プロパティに小数点形式の数を含めるときに、%Library.DecimalOpens in a new tab データ型クラスを使用します。
-
もう 1 つは IEEE Binary Floating-Point Arithmetic 標準 (#754–1985Opens in a new tab) に準拠した形式で、近年サポートされるようになりました。この後者の形式は $DOUBLE 形式と呼ばれ、数値をこの形式に変換するために使用される ObjectScript 関数 ($DOUBLE) にちなんで名付けられたものです。
クラス定義では、プロパティに $DOUBLE 形式の数を含めるときに、%Library.DoubleOpens in a new tab データ型クラスを使用します。
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–1985Opens 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 値の計算結果の差異に留まります。
SQL 表現
InterSystems SQL データ型の DOUBLE と DOUBLE PRECISION は、IEEE 浮動小数点数 (つまり $DOUBLE) を表します。SQL FLOAT データ型は、標準の InterSystems IRIS の10 進数を表します。
数値形式の選択
使用する形式の選択は、計算の要件に大きく依存します。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 です。
数値表現の変換
インターシステムズでは、10 進形式と $DOUBLE 形式間の変換は、アプリケーションで明示的に制御することを推奨しています。
値を文字列から数値に変換する場合や、記述された定数をプログラムのコンパイル時に処理する際、最初の 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))
}
10 進数から $DOUBLE への変換
$DOUBLE 形式への変換は、$DOUBLE 関数を使用して明示的に実行されます。また、この関数は、$DOUBLE(<S>) 式を使用して、非数および無限大に対する IEEE 表現の明示的な構築を許可します。ここで <S> は、以下を意味します。
-
文字列 “nan” は非数を生成します。
-
文字列 “inf”、“+inf”、“-inf”、“infinity”、“+infinity”、または “-infinity” のいずれかは、無限大を生成します。
-
数値リテラルおよび文字列リテラルは、それぞれ -0 および “-0” を生成します。
文字列 <S> のケースは、入力時に無視されます。出力時には、“NAN”、“INF”、および “-INF” のみが生成されます。
$DOUBLE から 10 進数への変換
$DOUBLE 形式の値は、$DECIMAL 関数で 10 進数値に変換されます。この関数の呼び出しの結果、10 進数値への変換に適した文字列が生成されます。
この説明では、$DECIMAL に指定される値は $DOUBLE 値であることを前提としていますが、必ずしもそうである必要はありません。任意の数値を引数として指定でき、丸めにも同じルールが適用されます。
$DECIMAL(x)
引数を 1 つ持つ形式のこの関数は、引数として指定された $DOUBLE 値を 10 進数に変換します。$DECIMAL によって数値の小数部は 19 桁に丸められます。$DECIMAL では常に最も近い 10 進数値に丸められます。
$DECIMAL(x, n)
引数を 2 つ持つ形式では、返される桁数を正確に制御できます。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 です。
値の正確な表現
各数値形式には、保持できる有効桁数の限度があります。普通は、この限度よりも桁数が多ければ、自動的に精度が落ちると考えがちです。しかし、一般的にはこれは正しくありません。
この影響は、InterSystems IRIS の 10 進数値に最もよく見られます。単純に、値 9,223,372,036,854,775,807 が正確に表現できる最大の数値であると考えがちですが、その考えでは指数が無視されています。9,223,372,036,854,775,807,000,000 は、百万倍大きいですがこれも正確に表現できることは明らかです。値が許可された範囲内にある、指数が 0 の他のすべての数値について、同じことが当てはまります。しかし、これは、10 進数の全範囲内で取り得るすべての数値ではありません。
この表現では、10 の累乗の代わりに 2 の累乗を数値に掛けること以外、状況は似ています。以下の特徴がある非常に単純なモデルを考えれば、この状況を図で示すことができます。
-
常に正の数を扱うので、符号ビットは必要がない。
-
3 ビットの非正規化された仮数 (値の範囲は 0 ~ 7) を使用する。
-
バイアスがない符号付の 3 ビットの指数 (値の範囲は -4 ~ +3) を使用する。
以下の表は、表現可能な値を示します。
指数 | -4 | -3 | -2 | -1 | 0 | 1 | 2 | 3 |
---|---|---|---|---|---|---|---|---|
仮数 | ||||||||
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0.0625 | 0.125 | 0.25 | 0.5 | 1 | 2 | 4 | 8 |
2 | 0.125 | 0.25 | 0.5 | 1 | 2 | 4 | 8 | 16 |
3 | 0.1875 | 0.375 | 0.75 | 1.5 | 3 | 6 | 12 | 24 |
4 | 0.25 | 0.5 | 1 | 2 | 4 | 8 | 16 | 32 |
5 | 0.3125 | 0.625 | 1.25 | 2.5 | 5 | 10 | 20 | 40 |
6 | 0.375 | 0.75 | 1.5 | 3 | 6 | 12 | 24 | 48 |
7 | 0.4375 | 0.875 | 1.75 | 3.5 | 7 | 14 | 28 | 56 |
以下の点に注意してください。
-
このモデルでは、仮数には 3 ビットしか使用しないので、1 桁の 10 進数には十分な精度がありません。9 という数値がありません。これを表現するには、仮数として 1001 が必要となり、これは明らかに長すぎるからです。
-
これらのバイナリ数値をすべて完全に正確な 10 進数文字列と正確に対照させるには、表現で有効な 4 桁の 10 進数を使用する必要があります。
-
0 ~ 56 の範囲の数値には表現できないものがあります。一例として 2.75 という値が挙げられます。 段階的に仮数を整数に変換すると、結果として 5.5、次に 11 という値が得られる一方で、これに対応して、指数が -1、次に -2 となります。しかし 11 は 3 ビットでは表現できない値なので、上記の表内にはありません。
以下の図では、この形式で表現できる値が、数直線上においてどのように分布しているかを示しています。図示するために、刻み幅を、最小の表現可能な値である 0.0625 (つまり 1/16) としています。数直線は、各直線が 4 単位分の長さ (つまり 64 刻み) となるように、折り返されています。X は、この形式で表現できる数値を示しています。配置可能な 56*16=896 個の位置のうち、正確に表現できるのは 64 個 (平均では 14 個中 1 個) のみです。
+0 +1 +2 +3 | | | | 0 XXXXXXXXX.X.X.X.X...X...X...X...X.......X.......X.......X....... 4 X...............X...............X...............X............... 8 X..............._...............X..............._............... 12 X..............._...............X..............._............... 16 X..............._..............._..............._............... 20 X..............._..............._..............._............... 24 X..............._..............._..............._............... 28 X..............._..............._..............._............... 32 X..............._..............._..............._............... 36 _..............._..............._..............._............... 40 X..............._..............._..............._............... 44 _..............._..............._..............._............... 48 X..............._..............._..............._............... 52 _..............._..............._..............._............... 56 X..............._..............._..............._...............
同様な状況が、InterSystems IRIS の 10 進数値にも当てはまります。正確に表現できる (指数が 0 の) 最大の整数は、9,223,372,036,854,775,807 です。説明の都合上、この値を MAX と呼びます。この結果、MAX*10 および (MAX-3)*100 も正確に表現できることになります。これらの指数は 0 ではありませんが、仮数は正確です。ただし、MAX+1 などのように正確に表現できない値もあります。
$DOUBLE 値でも、指数が InterSystems IRIS の場合のように 10 ではなく 2 の累乗である場合を除き、この現象が発生します。
関連項目
詳細は、以下のソースを参照してください。
-
IEEE-754–1985Opens in a new tab 標準。この標準の正式名は、“IEEE Standard for Binary Floating-Point Arithmetic” です。米国では、この仕様は ANSI/IEEE Std 754-1985 と表記されます。
これは国際標準でもあります。国際的には、IEC 60559:1989 “Binary floating-point arithmetic for microprocessor systems” と呼ばれています。
-
Computing Surveys の David Goldberg 氏著による “What Every Computer Scientist Should Know About Floating-Point ArithmeticOpens in a new tab” (1991 年 3 月発行)。