質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.47%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

2回答

4481閲覧

floatからdoubleへキャストする際の誤差について

Gurz1019_MP

総合スコア196

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

1グッド

0クリップ

投稿2022/05/17 11:16

編集2022/05/17 14:36

C# 7.3において、float型の変数をdouble型にキャストした際、非常に小さな値の誤差が発生しています。

例:float : 29 → double : 29.0000009536743

なんとなく、誤差が発生しそうだな、という気はしますが、これの原因を正確に説明できなくて困っています。
より大きい値を扱える型へのキャストでは誤差は発生しないものと思っていたのですが…

よろしくお願いいたします。

追記
全く同じコードは掲載できないのでイメージでしかありませんが、以下のようなコードをUnity 2019.4.15fで実行した際に発生したものです。

C#

1int A = 29; 2int AMax = 45; 3float ARatio = A / (float)AMax; 4 5Debug.Log(AMax * ARatio) // 29 6Debug.Log(AMax * (double)ARatio) // 29.0000009536743
Bongo👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2022/05/17 11:32 編集

このコメントは回答欄に移しました。
退会済みユーザー

退会済みユーザー

2022/05/17 13:44

> 以下のようなコードをUnity 2019.4.15fで実行した Unity のタグをつけておいてください。 Unity のタグが付いていたら自分は最初から関わらなかったのでお互い無駄なやり取りをしないで済んだかも
Gurz1019_MP

2022/05/17 13:51

SurferOnWww様 つけておきました。すみません、この質問の内容がUnityと関連するとは思っていませんでした
退会済みユーザー

退会済みユーザー

2022/05/17 14:02

Unity 関連の質問者さんはアレなので関わらないようにしているのです。なので私はこれで撤退します。
Gurz1019_MP

2022/05/17 14:38

質問文をやってはならないレベルで変更しすぎてしまったようなので、書き方を変えました
ikadzuchi

2022/05/18 14:06

別にUnityに関係しないC#の仕様の話ですから、Unityタグを付けないのは何も間違っていませんよ。 むしろどちらかといえばつけるべきでないと思います。 まあアレな回答者を避けられるようですので付けた方がよいかもしれませんね。
guest

回答2

0

ベストアンサー

float型で表現可能な29の次に大きい数値を求めて、double型に変換すれば、似たような結果にはなります。(参考URL)

c#

1using System; 2 3public class Test{ 4 public static void Main() { 5 float value = 29; 6 int bits = BitConverter.ToInt32(BitConverter.GetBytes(value), 0); 7 float nextValue = BitConverter.ToSingle(BitConverter.GetBytes(bits + 1), 0); 8 double d = nextValue; 9 Console.WriteLine(nextValue.ToString()); 10 Console.WriteLine(d.ToString()); 11 } 12}

結果

text

129 229.0000019073486

ただ、見ての通り、floatで表現可能な29の次に大きい数値をdoubleに変換しても29.0000019073486にしかならないので、29.0000009536743になるというのは何かの間違いではないでしょうか。

なお、ここで求めたfloat値とdouble値は、誤差なく完全に同じ数値です。
それなのにfloat値だけ29と表示されてしまうのは、ToString()の仕様が原因となります。

ToString()に数値書式指定文字列を与えない場合のデフォルトは"G"であり(参考URL)、"G"に精度を指定しない場合のデフォルトはfloatの場合7桁なので、29と表示されます。


追記への回答

Zuishinさんが別回答のコメントでおっしゃっている通り、AMax * (double)ARatioの結果はdoubleで、AMax * ARatioの結果はfloatです。(参考URL)

計算結果は正しくは29.0000009536743ですが、floatの精度では表現できない数値なので、丸められて29となります。


もしかして、全部floatで計算したほうが、途中でfloatをdoubleに変換して計算したときより、結果が正確に見えるのはなぜか、という質問でしょうか。
それは、最初にfloatで計算してしまってるので、途中でdoubleにしてもfloatでの有効桁までしか信用できないためです。

floatの有効桁は10進数で7桁+α、doubleの有効桁は15桁+αです。
最初にfloatで計算してしまっているので、その結果は7桁までしか信用できません。
その計算結果を後からdoubleにして15桁の有効桁で計算しても、15桁まで信用できるわけではなく、変わらず7桁までしか信用できません。
doubleは15桁まで信用できる前提で表示するので、29.0000009536743のように表示されて正しくないように見えますが、先頭7桁だけ見れば、29という正しい結果になっていることが分かると思います。

投稿2022/05/17 13:44

編集2022/05/17 21:04
actorbug

総合スコア2224

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Gurz1019_MP

2022/05/17 13:58

申し訳ありません。それほど大きな変更とは思っていませんでした… ToStringの仕様については、これもちょっと疑問だったので助かりました。ありがとうございます。
Gurz1019_MP

2022/05/18 03:11 編集

