JavaScript の Number 型の toString
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ toFixed でも 22 桁以上は指数表記になって小数点以下はでない
◆ 22 桁の場合は中では toString が実行されてる
◆ 有効桁数とか丸めとかがよくわからない (関数の実行内容で触れてないよう)
◆ 22 桁の場合は中では toString が実行されてる
◆ 有効桁数とか丸めとかがよくわからない (関数の実行内容で触れてないよう)
toFixed してもある程度を超えると指数表記になるなぁと思ってそのへんの仕様読んでた話です
引数を int 型変換したものを f とします
undefined なら 0 にします
f < 0 or f > 20 なら RangeError を発生させます
ただ 0 より小さい時と 20 より大きい時の値に対して tofixed の動作を拡張する実装が許容されてます
Chrome だとこんなエラーが出るので 100 桁までは有効みたいです
toFixed() digits argument must be between 0 and 100
単純な場合は以下の文字列になります
それ以外のときはちょっと複雑になります
と言っても 仕様としての関数での処理であって 結局のところのやってることは指定桁数まで表示して 超えている部分は丸めるというだけです
this の値を x として
「n / (10 ** f) - x」 が 0 に最も近くなる整数を n とします
書き換えるとこうなります
n = x * (10 ** f)
x と f はメソッド実行時に決まるので n を計算できます
1.23.toFixed(3) なら 「1.23 * 10 ** 3」 となり n は 1230 です
12.3.toFixed(0) なら 「12.3 * 10 ** 0」 となり 12.3 ですが整数なので n は 12 になります
f が 0 なら n を文字列として返します
f が 0 以外なら
n の桁数を k として k <= f となる場合に f + 1 - k 個の 0 (0x0030) を n の前にくっつけて k を f + 1 に置き換えます
k <= f となるのは 「0.012.toFixed(2)」 など数値が 1 未満のときです
最後に n の前から k - f 文字と ドット 「.」 と n の残りの文字を結合したものを返します
1.23.toFixed(3) なら k = 4, f = 3, n = 1230 なので 1.230 です
0.012.toFixed(2) なら k = 1, f = 2, n = 1 でその後 k = 3, n = 001 になり 0.01 です
ちゃんと toFixed で期待される値になっています
k = 1, f = 2, n = 2 → k = 3, n = 002 → 0.02 となると思うのですが 実際は 0.01
n / 100 - 0.015 を 0 に近づけるところで
n = 1 だと 0.01 - 0.015 = -0.005
n = 2 だと 0.02 - 0.015 = 0.005
2 値の 0 との距離が等しい場合は n が大きい方と指定されているので 2 であってると思います
そうなると 0.01 になりようがないと思うのですけど
5 は常に切り上げじゃなくて偶数に丸めるような挙動なのかとおもえば 0.01 と奇数になっているのでさらに謎です
ところで 0.115.toFixed(2) なら 0.12 になります
toFixed のほうが扱える桁数が多いわけでもなく Number.MAX_SAFE_INTEGER を超えて正確に扱えない部分も表示しているだけです
これも 10 の 21 乗までです
10 の 21 乗以上になると toString が呼び出されるようになるので 小数形式じゃなく指数形式になります
Number.prototype.toString では基数を指定して文字列化できます
2 ~ 36 の値を入れられます
10 以外のときは特殊な動きになるのでここでは 10 のみを対象にします
http://www.ecma-international.org/ecma-262/#sec-tostring-applied-to-the-number-type
それ以外がちょっと複雑です
n, k, s を以下の条件にあてはまる int 型の値とします
k >= 1 && 10**(k-1) <= s < 10**k (k は可能な限り最小)
m = s * 10**(n-k) (m は this の数値)
k は s の桁数ということになります
k=1 → 1 <= s < 10
k=2 → 10 <= s < 100
m が 12.34 の場合に (1234 * 10**-2) という形式にします
ここの段階には 0 やマイナスの値はきません
k <= n <=21 (1 以上の整数)
s の k 桁の数字のあとに n - k 個の 0 (0x0030) をあとにつけます
例: 200, 13
0 < n <= 21 (1 以上の小数)
s の上位 n 桁の数字のあとに小数点 (0x002e) そのあとに残った s の k - n 桁をつけます
例: 1.23, 130.4
-6 < n <= 0 (1 未満の小数)
0 (0x0030) のあとに小数点 (0x002e) そのあとに -n 桁の 0 (0x0030) そのあとに s の k 桁をつけます
例: 0.0123, 0.39
k = 1 (大きすぎか小さすぎで数字が1つだけ)
s の 1 桁のあとに e (0x0065) そのあとに n - 1 が正か負に応じて + (0x002b) または - (0x002d) そのあとに n - 1 の絶対値をつけます
例: 4e+20, 2e-3
その他 (大きすぎか小さすぎ)
s の上位 1 桁のあとに小数点 (0x002e) そのあとに s の残りの k - 1 桁そのあとに e (0x0065) そのあとに n - 1 が正か負に応じて + (0x002b) か - (0x002d) そのあとに n - 1 の絶対値をつけます
例: 1.23e+10, 9.8765e-4
小数点より上は 22 桁から 小数点以下は 7 桁から指数表記になります
ただ 読んでみても toFixed と違って有効桁数超えた部分を丸めるような処理は見あたらなかったです
英語+最終的に何をしたいかじゃなくて手続き的な順番にする処理の羅列ということでただでさえ読みづらく理解し難いのに ブラウザで確認したら一部例外的な動きをしてる ということもあっていつも以上に納得行かなかったです
特別理由がないならブラウザで適当にいくつかのケースを実行して こう動くものなんだと思うのが楽そうですね
1000000000000000000000..toFixed(3)
// 1e+21
// 1e+21
toFixed
http://www.ecma-international.org/ecma-262/#sec-number.prototype.tofixed引数を int 型変換したものを f とします
undefined なら 0 にします
f < 0 or f > 20 なら RangeError を発生させます
ただ 0 より小さい時と 20 より大きい時の値に対して tofixed の動作を拡張する実装が許容されてます
Chrome だとこんなエラーが出るので 100 桁までは有効みたいです
toFixed() digits argument must be between 0 and 100
3..toFixed(100)
// "3.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
// "3.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
単純な場合は以下の文字列になります
- this が NaN なら "NaN"
- this が負値なら "-" のあとに -this の値 (正値になる) に対しての結果をくっつけたもの
- this の値が 10 の 21 乗 (22桁) 以上なら toString メソッドの結果
それ以外のときはちょっと複雑になります
と言っても 仕様としての関数での処理であって 結局のところのやってることは指定桁数まで表示して 超えている部分は丸めるというだけです
this の値を x として
「n / (10 ** f) - x」 が 0 に最も近くなる整数を n とします
書き換えるとこうなります
n = x * (10 ** f)
x と f はメソッド実行時に決まるので n を計算できます
1.23.toFixed(3) なら 「1.23 * 10 ** 3」 となり n は 1230 です
12.3.toFixed(0) なら 「12.3 * 10 ** 0」 となり 12.3 ですが整数なので n は 12 になります
f が 0 なら n を文字列として返します
f が 0 以外なら
n の桁数を k として k <= f となる場合に f + 1 - k 個の 0 (0x0030) を n の前にくっつけて k を f + 1 に置き換えます
k <= f となるのは 「0.012.toFixed(2)」 など数値が 1 未満のときです
最後に n の前から k - f 文字と ドット 「.」 と n の残りの文字を結合したものを返します
1.23.toFixed(3) なら k = 4, f = 3, n = 1230 なので 1.230 です
0.012.toFixed(2) なら k = 1, f = 2, n = 1 でその後 k = 3, n = 001 になり 0.01 です
ちゃんと toFixed で期待される値になっています
丸め
0.015.toFixed(2) のときはk = 1, f = 2, n = 2 → k = 3, n = 002 → 0.02 となると思うのですが 実際は 0.01
n / 100 - 0.015 を 0 に近づけるところで
n = 1 だと 0.01 - 0.015 = -0.005
n = 2 だと 0.02 - 0.015 = 0.005
2 値の 0 との距離が等しい場合は n が大きい方と指定されているので 2 であってると思います
そうなると 0.01 になりようがないと思うのですけど
5 は常に切り上げじゃなくて偶数に丸めるような挙動なのかとおもえば 0.01 と奇数になっているのでさらに謎です
ところで 0.115.toFixed(2) なら 0.12 になります
有効桁数
仕様の Note にも書かれているのですが1000000000000000128..toFixed(0)
// 1000000000000000128
1000000000000000128..toString()
// 1000000000000000100
という違いがあります// 1000000000000000128
1000000000000000128..toString()
// 1000000000000000100
toFixed のほうが扱える桁数が多いわけでもなく Number.MAX_SAFE_INTEGER を超えて正確に扱えない部分も表示しているだけです
Number.MAX_SAFE_INTEGER
// 9007199254740991
1000000000000000125..toFixed(0)
// 1000000000000000128
1000000000000000100..toFixed(0)
// 1000000000000000128
計算する前の段階で情報が失われてるので あまりを求めて下位部分だけとりだしてもこうなります// 9007199254740991
1000000000000000125..toFixed(0)
// 1000000000000000128
1000000000000000100..toFixed(0)
// 1000000000000000128
1000000000000000234 % 1000
// 256
// 256
これも 10 の 21 乗までです
10 の 21 乗以上になると toString が呼び出されるようになるので 小数形式じゃなく指数形式になります
toString
toFixed の内部で使われてるので toString も見てみますNumber.prototype.toString では基数を指定して文字列化できます
2 ~ 36 の値を入れられます
10 以外のときは特殊な動きになるのでここでは 10 のみを対象にします
http://www.ecma-international.org/ecma-262/#sec-tostring-applied-to-the-number-type
- NaN は "NaN"
- +0, -0 は "0"
- 負値は "-" と正値としての結果をくっつけたもの
- ∞ は "Infitity"
それ以外がちょっと複雑です
n, k, s を以下の条件にあてはまる int 型の値とします
k >= 1 && 10**(k-1) <= s < 10**k (k は可能な限り最小)
m = s * 10**(n-k) (m は this の数値)
k は s の桁数ということになります
k=1 → 1 <= s < 10
k=2 → 10 <= s < 100
m が 12.34 の場合に (1234 * 10**-2) という形式にします
m | s * 10**(n-k) | k, n, s |
---|---|---|
12.34 | 1234 * 10**-2 | k = 4, n = 2, s = 1234 |
25000 | 25 * 10**3 | k = 2, n = 5, s = 25 |
0.09 | 9 * 10**-2 | k = 1, n = -1, s = 9 |
1 | 1 * 10**0 | k = 1, n = 1, s = 1 |
ここの段階には 0 やマイナスの値はきません
k <= n <=21 (1 以上の整数)
s の k 桁の数字のあとに n - k 個の 0 (0x0030) をあとにつけます
例: 200, 13
0 < n <= 21 (1 以上の小数)
s の上位 n 桁の数字のあとに小数点 (0x002e) そのあとに残った s の k - n 桁をつけます
例: 1.23, 130.4
-6 < n <= 0 (1 未満の小数)
0 (0x0030) のあとに小数点 (0x002e) そのあとに -n 桁の 0 (0x0030) そのあとに s の k 桁をつけます
例: 0.0123, 0.39
k = 1 (大きすぎか小さすぎで数字が1つだけ)
s の 1 桁のあとに e (0x0065) そのあとに n - 1 が正か負に応じて + (0x002b) または - (0x002d) そのあとに n - 1 の絶対値をつけます
例: 4e+20, 2e-3
その他 (大きすぎか小さすぎ)
s の上位 1 桁のあとに小数点 (0x002e) そのあとに s の残りの k - 1 桁そのあとに e (0x0065) そのあとに n - 1 が正か負に応じて + (0x002b) か - (0x002d) そのあとに n - 1 の絶対値をつけます
例: 1.23e+10, 9.8765e-4
小数点より上は 22 桁から 小数点以下は 7 桁から指数表記になります
123456789012345678901
// 123456789012345680000
1234567890123456789012
// 1.2345678901234568e+21
0.0000012
// 0.0000012
0.00000012
// 1.2e-7
// 123456789012345680000
1234567890123456789012
// 1.2345678901234568e+21
0.0000012
// 0.0000012
0.00000012
// 1.2e-7
ただ 読んでみても toFixed と違って有効桁数超えた部分を丸めるような処理は見あたらなかったです
英語+最終的に何をしたいかじゃなくて手続き的な順番にする処理の羅列ということでただでさえ読みづらく理解し難いのに ブラウザで確認したら一部例外的な動きをしてる ということもあっていつも以上に納得行かなかったです
特別理由がないならブラウザで適当にいくつかのケースを実行して こう動くものなんだと思うのが楽そうですね