コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2021年3月14日 >>> 2021年3月5日
link もっと後

2021年3月14日

GCCを調べる - memmoveのfoldingその2

目次: GCC

前回はmemmove() が030t.ccp1という最適化パスにて __builtin_memcpy() に置き換えられ、誤動作してしまうところまで見ました。今回はどこで置き換えられているのか、追いかけます。

入れ替えを行っている場所はgimple_fold_builtin_memory_op() という関数です。コールスタックは下記のようになります。今回はGCC 11というかHEADを使います。関数名は若干違いますがGCC 8くらいでも大筋は同じです。

memmove置き換え箇所までのコールスタック
do_ssa_ccp()  @tree-ssa-ccp.c
  ccp_finalize()
    substitute_and_fold_engine::substitute_and_fold()  @tree-ssa-propagate.c
      substitute_and_fold_dom_walker walker (CDI_DOMINATORS, this);
      walker.walk (ENTRY_BLOCK_PTR_FOR_FN (cfun));
        dom_walker::walk()  @domwalk.c
          substitute_and_fold_dom_walker::before_dom_children()  @tree-ssa-propagate.c
            fold_stmt()  @gimple-fold.c
              fold_stmt_1()  @gimple-fold.c
                gimple_fold_call()
                  gimple_fold_builtin()
                    gimple_fold_builtin_memory_op()

置き換えている箇所はgimple_fold_builtin_memory_op() 内に3箇所ありますが、今回のケースに該当するのは下記の部分です。

memmove置き換え箇所

// gcc/gcc/gimple-fold.c

