6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

この記事は、Business Bank Group Developers Advent Calendar 17日目の記事です。


こんにちは、ビジネスバンクグループの清水です。
フォームに入力した数値を使用して、計算処理をした時におきた問題について話します。

例は目標と実績の数値を入力させて、目標に達成しているかと達成率を表示する画面です。
(開発環境はAngular, TypeScriptです)
スクリーンショット 2018-12-17 0.37.20.png

計算処理
goal: number; // 目標
result1: number; // 実績1
result2: number; // 実績2

// 実績が目標に達成しているかどうかをチェックする
get achievement(): boolean {
  return this.goal <= (this.result1 + this.result2);
}

// 達成率
get rate(): number {
  return (this.result1 + this.result2) / this.goal * 100;
}

このようなフォームに大きな数値を入力すると、計算結果がおかしくなる ことがあります。↓達成率の計算が変
スクリーンショット 2018-12-17 9.42.42.png

これは、JavaScriptのNumber.MAX_SAFE_INTEGERによりおきている現象でした。

Number.MAX_SAFE_INTEGER

JavaScriptのNumberオブジェクトにはMAX_SAFE_INTEGERというプロパティがあります。
これは、JavaScriptで正確に扱える最大整数値を表します。

Number.MAX_SAFE_INTEGER // 9007199254740991

9007199254740991 になっているのは、Numberが倍精度浮動小数点型であるためだからだそうです。
正確に扱える最小整数値を表すMIN_SAFE_INTEGERもあります。

誤差が出てしまう

Number.MAX_SAFE_INTEGER Number.MIN_SAFE_INTEGERの範囲外の数値で計算処理をすると誤差が生じます。

9007199254740991 === 9007199254740992 // false
(9007199254740991 + 1) === (9007199254740991 + 2) // true
9007199254740991 + 1 // 9007199254740992
9007199254740992 + 1 // 9007199254740992

parseIntで数値変換しようとした時にも、誤差がでます。

parseInt("12345678901234567890", 10) // 12345678901234567000
parseInt("9007199254740992", 10) // 9007199254740992
parseInt("9007199254740993", 10) // 9007199254740992

これはJavaScriptの仕様です。
実際に、 Number.MAX_SAFE_INTEGER Number.MIN_SAFE_INTEGER のような大きな桁数を扱うケースはあまりありませんが
うっかり入力したら表示がおかしくなってしまったり、期待した処理をしなくなったりすることはなるべく防いでおきたいです。

まとめ

数値系の値を入力させるときは、Number.MAX_SAFE_INTEGER Number.MIN_SAFE_INTEGERを意識して下記のような制御をフロント側でしてあげると親切だなと思いました。
inputタグにmaxlengthを指定して入力自体を制限する
・値入力中またはフォームフォーカスアウト時にフロントでバリデーションチェックをし、正しくない値である可能性を知らせる
また、複数フォームのサマリーなどを別の計算処理に使用する場合などは上限・下限にゆとりを持たせるとより安全です。

おまけ

今回の数値フォームはAngularMaterialの MatInputModule を使用しました。

<p>
  <mat-form-field appearance="outline">
    <mat-label>目標</mat-label>
    <input matInput type="number" [(ngModel)]="goal">
  </mat-form-field>
</p>
<p>
  <mat-form-field appearance="outline">
    <mat-label>実績1</mat-label>
    <input matInput type="number" [(ngModel)]="result1">
  </mat-form-field>
</p>
<p>
  <mat-form-field appearance="outline">
    <mat-label>実績2</mat-label>
    <input matInput type="number" [(ngModel)]="result2">
  </mat-form-field>
</p>

<ng-container *ngIf="achievement; else notAchieved">
  <p class="rate">達成です!!(達成率:{{rate}}%)</p>
</ng-container>
<ng-template #notAchieved><p class="rate">未達成です..(達成率:{{rate}}%)</p></ng-template>

MatInputModuleをインポートすると、MatFormFieldModuleが使用できるようになります。
フォーカス/未入力/マウスオーバー時のスタイルはこんな感じです。便利でした!
スクリーンショット 2018-12-17 14.04.40.png

以上です。
明日は、@bigplantsさんの担当です。お願いします!

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?