HideMath.dllNo.09081
いちもんじ さん 19/10/04 11:05
 
下記のマクロを実行すると
//浮動小数点数
  setfloatmode 1;
  loaddll hidemarudir  + "\\hidemath.dll";
  if( !result ) {
      message "hidemath.dllのロードに失敗しました。";
      endmacro;
  }

  #a = 10;
  #X = 29700 / (1 + #a / 100) * #a / 100;
  message "1: " + str(#X);

  #X = dllfunc ( "Floor", #X );
  message "2: " + str(#X);

  #Y = 29700 - #X;

  message "Y= " + str(#Y);
endmacro;


message "1: " + str(#X); のステップ時には「2700」ですが
message "2: " + str(#X); のステップ時には「2699」となります。

Floorは少数点数以下切り捨てという理解で、同じになるのでは?と思うのですが。

HideMath.dllは、Ver.8.90β1(浮動小数点バージョン)に同梱されているものを使
っています。



[ ]
RE:09081 HideMath.dllNo.09082
秀丸担当 さん 19/10/04 16:14
 

まず浮動小数点数の計算は、どうしても誤差が出る性質になっていて、ほとんどのプ
ログラミング言語で誤差が出てしまいます。
Google検索で「浮動小数点数 誤差」とかで検索すると、いろいろ情報が得らえると
思います。

「1:」の時点では、正確には2699.9999999999995くらいの値になっていて、切り捨て
ると2699になるようです。
回避するには、+0.5してから切り捨てして、四捨五入するといいと思います。

hidemath.dllのFormat関数を使うと、sprintfの書式に従った詳細な文字列化ができ
ます。
str()で文字列化する場合は、sprintfの"%1.15lG"の書式に相当する、自動的な都合
のいい文字列化になっています。

精度の具合を確かめる例
  setfloatmode 1;
  loaddll hidemarudir  + "\\hidemath.dll";
  if( !result ) {
      message "hidemath.dllのロードに失敗しました。";
      endmacro;
  }
  #a = 10;
  #X = 29700 / (1 + #a / 100) * #a / 100;
  message dllfuncstr( "Format", "%.0lf", #X );
  message dllfuncstr( "Format", "%.5lf", #X );
  message dllfuncstr( "Format", "%.10lf", #X );
  message dllfuncstr( "Format", "%.11lf", #X );
  message dllfuncstr( "Format", "%.12lf", #X );
  message dllfuncstr( "Format", "%.13lf", #X );
  message dllfuncstr( "Format", "%.14lf", #X );
  message dllfuncstr( "Format", "%.15lf", #X );
  endmacro;

他のプログラミング言語でもだいたい同じで、拡張子.jsで保存してWScriptで実行し
て確かめる例
  a = 10;
  X = 29700 / (1 + a / 100) * a / 100;
  WScript.Echo( X.toFixed(0));
  WScript.Echo( X.toFixed(5));
  WScript.Echo( X.toFixed(10));
  WScript.Echo( X.toFixed(11));
  WScript.Echo( X.toFixed(12));
  WScript.Echo( X.toFixed(13));
  WScript.Echo( X.toFixed(14));
  WScript.Echo( X.toFixed(15));

[ ]
RE:09082 HideMath.dllNo.09083
いちもんじ さん 19/10/04 18:39
 
>「1:」の時点では、正確には2699.9999999999995くらいの値になっていて、切り捨
>てると2699になるようです。
>回避するには、+0.5してから切り捨てして、四捨五入するといいと思います。

ご教示いただいた方法で回避したいと思います。

確認なのですが。
今回、端数処理で行いたいのは「切り捨て」です。
hidemath.dllを使わず、整数バージョンの場合は、自動的に「切り捨て」になる...
という理解でよろしいでしょうか?

[ ]
RE:09083 HideMath.dllNo.09084
いちもんじ さん 19/10/04 19:53
 
>確認なのですが。
>今回、端数処理で行いたいのは「切り捨て」です。
>hidemath.dllを使わず、整数バージョンの場合は、自動的に「切り捨て」になる...
>という理解でよろしいでしょうか?

#a = 10;
#b = 29700;

#X = #b / (1 + #a / 100) * #a / 100;
$X = dllfuncstr( "Format", "%.02f", #X );
$X = leftstr( $X,strstr($X,"."));
message "#X:" + $X;

もしも、「自動的に切り捨て」でなければですが。
 小数点以下(もしくは右側)の文字列を除く
という方法も「アリ」?

[ ]
RE:09083 HideMath.dllNo.09085
でるもんたいいじま さん 19/10/04 20:31
 
でるもんた・いいじま@秀丸愛用者です。

> 確認なのですが。
> 今回、端数処理で行いたいのは「切り捨て」です。

この誤差が出るのは「有効桁数(通常は10進法で15桁ちょっとです)のうち
最後の2桁くらい」なので、明らかにその誤差の範囲より振れ幅の大きい値を
加減してやるのが一般的です。
具体的には、今回くらいの値なら、切り捨てるなら 0.000001 くらいの適当な
値を足してFloor、逆に切り上げるならやはり適当な値、0.000001くらいを
引いてCeilを使います。

ちなみに、「もし、この計算が万一にも新たな誤差を生んだら?」という
心配がある場合、たとえば間違いの許されない金融関係の計算(今回の元々の
例題が消費税の計算なのでアレですが…)では、整数演算のみを使って
「厳密にその業界での計算ルールに合致するように」と意識しながら計算します。
データベース関係の言語には、「整数型」「浮動小数点型」とは別個に
「通貨型」「日時型」といった型があります。

☆ ☆ ☆

> hidemath.dllを使わず、整数バージョンの場合は、自動的に「切り捨て」に
> なる...という理解でよろしいでしょうか?

はい。プラスの値のみの計算では単純に「切り捨て」と考えて結構です。

ただしマイナスの値が絡む場合、そもそも「切り捨て」という言葉の定義を
明確にするところから始めないといけません。
「元の値よりも数直線上で左側に移動する(−∞の方向に丸める)」のか、
それとも「絶対値を小さくする(ゼロの方向に丸める)」のかです。

いま確認したところ、秀丸の整数演算では後者の動作になります。
つまり、たとえば11/(-9)は-2ではなく、-1になります。
浮動小数点版で同じ割り算をして、それをFloorすると-2になるはずです。

☆ ☆ ☆

以下はおまけです。

どんな言語でも、floorと書いてあれば「−∞の方向に丸める」です。
同様に、ceilまたはceilingと書いてあれば「+∞の方向に丸める」です。

逆に、
・integer
・round
・truncate
といった単語を使っている場合はどういう挙動だかわかりませんので、
その都度テストコードを書いて実際に確認して、さらに、言語仕様の
文書にそのルールが明記されているかどうかを確認してください。

ちなみにJavaScriptだと、数値 a に対して a.toInteger() というメソッドが
Firefoxに一時期だけ存在したけれど現在は廃止、Math.round(a) は四捨五入、
Math.trunc(a)【IEやWSHでは未サポート】はゼロ方向に丸めます。

ではでは。

[ ]
RE:09084 HideMath.dllNo.09086
でるもんたいいじま さん 19/10/04 21:29
 
でるもんた・いいじまです。行き違いになりました。

> もしも、「自動的に切り捨て」でなければですが。
>  小数点以下(もしくは右側)の文字列を除く
> という方法も「アリ」?

はい。「切り捨て」という言葉の意味が「ゼロの方向に丸める」つまり
「絶対値を元の値より小さくして整数にする」ということであれば
そういう方法もアリです。

> #a = 10;
> #b = 29700;
>
> #X = #b / (1 + #a / 100) * #a / 100;
> $X = dllfuncstr( "Format", "%.02f", #X );
> $X = leftstr( $X,strstr($X,"."));
> message "#X:" + $X;

整数バージョンではこのコードはダメです。
まず、#a/100 はゼロになります。
そうすると、#b/(1+0/100) は当然 #b の値そのものです。
そのあとの計算は「先に#aをかけてから、100で割る」ですので、
厳密にその順序に従って計算していくと、#X は2970になります。

ご希望通りの計算をしてくれる式は #X = #b*#a/(100+#a); となります。
「1.10で割って本体価格を出して、それに0.10をかける」ではなくて、
10/110という「分母部分・分子部分とも整数になっている分数」を
かければいいのです。当然、途中で切り捨てが起きないように、
分母で割るよりも先に分子をかけます。
(数値が大きくなりすぎる場合は途中で約分しますが、
 今回の場合はその心配は要らないでしょう。)

その次の dllfuncstr() は、整数版とhidemath.dllを強引に組み合わせた
場合にはそもそも「浮動小数点版ではありません」というエラーが
表示されるかもしれませんし、もし一見して正常な値が返ってきた
ように見える場合でも、実際に期待通りの値が返ることはないはずです。
(唯一の例外が、64bit版の秀丸で #X=0 のときかな?)

☆ ☆ ☆

もうひとつ、「プログラミング全般における作法」みたいなものですが、
気になったので参考までに。

もし万一にも $X が "2700"; みたいに小数点のない文字列だった場合
(今回は"%.02f" なのでそれはありえないのですが)、strstr()は-1を
返します。

そうなると、秀丸のleftstr()は空文字列を返しますし、Excelの数式で
同様の事態になるとエラー値が返ります。言語によっては当該アプリが
その時点で強制終了することもありますし、OSを巻き込んで暴走する
こともあります。さらに最悪のケースとして、どこの誰だか知らない
人にコンピュータを乗っ取られて犯罪のための踏み台として使われる、
という事態も、世界中で毎日のように発生しています。

このように、一般論としてstrstr、instr、findといった関数の返り値を
ノーチェックで次の計算に使うのは危険なので、この部分の書き方は
「個人的には」気持ち悪いと感じます。

なので最低でも「%.02fだから大丈夫」といった趣旨のコメントを入れて
おきたいところですし、もし私が最初から書くとすれば、無意味だとは
分かっていてもstrstrの返り値が0以上になっているかどうか確認します。
これによる性能低下は微々たるものですので、よほど高密度な反復計算の
最中だとか、よほどメモリ空間が狭い(たとえば全部で1KBしかない)
とかでない限り、私個人の意見としては、保険料として超激安の部類に
入ると考えています。

あるいは、動画・音声など「外界から入ってきた信号」を扱う場面では、
速度低下を覚悟の上でこの手のチェックを徹底しなければならないという
ケースも当然あります。

ではでは。また与太話が過ぎましたのでこのへんで。

[ ]
RE:09086 HideMath.dllNo.09087
でるもんたいいじま さん 19/10/04 21:56
 
でるもんた・いいじまです。

スミマセン追記。何度も連投すみません。

> > もしも、「自動的に切り捨て」でなければですが。
> >  小数点以下(もしくは右側)の文字列を除く
> > という方法も「アリ」?
>
> はい。「切り捨て」という言葉の意味が「ゼロの方向に丸める」つまり
> 「絶対値を元の値より小さくして整数にする」ということであれば
> そういう方法もアリです。

これですが、数値処理で文字列を経由すると、速度が大幅に低下します。

#たとえば 2699.9999999999995 という数値は通常64ビットつまり
#8バイトで、CPUの1サイクルで1回の計算ができますが、文字列の
#"2699.9999999999995" は単純に数えても18文字で、メモリからの
#読み取り作業に最低18サイクル、それに加えて、ループを回すために
#追加で20〜40サイクルくらいかかるはずです。

なので、浮動小数点数を「ゼロの方向に丸めたい」場合には、
通常はプラスとマイナスとで場合分けして、プラスならfloor、
マイナスならceilを使います。

もちろん、今回のような誤差の問題は別途対策しなければいけません。

#文字列 "2699.9999999999995" の小数点以下を取り除く方法でも、
#やはり計算結果は2699にしかなりません。
#人間の都合のいいように自動で2700にはなりません。

以上、蛇足でした。

[ ]
RE:09087 HideMath.dllNo.09089
いちもんじ さん 19/10/10 10:48
 
でるもんた・いいじま様

お礼が遅くなり申し訳ありません。
丁寧な解説をありがとうございました。

秀丸マクロで一時的に消費税の計算をすることになり、端数の差異に苦戦していたと
ころでした。

助かりました。

[ ]