いろいろ話を聞いて、シミュレータを使って自分でも調べて、ようやく納得ができました。 浮動小数点数内部表現シミュレーター → https://tools.m-bsys.com/calculators/ieee754.php 質問の内容は質問文の通りで、floatに格納した値をdoubleにキャストしたときに、誤差が出るのはなぜか、という質問です。29.0000009536743の、9536743の部分はどこから出てきたのか、ということです。これは、値をfloatに格納した時点で、数値の丸めが発生し、それをdoubleにキャストすると、丸められた値をdoubleの精度で正確に表現してしまうため、このような誤差が発生するのだと理解しました。つまりキャストで丸めが発生したのではなく、キャストで丸められた値が表面化した、という感じでしょうか
guest

0

float は 4 バイト、double は 8 バイトの違いがある(浮動小数点を表すことができる精度に違いがある)のだから、違って当たり前ということで納得できませんか?

浮動小数点数値型 (C# リファレンス)
https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/builtin-types/floating-point-numeric-types

投稿2022/05/17 11:31

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2022/05/17 11:59

こんな小さな整数で誤差が出ましたっけ?
Gurz1019_MP

2022/05/17 12:01

うーむ… "違っても不思議ではない"とは思えるのですが、"当たり前"とはちょっと直感的に思えないです。存在しない仮数部の桁は全部ゼロにしてしまえば誤差なんて発生しないのでは? と思ってしまします。違いますかね?
Zuishin

2022/05/17 12:10

その誤差が出るコードを書くのが早いと思います。
退会済みユーザー

退会済みユーザー

2022/05/17 12:14 編集

そもそも「float : 29」とは何なのでしょうか? それの内部表現がどうなっていて、double にキャストしたものの内部表現がどうなっているのでしょうか? そしてそれらを何らかの書式設定で文字列に変換した時どうなるのでしょうか? その時の違いに何か意味があるのでしょうか?
退会済みユーザー

退会済みユーザー

2022/05/17 12:31 編集

要するに、丸めの誤差が避けれられない float や double のような浮動小数点型で、このスレッドのように float を double にキャストして文字列にした結果がちょっと違うという話はあまり意味がなさそうと言っています。
Gurz1019_MP

2022/05/17 13:05

すみません、確かに例の書き方が悪かったですね。 float : 29は、全く同じコードは掲載できないのでイメージでしかありませんが、以下のようなコードをUnity 2019.4.15fで実行した際に発生したものです。値はDebug.Logでコンソールに出力しています。 int A = 29; int AMax = 45; float ARatio = A / (float)AMax; ここで、AMax * ARatioの値が29になりました。一方、AMax * (double)ARatioの値は、質問文に示す値になりました。このコードのあとで (int)Math.Ceiling(AMax * ARatio); を計算する必要があり、これがAよりも1大きくなる事があり、それが困るので、その原因を調べていて発見しました。 この問題は、ARatioの型をdoubleにすることで解決したのですが、値が違った理由が説明できなくて… ということです
退会済みユーザー

退会済みユーザー

2022/05/17 13:22 編集

話が変わってきてます。表題は、 > floatからdoubleへキャストする際の誤差について でキャストしたときの話だったはずですけど。float と double の話だったはずが、int 型も話に入って、あれこれいじりまわしてますよね。コンパイラの最適化の影響とかないですか? 何にせよ、丸めの誤差が避けれられない float や double のような浮動小数点型で、文字列にした(コンソールに出力した)結果がちょっと違うという話は意味がないと思います。
Gurz1019_MP

2022/05/17 13:22

同じ環境ではありませんが、.NET6.0コンソールアプリケーションで、以下のコードで再現いたしました。 int A = 29; int AMax = 45; float ARatio = A / (float)AMax; Console.WriteLine(AMax * ARatio); Console.WriteLine(AMax * (double)ARatio); うまく表現できているかわからないのですが、同じ浮動小数点型の、より大きな値が扱える型にキャストしているだけなのに、何らかの丸めが生じる、ということがよくわからなかった、という感じです
退会済みユーザー

退会済みユーザー

2022/05/17 13:28 編集

話が違うと言ってるのに・・・
Gurz1019_MP

2022/05/17 13:29

違うかもしれませんね
Zuishin

2022/05/17 13:32

> AMax * (double)ARatio この結果は double です。 > AMax * ARatio この結果が float であれば、丸めが入るので結果が変わってくるのでは? C 言語の場合、float 同士の演算は double になっていたと思いますが、C# でどうだったかは記憶していません。 調べてみてはどうでしょうか。
Zuishin

2022/05/17 13:38 編集

あ、よく見ると AMax が int なので float で確定ですね。 と書いたけど、早とちりです。試したことがないのでわかりません。
退会済みユーザー

退会済みユーザー

2022/05/17 13:37

質問内容をまるまる書き換えないでください。それは回答とのつじつまが合わなくなるので絶対やってはいけないこと。 > 違うかもしれませんね かもしれないじゃなくて間違いなく違いますよ。そもそもは「floatからdoubleへキャストする際の誤差について」だったでしょ。
退会済みユーザー

退会済みユーザー

2022/05/17 13:42

> 以下のようなコードをUnity 2019.4.15fで実行した Unity のタグをつけておいてください。 Unity のタグが付いていたら自分は最初から関わらなかったのでお互い無駄なやり取りをしないで済んだかも。
Gurz1019_MP

2022/05/17 13:43

喧嘩になりそうなのでその話はもういいです。すみませんでした。 Console.WriteLine(ARatio); Console.WriteLine((double)ARatio); これでも違うのですが、これが疑問の原因だと思っているのですが、これでも質問文と違いますか?
Zuishin

2022/05/17 13:44

見えてるなら返事しろよ。
Gurz1019_MP

2022/05/17 13:49

Zuishin様、すみません、色々書いたり考えながらだったので返事が遅れました。 >調べてみてはどうでしょうか。 調べても良いキーワードが思い浮かばずよくわかりません。教えてください。
退会済みユーザー

退会済みユーザー

2022/05/17 13:53

> 喧嘩になりそうなのでその話はもういいです。 あなたが喧嘩を売っているようなものだと思いませんか。話がどんどん変わっていったり、質問を書き換えたり、回答者・閲覧者に大変失礼です。私だけでなく、あなたにお付き合いしていろいろ考えた人は皆さん怒っていると思いますよ。
退会済みユーザー

退会済みユーザー

2022/05/17 13:58

やっぱりあなたは理由も書かずにマイナス評価を付ける卑怯者だったんだね。
Gurz1019_MP

2022/05/17 14:06

私マイナス評価つけてないんですが… すみません、全く喧嘩を売っている意識はありませんでした。満足できる回答をいただけないので、色々方法を考えて書き換えてしまいました。話が変わっていたかもしれませんが、それも認識していませんでした。
Zuishin

2022/05/17 14:25

私でもないので、通りすがりの第三者でしょう。しかし、この低評価は嫌がらせの意図抜きで純粋に回答の評価として十分成り立つと思えるので、高評価はしません。理論上、編集前の質問でこの回答が成り立つことはないからです。 ですが、編集後の質問に対する回答であれば、特に矛盾はありません。矛盾がないというだけで解決を導く力は弱いので、今は私は中立の評価です。
Zuishin

2022/05/17 14:46

なお、今試したところ、float かける int が float になることが確かめられました。 一方が double で一方が float なので、精度に差があるのは当然ということになります。 Console.WriteLine(AMax * ARatio); // float Console.WriteLine(AMax * (double)ARatio); // double
Gurz1019_MP

2022/05/17 14:56

Zuishin様わざわざ有難うございます。GetType()については、ちょっとよくわかりませんでした。ARatioと(double)ARatioの時点で差が出てるのですが、これの原因はわかりますか…? また、精度が違うけれど大きいのだから、doubleはfloatが表現できる値をすべて表現できると思っていたのですが、違うのでしょうか…? 何にせよ、このキャストで丸めが発生する理由がよくわからないです
Zuishin

2022/05/17 15:16

actorbug さんの回答の通りです。表示する際に型によって違う丸め方がなされています。
退会済みユーザー

退会済みユーザー

2022/05/17 15:29

撤退したと言っておきながら戻ってきてレスするのもなんですが・・・ キャストのせいでも、ToString による表示長のせいでもなくて、float * float と double * double の計算の精度の違いによるようですよ。書式を指定して以下のようにすると、 Console.WriteLine("{0:F15}", AMax * ARatio); // float * float Console.WriteLine("{0:F15}", AMax * (double)ARatio); // double * double 結果は以下の通りとなります。 29.000000000000000 29.000000953674316
Gurz1019_MP

2022/05/18 03:40

いろいろ話を聞いて、自分でも調べて、ようやく納得ができました。 ありがとうございました
退会済みユーザー

退会済みユーザー

2022/05/18 14:37

> floatからdoubleへのキャストで誤差が生じると読める回答ですが そんなこと私の回答のどこに書いてあるの? 脳内変換とかしてませんか?
ikadzuchi

2022/05/19 14:31

え… 本気で言ってます? 「キャストで誤差が発生した。発生しないはずと思っていたが何故か?」という内容の質問に「違って当たり前ということで納得できませんか?」と答えておいて、質問の前提であるキャストで誤差が発生したことを認めていないというのですか? 続く「存在しない仮数部の桁は全部ゼロにしてしまえば誤差なんて発生しないのでは?」というキャストで誤差が発生した前提の質問にも、前提の誤りを正すこと無く話をそらしています。 「質問の前提について誤った認識で回答した」のでないとすると、「質問の前提を無視して無関係な回答をした」ことになりますが…。
退会済みユーザー

退会済みユーザー

2022/05/19 23:15

やっぱり脳内変換としか思えませんけど。模範回答を回答欄に書いてもらえませんか
ikadzuchi

2022/05/22 06:45

> 脳内変換としか思えません そうですか。あなたにまともな判断力を期待してはいません。 > 模範回答を回答欄に書いてもらえませんか 面倒なので嫌です。 actorbugさんの回答で十分だと感じましたし。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.47%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問