static bool
gimple_fold_builtin_memory_op (gimple_stmt_iterator *gsi,
			       tree dest, tree src, enum built_in_function code)
{
  gimple *stmt = gsi_stmt (*gsi);
  tree lhs = gimple_call_lhs (stmt);
  tree len = gimple_call_arg (stmt, 2);
  location_t loc = gimple_location (stmt);

...

      if (code == BUILT_IN_MEMMOVE)
	{

...

	  /* If *src and *dest can't overlap, optimize into memcpy as well.  */
	  if (TREE_CODE (src) == ADDR_EXPR
	      && TREE_CODE (dest) == ADDR_EXPR)
	    {

              //...ここでチェックしている...

	      fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
	      if (!fn)
		return false;
	      gimple_call_set_fndecl (stmt, fn);    //★★ここでmemmove -> memcpyに置き換わる
	      gimple_call_set_arg (stmt, 0, dest);
	      gimple_call_set_arg (stmt, 1, src);
	      fold_stmt (gsi);
	      return true;
	    }

...

/*
 * 補足: gimple * の中身が見たいときは、下記のようにすると表示できます。
 *
 *   print_gimple_stmt (stdout, stmt, 0);       //HEAD
 *   print_gimple_stmt (stdout, stmt, 0, 0);    //GCC 8あたり
 */

検証プログラムはmemmove() の引数にポインタを渡しているので、TREE_CODE (src) == ADDR_EXPR && TREE_CODE (dest) == ADDR_EXPRの条件が成立します。

置き換えの前にはいくつかチェックがあって、memmove() をmemcpy() に置き換えるべきではないと判断したらreturn false; し、置き換え箇所に到達しない仕組みになっています。

memmove置き換え前のチェック箇所

// gcc/gcc/gimple-fold.c

	  /* If *src and *dest can't overlap, optimize into memcpy as well.  */
	  if (TREE_CODE (src) == ADDR_EXPR
	      && TREE_CODE (dest) == ADDR_EXPR)
	    {
	      tree src_base, dest_base, fn;
	      poly_int64 src_offset = 0, dest_offset = 0;
	      poly_uint64 maxsize;

              //★★src->expr.operands[0] を返す、srcvarはMEM_REFタイプ
	      srcvar = TREE_OPERAND (src, 0);
              //★★src_baseはVAR_DECLタイプ
              //★★src_offsetは2(検証用プログラムでmemmoveのsrcにs + 2を渡しているから)
	      src_base = get_addr_base_and_unit_offset (srcvar, &src_offset);
	      if (src_base == NULL)
		src_base = srcvar;

              //★★dest->expr.operands[0] を返す、destvarはMEM_REFタイプ
	      destvar = TREE_OPERAND (dest, 0);
              //★★dest_baseはVAR_DECLタイプ
              //★★dest_offsetは0(検証用プログラムでdestにsを渡しているから)
	      dest_base = get_addr_base_and_unit_offset (destvar,
							 &dest_offset);
	      if (dest_base == NULL)
		dest_base = destvar;
	      if (!poly_int_tree_p (len, &maxsize))
		maxsize = -1;
	      if (SSA_VAR_P (src_base)
		  && SSA_VAR_P (dest_base))    //★★この条件が成立する
		{
                  //★★volatileがあるとoperand_equal_p() がfalseになり
                  //★★memmoveがmemcpyに置き換えられてしまう
		  if (operand_equal_p (src_base, dest_base, 0)
		      && ranges_maybe_overlap_p (src_offset, maxsize,
						 dest_offset, maxsize))
		    return false;    //★★チェック1箇所目
		}
	      else if (TREE_CODE (src_base) == MEM_REF
		       && TREE_CODE (dest_base) == MEM_REF)
		{
		  if (! operand_equal_p (TREE_OPERAND (src_base, 0),
					 TREE_OPERAND (dest_base, 0), 0))
		    return false;
		  poly_offset_int full_src_offset
		    = mem_ref_offset (src_base) + src_offset;
		  poly_offset_int full_dest_offset
		    = mem_ref_offset (dest_base) + dest_offset;
		  if (ranges_maybe_overlap_p (full_src_offset, maxsize,
					      full_dest_offset, maxsize))
		    return false;    //★★チェック2箇所目
		}
	      else
		return false;    //★★チェック3箇所目

	      fn = builtin_decl_implicit (BUILT_IN_MEMCPY);
	      if (!fn)
		return false;
	      gimple_call_set_fndecl (stmt, fn);    //★★ここでmemmove -> memcpyに置き換わる
	      gimple_call_set_arg (stmt, 0, dest);
	      gimple_call_set_arg (stmt, 1, src);
	      fold_stmt (gsi);
	      return true;
...


// gcc/gcc/tree-dfa.c

/* Returns the base object and a constant BITS_PER_UNIT offset in *POFFSET that
   denotes the starting address of the memory access EXP.
   Returns NULL_TREE if the offset is not constant or any component
   is not BITS_PER_UNIT-aligned.  */

tree
get_addr_base_and_unit_offset (tree exp, poly_int64_pod *poffset)
{
  return get_addr_base_and_unit_offset_1 (exp, poffset, NULL);
}


/* Returns the base object and a constant BITS_PER_UNIT offset in *POFFSET that
   denotes the starting address of the memory access EXP.
   Returns NULL_TREE if the offset is not constant or any component
   is not BITS_PER_UNIT-aligned.
   VALUEIZE if non-NULL is used to valueize SSA names.  It should return
   its argument or a constant if the argument is known to be constant.  */

tree
get_addr_base_and_unit_offset_1 (tree exp, poly_int64_pod *poffset,
				 tree (*valueize) (tree))
{
  poly_int64 byte_offset = 0;

  /* Compute cumulative byte-offset for nested component-refs and array-refs,
     and find the ultimate containing object.  */
  while (1)
    {
      switch (TREE_CODE (exp))
	{

...

	case MEM_REF:
	  {
	    tree base = TREE_OPERAND (exp, 0);
	    if (valueize
		&& TREE_CODE (base) == SSA_NAME)
	      base = (*valueize) (base);

	    /* Hand back the decl for MEM[&decl, off].  */
	    if (TREE_CODE (base) == ADDR_EXPR)
	      {
		if (!integer_zerop (TREE_OPERAND (exp, 1)))
		  {
		    poly_offset_int off = mem_ref_offset (exp);
		    byte_offset += off.force_shwi ();
		  }
		exp = TREE_OPERAND (base, 0);
	      }
	    goto done;
	  }


// gcc/gcc/tree.h

#define SSA_VAR_P(DECL)							\
	(TREE_CODE (DECL) == VAR_DECL					\
	 || TREE_CODE (DECL) == PARM_DECL				\
	 || TREE_CODE (DECL) == RESULT_DECL				\
	 || TREE_CODE (DECL) == SSA_NAME)

領域が重複している場合は、1箇所目のチェックに引っかかります。不思議なことに、配列sの宣言からvolatileを外して動かすと、1箇所目のチェックに引っかかりますが、volatileを付けるとチェックに引っかからなくなります。

検証用のコード(部分、再掲)

int main(int argc, char *argv[])
{
	volatile char s[] = PRE STR;    //★★volatileを外すと __builtin_memcpy() への置き換えが発生しなくなる
	char *p = (char *)s;
	size_t sz_pre = strlen(PRE);
	size_t sz = strlen(p) - sz_pre + 1;

本来はvolatileがあろうがなかろうが1箇所目のチェックに引っかからないとおかしいですが、volatileがあるときは、なぜか1箇所目のチェックを通過してしまいます。

長くなってきたので、続きはまた次回。

編集者:すずき(2023/09/24 11:53)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2021年3月13日

GCCを調べる - memmoveのfoldingその1

目次: GCC

GCCのmemmoveに対する最適化とバグっぽい挙動についてのメモです。半年経ったら絶対忘れてわからなくなる自信があるので、書き残しておきます。

検証に使用するのは下記のプログラムです。"AB0123456789abcdefg" が格納された配列sから、同じ配列sに2文字だけずらしてmemmove() します。期待値は先頭2文字が消えた "0123456789abcdefg" です。このプログラムはmemmove() を使う必要があります。memcpy() はコピー元(src)とコピー先(dst)が重複したメモリ領域だと正常に動作しないことがあるからです。

重複したメモリ領域にmemmove() するプログラム

#include <stdio.h>
#include <string.h>

#define PRE "AB"
#define STR "0123456789abcdefg"
#define NOP __asm__ volatile("nop;")

int main(int argc, char *argv[])
{
	volatile char s[] = PRE STR;
	char *p = (char *)s;
	size_t sz_pre = strlen(PRE);
	size_t sz = strlen(p) - sz_pre + 1;

	NOP;
	memmove(p, p + sz_pre, sz);
	NOP; NOP;

	if (strcmp(p, STR) == 0) {
		printf("  OK: %s\n", p);
	} else {
		printf("  NG: %s\n", p);
	}
}
実行結果
$ gcc -O2 -Wall -g test.c

$ ./a.out
  NG: 01234567abcdefg

しかし、実行してみると期待値と一致しません。真ん中の "89" がどこかに消えました。また配列sの宣言からvolatileを削除すると、正常に動作するようになります。一体、何が起きているのでしょうか?

GCCのアグレッシブな最適化

GCCはある条件下でmemmove() を __builtin_memcpy() に置き換えるという最適化を行います。その様子を観察するために、オプション --dump-tree-allを付けて、GIMPLEのファイルを出力します。

GIMPLEを調べる
$ gcc -O2 -Wall -g test.c --dump-tree-all

$ grep -r 'memmove ' ./ | sort

./test.c.004t.original:  memmove ((void *) p, (const void *) (p + (sizetype) sz_pre), sz);
./test.c.005t.gimple:    memmove (p, _3, sz);
./test.c.007t.omplower:    memmove (p, _3, sz);
./test.c.008t.lower:  memmove (p, _3, sz);
./test.c.011t.eh:  memmove (p, _3, sz);
./test.c.013t.cfg:  memmove (p, _3, sz);
./test.c.015t.ompexp:  memmove (p, _3, sz);
./test.c.020t.fixup_cfg1:  memmove (p, _3, sz);
./test.c.021t.ssa:  memmove (p_8, _3, sz_10);
./test.c.023t.nothrow:  memmove (p_8, _3, sz_10);
./test.c.025t.fixup_cfg2:  memmove (p_8, _3, sz_10);
./test.c.026t.local-fnsummary1:  memmove (p_8, _3, sz_10);
./test.c.027t.einline:  memmove (p_8, _3, sz_10);
./test.c.028t.early_optimizations:  memmove (p_8, _3, sz_10);
./test.c.029t.objsz1:  memmove (p_8, _3, sz_10);

ファイルをmemmoveで検索すると、029t.objsz1を最後に出現しなくなっています。029t.objsz1の次である030t.cpp1を見て、memmoveがどうなったのか確かめます。

030t.ccp1

// test.c.030t.ccp1

;; Function main (main, funcdef_no=11, decl_uid=2555, cgraph_uid=12, symbol_order=11)

main (int argc, char * * argv)
{

...

  __asm__ __volatile__("nop;");
  # DEBUG BEGIN_STMT
  __builtin_memcpy (&s, &MEM <volatile char[20]> [(void *)&s + 2B], sz_10);
  # DEBUG BEGIN_STMT
  __asm__ __volatile__("nop;");
  # DEBUG BEGIN_STMT
  __asm__ __volatile__("nop;");

...

検証用プログラムではmemmoveと書いたはずの場所が、__builtin_memcpy() に変わっています。この __builtin_memcpy() は最終的にアセンブラに展開されます。あまり詳しく追っていませんが、末尾の余りバイトをコピーしてから、先頭から8バイトずつコピーするループが実行されるようで、srcとdestが重なっていると、正常に動作しません。

逆アセンブル

$ objdump -drS a.out

...

        NOP;
    1084:       90                      nop
        size_t sz = strlen(p) - sz_pre + 1;
    1085:       48 8d 50 ff             lea    -0x1(%rax),%rdx
        memmove(p, p + sz_pre, sz);
    1089:       48 8d 4c 24 02          lea    0x2(%rsp),%rcx
    108e:       48 83 fa 08             cmp    $0x8,%rdx
    1092:       73 52                   jae    10e6 <main+0x86>    ★コピーするコードの本体
    1094:       f6 c2 04                test   $0x4,%dl
    1097:       0f 85 85 00 00 00       jne    1122 <main+0xc2>
    109d:       48 85 d2                test   %rdx,%rdx
    10a0:       74 0f                   je     10b1 <main+0x51>
    10a2:       0f b6 01                movzbl (%rcx),%eax
    10a5:       88 45 00                mov    %al,0x0(%rbp)
    10a8:       f6 c2 02                test   $0x2,%dl
    10ab:       0f 85 80 00 00 00       jne    1131 <main+0xd1>
        NOP; NOP;
    10b1:       90                      nop
    10b2:       90                      nop

...

        memmove(p, p + sz_pre, sz);
    10e6:       48 8b 74 14 fa          mov    -0x6(%rsp,%rdx,1),%rsi
    10eb:       48 83 e8 02             sub    $0x2,%rax
    10ef:       48 89 74 14 f8          mov    %rsi,-0x8(%rsp,%rdx,1)
    10f4:       48 83 f8 08             cmp    $0x8,%rax
    10f8:       72 b7                   jb     10b1 <main+0x51>
    10fa:       48 83 e0 f8             and    $0xfffffffffffffff8,%rax
    10fe:       31 d2                   xor    %edx,%edx
    1100:       48 8b 34 11             mov    (%rcx,%rdx,1),%rsi      ★この辺りがコピーのためのループ
    1104:       48 89 74 15 00          mov    %rsi,0x0(%rbp,%rdx,1)
    1109:       48 83 c2 08             add    $0x8,%rdx
    110d:       48 39 c2                cmp    %rax,%rdx
    1110:       72 ee                   jb     1100 <main+0xa0>
    1112:       eb 9d                   jmp    10b1 <main+0x51>
                printf("  OK: %s\n", p);
    1114:       48 8d 3d fb 0e 00 00    lea    0xefb(%rip),%rdi        # 2016 <_IO_stdin_used+0x16>
    111b:       e8 20 ff ff ff          callq  1040 <printf@plt>
    1120:       eb bc                   jmp    10de <main+0x7e>
        memmove(p, p + sz_pre, sz);
    1122:       8b 01                   mov    (%rcx),%eax
    1124:       89 45 00                mov    %eax,0x0(%rbp)
    1127:       8b 44 11 fc             mov    -0x4(%rcx,%rdx,1),%eax
    112b:       89 44 15 fc             mov    %eax,-0x4(%rbp,%rdx,1)
    112f:       eb 80                   jmp    10b1 <main+0x51>
    1131:       0f b7 44 11 fe          movzwl -0x2(%rcx,%rdx,1),%eax
    1136:       66 89 44 15 fe          mov    %ax,-0x2(%rbp,%rdx,1)
    113b:       e9 71 ff ff ff          jmpq   10b1 <main+0x51>

長くなってきたので、また次回。

編集者:すずき(2023/09/24 11:53)

コメント一覧

  • emkさん(2024/03/05 12:44)
    キャストでvolatileを外してアクセスしている時点で未定義動作なので、その後の調査には何の意味もありませんね。GCCではなくこのプログラムがバグっているだけです。
  • すずきさん(2024/03/05 15:13)
    あー、このプログラムがまずいんですね。ご指摘ありがとうございます。
open/close この記事にコメントする



2021年3月12日

浮動小数点数の丸め方

目次: C言語とlibc

C言語の規格では浮動小数点の丸めモードが4つ定義されています。実行中に変更することができ、fesetround() 関数で指定します。

ついUPWARD = 切り上げ、DOWNWARD = 切り捨て、と説明したくなりますが、TOWARDZEROと区別が付きませんし、正の数はまだしも、負の数を考えたとき混乱します。やや意味はわかりにくいですが、誤解のない説明をすると、こんな感じです。

FE_DOWNWARD
マイナス無限大方向への丸め、-11.5, -10.5, 10.5, 11.5を整数にすると、-12, -11, 10, 11
FE_TONEAREST
最も近い数への丸め
FE_TOWARDZERO
ゼロ方向への丸め、-11.5, -10.5, 10.5, 11.5を整数にすると、-11, -10, 10, 11
FE_UPWARD
プラス無限大方向への丸め、-11.5, -10.5, 10.5, 11.5を整数にすると、-11, -10, 11, 12

DOWNWARD, TOWARDZERO, UPWARDは他に解釈の余地がありませんが、TONEARESTはど真ん中の数が来たときに、扱いに困ります。IEEE 754では2つの方式が定義されているようです。

ties to even
偶数方向に丸める、10.5, 11.5を整数にすると10.0, 12.0になる
ties away from zero
ゼロから遠い方に丸める、10.5, 11.5を整数にすると11.0, 12.0になる、四捨五入に近い

説明するより実際に動かしたほうがわかりやすいでしょう。

丸めモードを実感

丸めモードの動作を確認するプログラムを書きます。8388610という数値はfloatの仮数部24bitを使い切った値となっています。1.0の増減は表現できますが、1.0より小さい値の増減はビットが足りないので表せないです。

8388610に対し1.0より小さい値(今回は0.25, 0.5, 0.75の3つを選びました)を加減算すれば、結果を正確に表現できないので、必ず丸め処理が行われます。丸めモードによる演算結果の違いを見るには丁度よいですね。

浮動小数点の丸めモードの動作を確認するプログラム

#include <stdio.h>
#include <fenv.h>
#include <float.h>

#define BASE_EVEN    8388610.0
#define BASE_ODD     8388611.0

union n_f {
	int n;
	float f;
};

static inline void test(void)
{
	union n_f a, b, c;

	a.f = BASE_EVEN;
	b.f = 0.25;
	c.f = a.f + b.f;
	printf("   %.2f+%.2f =  %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
	b.f = 0.5;
	c.f = a.f + b.f;
	printf("   %.2f+%.2f =  %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
	b.f = 0.75;
	c.f = a.f + b.f;
	printf("   %.2f+%.2f =  %.2f 0x%08x\n", a.f, b.f, c.f, c.n);

	a.f = BASE_ODD;
	b.f = 0.25;
	c.f = a.f + b.f;
	printf("   %.2f+%.2f =  %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
	b.f = 0.5;
	c.f = a.f + b.f;
	printf("   %.2f+%.2f =  %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
	b.f = 0.75;
	c.f = a.f + b.f;
	printf("   %.2f+%.2f =  %.2f 0x%08x\n", a.f, b.f, c.f, c.n);

	a.f = -BASE_EVEN;
	b.f = 0.25;
	c.f = a.f - b.f;
	printf("  %.2f-%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
	b.f = 0.5;
	c.f = a.f - b.f;
	printf("  %.2f-%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
	b.f = 0.75;
	c.f = a.f - b.f;
	printf("  %.2f-%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);

	a.f = -BASE_ODD;
	b.f = 0.25;
	c.f = a.f - b.f;
	printf("  %.2f-%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
	b.f = 0.5;
	c.f = a.f - b.f;
	printf("  %.2f-%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
	b.f = 0.75;
	c.f = a.f - b.f;
	printf("  %.2f-%.2f = %.2f 0x%08x\n", a.f, b.f, c.f, c.n);
}

int main(int argc, char *argv[])
{
	printf("DOWNWARD\n");
	fesetround(FE_DOWNWARD);
	test();

	printf("TONEAREST\n");
	fesetround(FE_TONEAREST);
	test();

	printf("TOWARDZERO\n");
	fesetround(FE_TOWARDZERO);
	test();

	printf("UPWARD\n");
	fesetround(FE_UPWARD);
	test();

	return 0;
}

ところが、コンパイルして実行すると奇妙な結果が得られます。全てTONEARESTと同じ結果になってしまいます。

rounding-mathなし
$ gcc -Wall -g -O2 a.c -lm

$ ./a.out

DOWNWARD
   8388610.00+0.25 =  8388610.00 0x4b000002    ★TONEARESTと同じになっている
   8388610.00+0.50 =  8388610.00 0x4b000002
   8388610.00+0.75 =  8388611.00 0x4b000003
   8388611.00+0.25 =  8388611.00 0x4b000003
   8388611.00+0.50 =  8388612.00 0x4b000004
   8388611.00+0.75 =  8388612.00 0x4b000004
  -8388610.00-0.25 = -8388610.00 0xcb000002
  -8388610.00-0.50 = -8388610.00 0xcb000002
  -8388610.00-0.75 = -8388611.00 0xcb000003
  -8388611.00-0.25 = -8388611.00 0xcb000003
  -8388611.00-0.50 = -8388612.00 0xcb000004
  -8388611.00-0.75 = -8388612.00 0xcb000004
TONEAREST
   8388610.00+0.25 =  8388610.00 0x4b000002
   8388610.00+0.50 =  8388610.00 0x4b000002
   8388610.00+0.75 =  8388611.00 0x4b000003
   8388611.00+0.25 =  8388611.00 0x4b000003
   8388611.00+0.50 =  8388612.00 0x4b000004
   8388611.00+0.75 =  8388612.00 0x4b000004
  -8388610.00-0.25 = -8388610.00 0xcb000002
  -8388610.00-0.50 = -8388610.00 0xcb000002
  -8388610.00-0.75 = -8388611.00 0xcb000003
  -8388611.00-0.25 = -8388611.00 0xcb000003
  -8388611.00-0.50 = -8388612.00 0xcb000004
  -8388611.00-0.75 = -8388612.00 0xcb000004
TOWARDZERO
   8388610.00+0.25 =  8388610.00 0x4b000002    ★TONEARESTと同じになっている
   8388610.00+0.50 =  8388610.00 0x4b000002
   8388610.00+0.75 =  8388611.00 0x4b000003
   8388611.00+0.25 =  8388611.00 0x4b000003
   8388611.00+0.50 =  8388612.00 0x4b000004
   8388611.00+0.75 =  8388612.00 0x4b000004
  -8388610.00-0.25 = -8388610.00 0xcb000002
  -8388610.00-0.50 = -8388610.00 0xcb000002
  -8388610.00-0.75 = -8388611.00 0xcb000003
  -8388611.00-0.25 = -8388611.00 0xcb000003
  -8388611.00-0.50 = -8388612.00 0xcb000004
  -8388611.00-0.75 = -8388612.00 0xcb000004
UPWARD
   8388610.00+0.25 =  8388610.00 0x4b000002    ★TONEARESTと同じになっている
   8388610.00+0.50 =  8388610.00 0x4b000002
   8388610.00+0.75 =  8388611.00 0x4b000003
   8388611.00+0.25 =  8388611.00 0x4b000003
   8388611.00+0.50 =  8388612.00 0x4b000004
   8388611.00+0.75 =  8388612.00 0x4b000004
  -8388610.00-0.25 = -8388610.00 0xcb000002
  -8388610.00-0.50 = -8388610.00 0xcb000002
  -8388610.00-0.75 = -8388611.00 0xcb000003
  -8388611.00-0.25 = -8388611.00 0xcb000003
  -8388611.00-0.50 = -8388612.00 0xcb000004
  -8388611.00-0.75 = -8388612.00 0xcb000004

これはGCCの最適化による影響です。O1以上の最適化を行うと、演算時の丸めモードをTONEARESTと仮定し、コンパイル時に計算できる値を事前に計算する、という最適化が行われます。

この最適化が都合が良い場合もありますが、今回のようなプログラムでは丸めモードをTONEAREST以外に変更しているので、勝手にTONEARESTを仮定してはいけません。GCCの場合-frounding-mathというオプションを付けると、デフォルトの丸めモードを仮定した最適化をやめることができます。

rounding-mathあり
$ gcc -Wall -g -O2 a.c -lm -frounding-math

$ ./a.out

DOWNWARD    ★マイナス無限大方向に丸める
   8388610.00+0.25 =  8388610.00 0x4b000002    ★10.25 ->  10.00
   8388610.00+0.50 =  8388610.00 0x4b000002    ★10.50 ->  10.00
   8388610.00+0.75 =  8388610.00 0x4b000002    ★10.75 ->  10.00
   8388611.00+0.25 =  8388611.00 0x4b000003    ★11.25 ->  11.00
   8388611.00+0.50 =  8388611.00 0x4b000003    ★11.50 ->  11.00
   8388611.00+0.75 =  8388611.00 0x4b000003    ★11.75 ->  11.00
  -8388610.00-0.25 = -8388611.00 0xcb000003    ★-10.25 -> -11.00
  -8388610.00-0.50 = -8388611.00 0xcb000003    ★-10.50 -> -11.00
  -8388610.00-0.75 = -8388611.00 0xcb000003    ★-10.75 -> -11.00
  -8388611.00-0.25 = -8388612.00 0xcb000004    ★-11.25 -> -12.00
  -8388611.00-0.50 = -8388612.00 0xcb000004    ★-11.50 -> -12.00
  -8388611.00-0.75 = -8388612.00 0xcb000004    ★-11.75 -> -12.00
TONEAREST    ★最近接数に丸める、真ん中(0.5)は偶数側に丸める
   8388610.00+0.25 =  8388610.00 0x4b000002    ★10.25 ->  10.00
   8388610.00+0.50 =  8388610.00 0x4b000002    ★10.50 ->  10.00
   8388610.00+0.75 =  8388611.00 0x4b000003    ★10.75 ->  11.00
   8388611.00+0.25 =  8388611.00 0x4b000003    ★11.25 ->  11.00
   8388611.00+0.50 =  8388612.00 0x4b000004    ★11.50 ->  12.00
   8388611.00+0.75 =  8388612.00 0x4b000004    ★11.75 ->  12.00
  -8388610.00-0.25 = -8388610.00 0xcb000002    ★-10.25 -> -10.00
  -8388610.00-0.50 = -8388610.00 0xcb000002    ★-10.50 -> -10.00
  -8388610.00-0.75 = -8388611.00 0xcb000003    ★-10.75 -> -11.00
  -8388611.00-0.25 = -8388611.00 0xcb000003    ★-11.25 -> -11.00
  -8388611.00-0.50 = -8388612.00 0xcb000004    ★-11.50 -> -12.00
  -8388611.00-0.75 = -8388612.00 0xcb000004    ★-11.75 -> -12.00
TOWARDZERO    ★ゼロ方向に丸める
   8388610.00+0.25 =  8388610.00 0x4b000002    ★10.25 ->  10.00
   8388610.00+0.50 =  8388610.00 0x4b000002    ★10.50 ->  10.00
   8388610.00+0.75 =  8388610.00 0x4b000002    ★10.75 ->  10.00
   8388611.00+0.25 =  8388611.00 0x4b000003    ★11.25 ->  11.00
   8388611.00+0.50 =  8388611.00 0x4b000003    ★11.50 ->  11.00
   8388611.00+0.75 =  8388611.00 0x4b000003    ★11.75 ->  11.00
  -8388610.00-0.25 = -8388610.00 0xcb000002    ★-10.25 -> -10.00
  -8388610.00-0.50 = -8388610.00 0xcb000002    ★-10.50 -> -10.00
  -8388610.00-0.75 = -8388610.00 0xcb000002    ★-10.75 -> -10.00
  -8388611.00-0.25 = -8388611.00 0xcb000003    ★-11.25 -> -11.00
  -8388611.00-0.50 = -8388611.00 0xcb000003    ★-11.50 -> -11.00
  -8388611.00-0.75 = -8388611.00 0xcb000003    ★-11.75 -> -11.00
UPWARD    ★プラス無限大方向に丸める
   8388610.00+0.25 =  8388611.00 0x4b000003    ★10.25 ->  11.00
   8388610.00+0.50 =  8388611.00 0x4b000003    ★10.50 ->  11.00
   8388610.00+0.75 =  8388611.00 0x4b000003    ★10.75 ->  11.00
   8388611.00+0.25 =  8388612.00 0x4b000004    ★11.25 ->  12.00
   8388611.00+0.50 =  8388612.00 0x4b000004    ★11.50 ->  12.00
   8388611.00+0.75 =  8388612.00 0x4b000004    ★11.75 ->  12.00
  -8388610.00-0.25 = -8388610.00 0xcb000002    ★-10.25 -> -10.00
  -8388610.00-0.50 = -8388610.00 0xcb000002    ★-10.50 -> -10.00
  -8388610.00-0.75 = -8388610.00 0xcb000002    ★-10.75 -> -10.00
  -8388611.00-0.25 = -8388611.00 0xcb000003    ★-11.25 -> -11.00
  -8388611.00-0.50 = -8388611.00 0xcb000003    ★-11.50 -> -11.00
  -8388611.00-0.75 = -8388611.00 0xcb000003    ★-11.75 -> -11.00

丸めモードの切り替えが正常に働いている様子がわかります。またTONEARESTの丸めモードにした場合、ties to evenであることも観察できます。

C言語の規格は何と言っている?

C99のfinal draftをざっと見た限りでは、ties to evenかties away from zeroか、明確に書かれていないように見えました。規格的にはどちらなんでしょうね?実装依存なのかなあ?

編集者:すずき(2023/02/04 20:17)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2021年3月7日

電源タップの雷ガード

在宅勤務環境を整えようと、電源タップを物色していました。電源タップを見ていると大体3つに分類できて、

  • 普通のタップ
  • スイッチ付き
  • 雷ガード(雷サージ保護)付き

前者2つはわかりやすいです。3つ目の雷ガードとは何者でしょう?前から不思議だったので、軽く調べました。

雷ガードの定義

雷ガードは雷の直撃を防ぐわけではなく(それは避雷針の仕事)、落雷したとき電線に混入する1〜20kV程度のサージ電流を防ぐ仕組みのようです。パナソニックのタップは0.8kV, 1.2kV、LED照明器具は15kV、サンワサプライは15kV, 20kVを最大電圧とした製品を見つけました。

保護の仕組みですが、サンワサプライは電源系にはバリスタ(Varistor, Variable Resistor)を使っていると書いています。パナソニックのサイトも雷サージ対策にバリスタとあるので、バリスタがポピュラーな仕組みなのでしょう。

参考: 【EMC対策】雷サージ対策部品 "バリスタ" の落とし穴 - パナソニック

バリスタはいわゆる静電気(ESD, Electric Static Discharge)対策で使われる素子です。ツェナーダイオードを2つ逆接続したようなV-I特性で、電圧の正負を気にせず、とにかく高い電圧に対し低い抵抗値を持ちます。

参考: チップバリスタ - 電子デバイス・産業用機器 - パナソニック

バリスタと本体の電子機器を並列に接続します。回路に高い電圧が掛かると、バリスタ側の抵抗値が急激に下がり、バリスタ側に電流が流れます。バリスタ側に電流が逃げることで、本体の電子機器は高い電圧を食らわずに済みサージ電流から保護される仕組みです。

ESDの場合は素子が壊れないように十分な耐圧のバリスタを選定すると思いますが、雷サージの電圧は最大何Vかわかりません。時にバリスタが耐えられない電圧が掛かり、バリスタが故障します。特にバリスタがショートモードで故障し、ユーザーが気づかずに使い続けた場合、素子が加熱し火災が発生する恐れがあります。

これは実際に起きた事故で、10年前にNOATEKの雷ガード製品がバリスタの加熱&火災を起こし150万個もリコールされました。NITEの解説を見る限り、この製品は「雷ガード=バリスタ入れればOK」という甘い設計だったため、ショートモードで故障した後、加熱を検知できず火災に至ったようです。

雷を防いだら、火事になりました、なんて製品は全く笑えません。安全な設計というのは難しいですね。

こんなことばかり調べているから、全然買い物が進みません。在宅勤務環境が整うのはまだ先のようです……。

メモ: 技術系の話はFacebookから転記しておくことにした。リンク、情報追加、加筆。

編集者:すずき(2021/03/08 03:23)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2021年3月6日

気に入るマウスはどれ?

手に合うワイヤレスマウスを探し続け、高級製品、小さい製品、お手ごろ製品と買いまくり、一時は家に5個くらいワイヤレスマウスがありました。

日記から遍歴を辿ってみたところ、少なくとも下記の製品を持っていたようです。

  • 2004年12月: Logitech Optical Mouse USB M-UV96
  • 2007年1月: Logicool Nano Cordless Laser Mouse V450
  • 2010年2月: Logicool Cordless Laser Mouse MX1100
  • 2011年12月: Logicool Wireless Mouse M510
  • 2012年4月: Logicool Perfomance MX M950
  • 2013年10月: Microsoft Wireless Mouse 1000
  • 2014年8月: Logicool Perfomance MX M950(2個目)
  • 2017年5月: エレコムEX-G Ultimate Laser Mouse M-XGL20DLBK
  • 2017年5月: Logicool Wireless Mouse M560
  • 2017年12月: Logicool Marathon Mouse M705
  • 2021年1月: Microsoft Basic Optical Mouse

全部で11個です。こんなに買っていたとは思いませんでした……。

最終的に辿り着いた先は

色々試して最終的に辿り着いたのは、Microsoftの1000円の有線マウス(Basic Optical Mouse)でした。このマウスは中身がほぼ空っぽでめちゃくちゃ軽いです。

はい、何ですか?ワイヤレスじゃないって?そうですね。ワイヤレスマウスはどうしても重くて、手首が疲れてしまいダメでした。

小さいワイヤレスマウスなら軽いですが、手の大きさと合わずやっぱり手が疲れてしまいます。どうもマウスの持ち方(親指と小指で挟むように持つ)が、小さいマウス、ワイヤレスマウスと合わないみたいです。

という訳でワイヤレスマウスは諦めました。有線マウスは安くて軽くて最高です!

メモ: 技術系の話はFacebookから転記しておくことにした。マウス遍歴を追加。

編集者:すずき(2022/01/21 18:42)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2021年3月14日 >>> 2021年3月5日
link もっと後

管理用メニュー

link 記事を新規作成

<2021>
<<<03>>>
-123456
78910111213
14151617181920
21222324252627
28293031---

最近のコメント5件

  • link 20年6月19日
    すずきさん (04/06 22:54)
    「ディレクトリを予め作成しておけば良いです...」
  • link 20年6月19日
    斎藤さん (04/06 16:25)
    「「Preferencesというメニューか...」
  • link 21年3月13日
    すずきさん (03/05 15:13)
    「あー、このプログラムがまずいんですね。ご...」
  • link 21年3月13日
    emkさん (03/05 12:44)
    「キャストでvolatileを外してアクセ...」
  • link 24年1月24日
    すずきさん (02/19 18:37)
    「簡単にできる方法はPowerShellの...」

最近の記事3件

  • link 24年4月17日
    すずき (04/18 22:44)
    「[VSCodeとMarkdownとPlantUMLのローカルサーバー] 目次: LinuxVSCodeのPlantUML Ex...」
  • link 23年4月10日
    すずき (04/18 22:30)
    「[Linux - まとめリンク] 目次: Linuxカーネル、ドライバ関連。Linuxのstruct pageって何?Linu...」
  • link 20年2月22日
    すずき (04/17 02:22)
    「[Zephyr - まとめリンク] 目次: Zephyr導入、ブート周りHello! Zephyr OS!!Hello! Ze...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 04/18 22:44