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

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

新規登録して質問してみよう
ただいま回答率
85.48%
VB

VB(ビジュアルベーシック)はマイクロソフトによってつくられたオブジェクト指向プログラミング言語のひとつで、同社のQuickBASICが拡張されたものです。VB6の進化版といわれています。

.NET Core

.NET Coreは、マネージソフトウェアフレームワークでオープンソースで実装されています。クロスプラットフォームを前提に考えられており、Windows/Mac/Linuxで動くアプリケーションを作成することが可能です。

C#

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

Q&A

解決済

5回答

7692閲覧

0.999... = 1にするための最適な方法について

yokatone

総合スコア43

VB

VB(ビジュアルベーシック)はマイクロソフトによってつくられたオブジェクト指向プログラミング言語のひとつで、同社のQuickBASICが拡張されたものです。VB6の進化版といわれています。

.NET Core

.NET Coreは、マネージソフトウェアフレームワークでオープンソースで実装されています。クロスプラットフォームを前提に考えられており、Windows/Mac/Linuxで動くアプリケーションを作成することが可能です。

C#

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

0グッド

1クリップ

投稿2019/05/10 06:44

編集2019/05/10 06:49

前提・実現したいこと

.999...の値を1として処理したい

発生している問題・エラーメッセージ

現在小数点許容、基本的に切り捨て処理である処理を作成しています。
(Decimal型で受けて、Decimal型で返します)
このとき、例えば123.9999....の値の場合、返り値が123になってしまいますが、
実計算上では124になってほしいのです。

この対策についていろいろと調べてみましたが、いまいち要領を得ず
(.999...は浮動小数点としての計算的には正しい処理なので当然なのでしょうが...)
このままだとif条件分岐などで対応してしまいそうなので、
0.999... = 1 にする一般的で最適な方法について、
ぜひ皆様のお知恵を拝借させてください。

該当のソースコード

