目次: GCC
前回はmemmove() が030t.ccp1という最適化パスにて __builtin_memcpy() に置き換えられ、誤動作してしまうところまで見ました。今回はどこで置き換えられているのか、追いかけます。
入れ替えを行っている場所はgimple_fold_builtin_memory_op() という関数です。コールスタックは下記のようになります。今回はGCC 11というかHEADを使います。関数名は若干違いますがGCC 8くらいでも大筋は同じです。
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箇所ありますが、今回のケースに該当するのは下記の部分です。
// 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; し、置き換え箇所に到達しない仕組みになっています。
// 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箇所目のチェックを通過してしまいます。
長くなってきたので、続きはまた次回。
目次: GCC
GCCのmemmoveに対する最適化とバグっぽい挙動についてのメモです。半年経ったら絶対忘れてわからなくなる自信があるので、書き残しておきます。
検証に使用するのは下記のプログラムです。"AB0123456789abcdefg" が格納された配列sから、同じ配列sに2文字だけずらしてmemmove() します。期待値は先頭2文字が消えた "0123456789abcdefg" です。このプログラムはmemmove() を使う必要があります。memcpy() はコピー元(src)とコピー先(dst)が重複したメモリ領域だと正常に動作しないことがあるからです。
#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はある条件下でmemmove() を __builtin_memcpy() に置き換えるという最適化を行います。その様子を観察するために、オプション --dump-tree-allを付けて、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がどうなったのか確かめます。
// 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>
長くなってきたので、また次回。
目次: C言語とlibc
C言語の規格では浮動小数点の丸めモードが4つ定義されています。実行中に変更することができ、fesetround() 関数で指定します。
ついUPWARD = 切り上げ、DOWNWARD = 切り捨て、と説明したくなりますが、TOWARDZEROと区別が付きませんし、正の数はまだしも、負の数を考えたとき混乱します。やや意味はわかりにくいですが、誤解のない説明をすると、こんな感じです。
DOWNWARD, TOWARDZERO, UPWARDは他に解釈の余地がありませんが、TONEARESTはど真ん中の数が来たときに、扱いに困ります。IEEE 754では2つの方式が定義されているようです。
説明するより実際に動かしたほうがわかりやすいでしょう。
丸めモードの動作を確認するプログラムを書きます。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と同じ結果になってしまいます。
$ 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というオプションを付けると、デフォルトの丸めモードを仮定した最適化をやめることができます。
$ 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であることも観察できます。
C99のfinal draftをざっと見た限りでは、ties to evenかties away from zeroか、明確に書かれていないように見えました。規格的にはどちらなんでしょうね?実装依存なのかなあ?
在宅勤務環境を整えようと、電源タップを物色していました。電源タップを見ていると大体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から転記しておくことにした。リンク、情報追加、加筆。
手に合うワイヤレスマウスを探し続け、高級製品、小さい製品、お手ごろ製品と買いまくり、一時は家に5個くらいワイヤレスマウスがありました。
日記から遍歴を辿ってみたところ、少なくとも下記の製品を持っていたようです。
全部で11個です。こんなに買っていたとは思いませんでした……。
色々試して最終的に辿り着いたのは、Microsoftの1000円の有線マウス(Basic Optical Mouse)でした。このマウスは中身がほぼ空っぽでめちゃくちゃ軽いです。
はい、何ですか?ワイヤレスじゃないって?そうですね。ワイヤレスマウスはどうしても重くて、手首が疲れてしまいダメでした。
小さいワイヤレスマウスなら軽いですが、手の大きさと合わずやっぱり手が疲れてしまいます。どうもマウスの持ち方(親指と小指で挟むように持つ)が、小さいマウス、ワイヤレスマウスと合わないみたいです。
という訳でワイヤレスマウスは諦めました。有線マウスは安くて軽くて最高です!
メモ: 技術系の話はFacebookから転記しておくことにした。マウス遍歴を追加。
< | 2021 | > | ||||
<< | < | 03 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | - | - | - |
合計:
本日:
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2023.
Powered by PHP 8.2.15.
using GD bundled (2.1.0 compatible)(png support.)