ServiceNow – 少数を表すデータ型の解説(Decimal, Floating Point Number)

検討・企画

ServiceNowにおいて、少数を表現するデータ型であるDecimal型とFloating Point Number型の仕様、それらの使い分けについて解説します。

Decimal, Floating Poit Number, 他のデータ型の使い分け(結論)

まずは、この記事の結論である使い分けを初めに示しておきます。後述すように、少数を表すデータ型は仕様がトリッキーで、きれいには分離できないため、独断を含んでいます。なお、ServiceNowでは、一般的に倍精度浮動小数点数を表すDouble型は存在しません。

やりたいこと適切なデータ型解説
大きな桁数の整数を保持したいlong,
Decimal
Decimalを用いる場合約16桁までの値しか正確に保持できないが、long型であれば約19桁まで保持できる上に挙動がシンプルなので、longを推奨します。ただし、入力できる桁数を制限したい場合は、簡単な設定で桁数を制御できるDecimalが良いと思います。
*なお、”他システムから取得した値をスクリプト経由で計算処理無しで記録しておく”といった特殊ケースで、かつlongでは対応できない大きな桁数の値を保持したい場合は、Decimalを採用してもよいかもしれません。(詳細は後述)
桁数の比較的少ない少数を保持したいFloating Point Number,
Decimal
パーセント(e.g. 15.5%)や評価(e.g. 3.5点)、工数(e.g. 1.25人月)などの、口述のFloating Point Number型で表現できる範囲の桁数に収まることが確実な数であれば、Decimalよりも挙動が素直なFloatを推奨します。ただし、入力できる桁数を制限したい場合は、簡単な設定で桁数を制御できるDecimalが良いと思います。
桁数の大きい少数を保持したいDecimal金額(e.g. 123,456,789,012.5ドル)や精密な量(0.00005523kg)などで、Floating Point Numberで表現できる範囲を超える可能性のある少数を格納する場合は、Decimalしか対応できません。
Decimal, Floating Point Number, longの使い分け

Decimal型項目の解説

基本

少数を保持する項目(フィールド)を作成する場合のデータ型の選択肢の一つが、Decimal型です。

Decimal型フィールドでは、整数部と小数部を合わせた最大桁数をMax length項目で、小数部の最大桁数をScale属性(Attribute項目で指定する項目の設定)で指定できます。Max lengthを15、Scaleを7で指定した場合、整数部最大8桁、小数部最大7桁の少数を格納することができます。

Decimal型の不思議な挙動とその理由、対応方法

Decimalは数々のトリッキーな挙動を示すデータ型であり、以下に具体的な挙動とその理由、存在するものは対応方法を示します。

なお、これらは贔屓目に見ても謎な仕様ばかりなので、今後修正が入る可能性もあります。本記事は2023年Vancouverバージョンの挙動をもとに調査しているため、後発のバージョンでは挙動が変わっている可能性があります。

謎挙動をまとめると、次の通りです。

  • Max Lengthの最大値は38
  • ScaleはMax Length以下でなければならず、最大値は38(Max length = Scale = 38とした場合、0.<38桁>の形式の少数のみ保持できる)
  • 標準ではHTML/CSS上でDecimal型フィールドに対応するformタグにmaxlength=14が指定されているため、小数点やカンマを含めて14桁までしか入力できないようになっているフォーム上で14桁以上入力させる必要がある場合はonloadのClient Scriptでこの制御を外さなければならない
  • 標準ではDecimal型項目に対してフォーム上で入力を行うと、整数部のみ自動フォーマットが行われ、3桁ごとのカンマや、21桁を超えた場合の指数表記への変換が行われる。この自動フォーマットにおいて、数値はIEEE 754 倍精度浮動小数点数で処理されているため、9,007,199,254,740,991(10進数、16桁)を超えた数値を入れると、丸め誤差が発生する可能性がある。つまり、フォームからの入力を前提とする場合、正確な値を保持できる実質の最大桁数は15桁

以下、一つずつ詳細を説明していきます。

謎仕様① Max Lengthを14以下にしようとすると強制的に15になる

Max lengthを14以下に変更しようとすると、”Set Length for Decimal”というBusiness Ruleによって強制的に15に変更されるようです。

基本的に桁数が大きい分には問題にならないと思いますが、例えば10桁以内の整数を格納する項目を作りたい場合などは、なぜこのような制御がかかっているかはわかりませんが、この制御が内部的なデータベースの扱いなどに関連している可能性もあるため、14桁以下に制御する必要がある場合は、標準のBusiness Ruleを無効化/変更せずUI PolicyやClient Script、Business Ruleを用いて制御するほうが良いでしょう。

謎仕様② Max Lengthを38より大きくしても内部的には38桁までしか保存できない

Decimal項目のMax lengthの値を38より大きくしようとすると、画面上は可能なのですが、内部的には38が限界なようです。ServiceNowの内部で用いられているMySQL(MariaDB)の仕様では、最大桁数は65桁ですので、この制約はServiceNowのアプリケーションレイヤでかけられているものと思われます。

ちなみに、Dictionary(項目定義)上では38桁より大きな値を入力しても、フォーム上でフィールドの情報を確認すると、38桁と表示されます。

謎仕様③ Scaleを変更しても小数点以下桁数が変更されない

Decimal型フィールドにおいてScale attributeだけを変更してもDBには反映されず、Max lengthを変更した際にMax lengthとScaleがまとめて反映されるそうです。そのため、Scaleだけを変更したい場合であっても、Scale変更後に一度Max lengthを変更して、元に戻す必要があります。

参考: KB0696658 Number of decimal places always rounds to 2 even when ‘scale’ is attribute is set

謎仕様④ Decimal型のフィールドに14文字までしか入力できない