private decimal Kirisute(decimal d){ //処理 } decimal da = 1 decimal db = (da / 3) decimal dc = db * 3 decimal dd = Kirisute(dc) // expected: dd = 1, dc = 0.999... // actual: dd = 0.999..., dd = 0.999....

補足情報(FW/ツールのバージョンなど)

C# 3.0
VB 2008

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

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

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

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

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

m.ts10806

2019/05/10 06:46

>切り捨て処理 0.999→1なら「切り上げ」だと思いますが、なぜ「切り捨て」と?
yokatone

2019/05/10 06:51

基本的に0.5, 0.91などは切り捨て処理を行うが、 例外的に0.999...は切り上げ処理にしたい...という目的の関数であるためです。
m.ts10806

2019/05/10 06:52

具体的に記載しておいてください。
m.ts10806

2019/05/10 06:53

すでに2つ、その要件が満たされない回答がついてしまっていて無駄になっています。
Zuishin

2019/05/10 07:17

意味不明ですね。 0.99 は 1 になり、0.998 は 0 になるのなら、1 < 0 となってしまいます。
fana

2019/05/13 01:30

0.001を足すという方法がBAに選ばれてますし, 結局,本当にやりたいことは「9が連続する場合だけを特別扱い」ではなかった,ということかな?
yokatone

2019/05/14 00:27 編集

>Zuishinさん 0.990と0.998(桁そろえしてます)、さらに0.999があるとして、当然1 > 0.999 > 0.998 > 0.990です。 桁そろえのために足す0.0..1の桁数は可変ではなく固定なので、0.001を足すと、1.001 > 1.000 > 0.999 > 0.991 で、小数点以下丸ごと切り捨てなので、1(1)、1(0.999)、0(0.998)、0(0.99)という結果になります。よって0.990 = 0であって、 0.998 = 0です。 この質問のなかにある`.999...`とは、小数点以下において9が無限に続くことを意味しており、小数点以下の9の数が3ケタとか4ケタとかに決まっているわけではありません。また、0.99は確かに小数点以下に存在する数字すべてが9になっていますが、0.99の実態というのはその9以下は0で表現されているところが省略され0.990000...といったものであるため、これは意図したい無限小数ではありません。 この質問と、そしてBAに至るまでに、私自身が無限小数を1とみなす処理は現実的にできず、有効桁数を設定する必要があると考えを変えたので、質問とBAの意味するところの差異に混乱があったかもしれません。 >fanaさん まず前提として、たとえば0.99899という数字があって、足す桁数をそろえ、0.00001を足すとします。 すると0.99900になりますが、正の位まで繰り上がってないので、小数点以下を切り捨て、結果は0となります。 おっしゃりたいのは、0.9992という数字があったとき、加える小数点の位を0.001と定義してしまうと、すべての小数点以下が9ではないにも関わらず1になってしまう、ということですよね。 もちろんできることであれば0.999....という場合のみに1という扱いにしたかったのですが、 皆様からご回答をいただく中で、有効桁数を決めない限りそのような処理は不可能である (もちろん数値を文字列に変換し、ループを回してすべての小数点以下の文字が9であるかどうか、という判断を行う方法を取れば可能ですが)という 考えに至りましたので、最も無難で処理も煩雑にならない、有効桁数を定義する処理をBAにさせていただいた次第です。
Zuishin

2019/05/14 00:35

質問に書いてあるのは「9 が無限に連続した時」というもので、「9 が〇〇個連続した時」というものではありません。無限は扱えないので途中で切る必要があります。どこで切るかが示されていないので 9 の連続が途切れた時、もしくは数値が一定の上限を超えたときと推測されます。 つまり二通りの考え方があるわけですね。 「ここまでは質問者もわかった上で質問している」という前提で考えると意味不明になりますし、「ここがわかっていない」と考えると、どちらかあるいは両方を答える回答になります。 なので、次回質問された場合は、追記修正依頼を読んでそれに答え、質問意図を明確にしてください。でなければ回答者は推測で答えるより他なくなります。今回は二通りしかないので、たまたま推測が当たっているようですが、次回もそうなるとは限りません。「意味不明」と言われている時には、質問をした人が意味を明確にする必要があると思います。 だから私は回答していないし、他にも回答を控えた人が大勢いることでしょう。「今回は」回答した人の中から正解が出ました。
fana

2019/05/14 01:29

> (もちろん数値を文字列に変換し、ループを回してすべての小数点以下の文字が9であるかどうか、という判断を行う方法を取れば可能ですが) これだと私の(問題の内容を間違った)回答と同じ結果になりそうな…?
yokatone

2019/05/14 05:58

>fanaさん ほんとだ。そうなってしまいますね。 気づきませんでした。ありがとうございます。
guest

回答5

0

ベストアンサー

この誤差はどうにもならないので、切り捨てる前に0.001を足して切り捨てるという方法になると思います
基本的に小数に落ちた時点で厳密な計算は諦めるしかないのが実情です

整数同士の四則演算しか行わず、一切の誤差が出ない状況であってほしい、となると
分数型クラスを自作するという方法もあります
(ぐぐったら、いくつか出来合いのものがあるようです… 信頼度はわかりませんが)

投稿2019/05/10 07:00

izmktr

総合スコア2856

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

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

maisumakun

2019/05/10 07:01

言語によっては標準ライブラリに有理数型がある例もありますが、C#にはないみたいですね。
yokatone

2019/05/10 07:05

0.001などの小さい値を足すという処理はスマートですね。 丸めを行う一般的な方法である桁数を指定して、掛け算をして、戻して...という処理は どうにもよからぬミスなど発生してしまいそうで、躊躇しておりましたが その方法だと見た目もスッキリしそうです。ありがとうございます。
guest

0

0.999... = 1 にする一般的で最適な方法

そのような計算が必要なら、decimalではなく有理数として処理するのがいちばん適切ではないかと思います。

それができないのであれば、計算全体を俯瞰してどれだけの桁までが正しい演算なのか吟味して、適切な桁で丸める、ということになるかと思います。

投稿2019/05/10 06:50

maisumakun

総合スコア145184

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

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

yokatone

2019/05/10 06:57

計算上は正しいのだから、どこまでを切り上げ/切り捨てなど、どういう風に見做すかという処理は やはりその許容を定義するしかないんですね。ありがとうございます。
maisumakun

2019/05/10 06:57

厳密な値が得られれば、あとは改めて切り捨てをすればOKです。
yokatone

2019/05/10 07:03

そのように関数を作成します。ありがとうございました。
guest

0

やりたいことの話と合っているかわかりませんが…

入力値の指数部を見れば,小数点以下が何桁あるのかわかると思うので,
入力値を「切り捨て処理」した結果の値をIとするときに,Iに対して前記桁数分だけ小数点以下に9を並べた値
I.99999...
を作って,それが入力値と等しいか否かを判定するとか…?


雰囲気コードを書いてみた.

cshrp

1decimal Input = 112.99999m; //何か入力値があって… 2 3//とりあえず入力値が正だとしたらこんな処理? 4//(負の場合も同じような感じで) 5decimal I = (decimal)( (int)Input ); //小数点以下を切り捨てた値 6var N = ( decimal.GetBits( Input )[3] >> 16 ) & 0x7F; //小数点以下の桁数 7decimal Nines = 1m - (decimal)Math.Pow( 0.1, N ); //0.999.. と9がN個並ぶ 8 9decimal Result = ( (I + Nines)==Input ? I+1m : I ); //判定して結果値を作る

投稿2019/05/10 08:06

編集2019/05/10 08:36
fana

総合スコア11658

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

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

yokatone

2019/05/14 00:37 編集

まず文章について、 0.99であっても、0.9であっても、小数点以下の文字すべてが9であると必ず1になってしまうように思えます。0.9000や0.9900は1ではなく、0.9999....のみが1である、といいますか、 要するに(1/3)*3 = 0.999...というケースのみ、1と見做したいのです。 やるのであれば、型が持てる小数点の上限をnとして、9のチェックをすることになるかと思います。 次にコードについて、 ありがとうございます。GetBitsのことは知りませんでしたので、稚拙な回答であることをお許しください。これから試してみます。ありがとうございました。
fana

2019/05/14 01:11

なるほど,内容を勘違いしていたことがわかりました. 入力がdecimalとされていたので, 「小数点以下の桁が全て9で構成されている」を判定するのかと思ったのですが,そうではなくて 「(実数の世界で考えた際に?)9が無限に続く」という意味なのですね.
yokatone

2019/05/14 06:19 編集

ややこしくてすみません。(評価対象となるdecimal型のバイナリにおいて、そのdecimal型が表現できる最大有効桁数の小数点部位のすべての値において)"小数点の桁以下がすべて9で構成されている"という、その捉え方であっています。 これぞ正解です、と即座に歯切れよく言えなかったのは、GetBitsが変数自体をバイナリとして評価する関数であろうという推測は持ちつつも、その関数自体の実際の挙動を知らないので、自分としてはとりあえずやってみなければわからない....といった心持ちよりのものでした。 (decimalの仮数や指数のビット数の知識などがなく、そもそも浮動小数点の表現方法についても自信がなく、指数部を評価しているところまでは理解できるものの、このときの"n個並ぶ"のnとは、decimalの表現できる上限をnとしているのか、何らかの数字が入ってる桁数をnとしているかがわからなかったことなど...) 本物の無限小数はPCでは表現できませんし、"PCの中、decimal型の範囲内で表現される、見かけ上無限小数ということのできる数"(つまり.999...)を=1にしたかったのです。おそらくソース的にfanaさんの回答は正しいものであると思います。ありがとうございました。
fana

2019/05/14 06:52

私のコードだと,入力が 1.9 のときに切り上げて2にしてしまいますが,おそらくこれはやりたいことではないのでダメでしょう. 1.9という状態は仮数部は5bitしか使ってない(スペース的には1.99や1.999のようにもっと小数点以下に9を詰め込める余地があるのにそうしてない).つまり,本当に値が1.9なのであって,9がたくさん続かない値だということ. やりたいことはきっと「仮数部を限界まで使った状態(?)であって,且つ,小数点以下が全部9になっている」を判定することなんじゃないかな,と思います.
guest

0

こんにちは。

それは一般に「丸め」と呼ばれている処理です。そのうち四捨五入を使えばOKです。
C#なら、大抵の場合Math.Round()関数を使えば良いと思います。(厳密には微細差があります。)

投稿2019/05/10 07:06

Chironian

総合スコア23272

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

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

yokatone

2019/05/10 07:11

すみません。すでに怒られてますが、Roundでは `0.5 = 1`になってしまいますが、 実現したい機能はあくまで `0.999... = 1`のみで、 `0.5 = 0` でなければならないのです。。
Chironian

2019/05/10 07:24

なるほど、あまり一般的な処理ではないのですね。 123.9999以上3未満を123へ切り上げるなら、0.0001を加えて少数以下を切り捨てればよいと思います。 なお、123.9999....の....が9の連続を意味する場合、9の続く数が不明なのでそのような曖昧な条件では処理不可能です。9がいくつ続けば切り上げるか等の切り上げ条件を事前に決定しておく必要があります。 また、もし浮動小数点数を用いる場合は有効桁数を考慮して慎重な検討が必要です。
guest

0

小数点第二位以下を 切り捨てや四捨五入する方法

を読んでみては?
別に二位以下じゃなくても応用できると思います。

投稿2019/05/10 06:47

showkit

総合スコア1638

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

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

yokatone

2019/05/10 06:54

0.999....の時のみ切り上げたく、0.5などは切り捨てにしたいので、 ややこしいですが、たとえば0.999991の場合は切り捨ててよく、 0.999....の場合のみ切り上げたいため、 この方法では該当する桁数を指定する必要があるのではないでしょうか。 よって、ToString処理をして、末尾の数字が9であって、すべて9かどうか、という確認処理を行う ということになるのではないかと思いますが、それが一般的な方法なのでしょうか?
showkit

2019/05/10 06:57

一般的ではないと思いますが・・・特殊なプログラムでは特殊なことを行うのはよくあることです。 というのは置いといて 桁数は指定しないと・・・無限に続く0を処理することはできませんが・・・ わたしが質問に対する認識を誤っていたらごめんなさい。
yokatone

2019/05/10 07:02

よくありがちな問題だろうし、何か都合のいい処理があるのではないか...と期待していましたが、 別にこの処理結果は間違いではないわけだし、やはりここは独自の実装が必要ですね。 ご回答ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問