Max lengthの設定とは無関係に、フォーム上でDecimal型フィールドに値を入力しようとすると、小数点を含め最大14文字までしか入力することができません。これは、HTML/CSS側で最大文字数が14文字に制御されているためです。

なぜこのような制御がかかっているかはわかりませんが、このままだと小数点を含めて14文字以内の値しか入力できなくなってしまうので、符号や小数点を含め15文字以上を入力できる必要がある場合はonloadのClient Scriptで次のように制約を外す必要があります。なお、ここで設定するmaxlengthは、テキストボックスへ入力できる最大文字数ですので、符号や整数の区切りのカンマ、小数点を考慮しておく必要があります。厳密に桁数を制御したい場合は、別途UI PolicyやClient Script、Business Ruleで制御すべきです。

function onLoad() {
	// Max length=15, Scale=7を前提として、小数点及び整数部のカンマを考慮し18文字に上書き
    g_form.getElement('u_decimal_field').setAttribute('maxlength', '18');
}

なお、MySQLの仕様だと、小数部の桁数の最大値は30のようですが、ServiceNowとしては、Max Length = Scale = 38、つまり小数部が38桁の少数も格納することができるようでした。

謎仕様⑤ フォーム上で数値を入力すると整数部に丸め誤差が発生する/整数部が22桁以上になる値を入力すると指数表記(例:”1e,+21″)になり保存できない

前提として、フォーム上でDecimal型フィールドに数値を入力すると、整数部に3桁ごとのカンマが入ります。これは、HTML/CSS側で値変更時に動作するformatNumber()関数によるものだと思われます。

整数部に16桁以上入力できるようにした上で、例えば”9007199254740993″と入力してみると、”9,007,199,254,740,992″に勝手に変更され、丸め誤差が発生します。

実機の挙動を見る限り、これは、上述のformatNumber()関数において、整数部の数値をIEEE 754 倍精度浮動小数点数で扱っているためだと思われます

詳細は省きますが、IEEE 754 倍精度浮動小数点数では仮数部が53bitで表現できる9,007,199,254,740,991(10進数、16桁) = 1 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111(2進数、53桁)までの値を正確に保持できます。この値を超えると、仮数部が53bitを超える可能性があり、その場合に丸め誤差が発生します。例えば、9,007,199,254,740,992(10進数、16桁) = 10 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000(2進数、54桁) = 1 * 2^54は仮数部は1桁になるため丸め誤差は発生しまあせんが、9,007,199,254,740,993(10進数、16桁) = 10 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001(2進数、54桁)は仮数部が54桁となるため、53桁に丸められ9,007,199,254,740,992(10進数、16桁)に変更されます。

仮に丸め誤差を許容したとしても、フォーム上で整数部が22桁以上になる値を入力すると指数表記(例:”1e,+21″)になり、指数表記の状態だとエラーが出て値を保存することができません

こちらもformatNumber()の仕様によるものだと思われます

formatNumber()の処理を防ぐのは難しいと思われますが、onChangeのClientScriptで丸め誤差や指数表記をもとに戻すことは理論的には可能です。しかし、その処理を行うコードの中でも数値はIEEE 754 倍精度浮動小数点数で扱われてしまうため、実現するためには複雑な処理を実装しなければなりません。そのため、桁数の大きな整数を保持する場合、9,223,372,036,854,775,807(19桁)までで問題なければlong型を利用するのが良いです。

なお、フォーム経由ではなくスクリプト経由で値を入れる場合は、formatNumber()が動作しないためか、これらの挙動は確認されませんでした。とはいえ、やはりこの場合も、スクリプトの中では基本的に数値は数値はIEEE 754 倍精度浮動小数点数で扱われてしまうため、気を付けないと結局丸められてしまいます。

Floating Point Number型項目の解説

基本

少数を保持する項目(フィールド)を作成する場合のデータ型のもう一つの選択肢が、Floating Point Number型です。

Floating Point Number型は、Decimal型ほどではないですが、やはり少しトリッキーな挙動を示しますので、以下にそれらを示します。こちらも2023年Vancouverバージョンの挙動をもとに調査しているため、後発のバージョンでは挙動が変わっている可能性があります。

謎仕様① Floating Point Number型のフィールドに14文字までしか入力できない

Decimal型と同様にフォーム上でFloating Point Number型フィールドに値を入力しようとすると、小数点を含め最大14文字までしか入力することができません。これは、HTML/CSS側で最大文字数が14文字に制御されているためです。対応については前述した通りです。

謎仕様② 単精度浮動小数点数ではない

Floating Point Numberというと、一般的なプログラミング言語でいうFloat(float)型、つまりIEEE 754 単精度浮動小数点数を表すように見えますが、ServiceNowのFloating Point Number型は、単精度浮動小数点数よりももう少し表現力が高いようです。

内部のデータ構造はよくわかりませんでしたが、実機の挙動としては、少なくとも次のような値を表現することができました。

整数部小数部備考
11桁0桁整数部のみであれば11桁までの数値を格納できるようです。12桁以上の数値を入力したところ、100,000,000,000(11桁)に変更されてしまいました。
11桁5桁整数部11桁、小数部5桁もほとんどの数値を格納できました。ただし、99,999,999,999.99999としたところ、99,999,999,999.99998と変更されてしまったり、10,000,000,000.99999だと末尾の9はそのままだったりと、少し変な挙動は示します。規則性はわかりませんでした。
0桁7桁小数部ので可能な限り大きな桁数を格納しようとすると、7桁まで格納できました。
9桁7桁小数部を7桁格納できる、かつ整数部にできるだけ大きな桁数を格納しようとすると、9桁まで格納できました。
ServiceNowのFloating Point Number型で表現できる値

以上です。

コメント

タイトルとURLをコピーしました