2017年2月28日 星期二

MySQL server Host Cache

MySQL 會將網域IP解析快取。
有時一開始網域解析設定錯了,嘗試連到 MySQL server 才發現這個錯誤。
訂正後,卻還是出現類似這樣的錯誤:
ERROR 1130 (HY000): Host 'new.example.com' is not allowed to connect to this MySQL server


這可能是 MySQL server Host Cache 的影響,更新 Host 快取指令:
使用 mysqladmin
mysqladmin flush-hosts -uroot -p

或登入MySQL後
mysql> flush hosts;


參考:
MySQL :: MySQL 5.7 Reference Manual :: B.5.2.6 Host 'host_name' is blocked
MySQL :: MySQL 5.6 Reference Manual :: 22.10.10.1 The host_cache Table


2017年1月9日 星期一

JavaScript 小數相加

因為浮點數在電腦中不是儲存準確的值,所以會有誤差。
原因參考:
The Floating-Point Guide - Basic Answers
[浮點數] IEEE754 , C/C++ 浮點數誤差 @ Edison.X. Blog :: 痞客邦 PIXNET ::


尤其在經過運算後,可能目視便可看到誤差,也可能多出許多非預期的小數位數。
網路上有幾種處理方式,以下用兩個浮點數相加為例。
測試瀏覽器:Chrome 55.0.2883.87 m

例如:
0.1+0.7 (預計結果為0.8)
=>0.7999999999999999


處理方式 1:
轉成整數運算後,再轉回小數。
(參考:http://www.w3schools.com/js/js_numbers.asphttp://www.cnblogs.com/konooo/archive/2010/01/23/1654617.html)
「先轉成整數運算後」-->「再轉回小數」
(0.1*10+0.7*10)/10
=>0.8

但發現一個特例
155.2+0.67 (預計結果為155.87)
=>155.86999999999998
(155.2*100+0.67*100)/100
=>155.86999999999998
=>「先轉成整數運算後」-->「再轉回小數」,這方法無效


處理方式 2:
轉成整數運算後,緊接著使用 round,再轉回小數。
(參考:http://stackoverflow.com/a/10474055)
「先轉成整數運算後」-->「round處理確保為整數」-->「再轉回小數」
Math.round((155.2*100+0.67*100))/100
=>155.87
參考連結範例,是直接乘上 1e12 倍,看似也正常
Math.round((155.2*1e12+0.67*1e12))/1e12
=>155.87


但 Math.round 直接乘上 1e12 倍再還原,也出現例外
(參考:http://stackoverflow.com/a/13388202)
1234563995.721+12345691212.718+1234568421.5891+12345677093.49284 (預計結果為27160500723.52094)
=>27160500723.520943 (IE11顯示 27160500723.520942)

Math.round(1234563995.721*100000+12345691212.718*100000+1234568421.5891*100000+12345677093.49284*100000)/100000
=>27160500723.52094 (IE11顯示 27160500723.52094)

Math.round(1234563995.721*1000000+12345691212.718*1000000+1234568421.5891*1000000+12345677093.49284*1000000)/1000000
=>27160500723.52094 (IE11顯示 27160500723.52094)

Math.round(1234563995.721*1e12+12345691212.718*1e12+1234568421.5891*1e12+12345677093.49284*1e12)/1e12
=>27160500723.520943  (IE11顯示 27160500723.520942) 
=> 小數原本只有5位,但chrome和IE11都變成6位
=> 直接乘上 1e12 倍再還原,無法完美得出 27160500723.52094


處理方式 3:
前面 http://stackoverflow.com/a/13388202 參考連結,建議用 toFixed
(1234563995.721+12345691212.718+1234568421.5891+12345677093.49284).toFixed(5)
=>"27160500723.52094" (但需注意 toFixed 回傳的結果是字串)

但 toFixed 在某些地方,也會出現不如預期的現象 (參考:http://stackoverflow.com/a/661757)
(1.695).toFixed(2)
=>"1.70"

(0.695).toFixed(2)  (預期結果"0.70")
=>"0.69" (但IE11正常顯示 "0.70")
=> Chrome無法正常四捨五入,但IE11可以正常四捨五入
其他特例
(1.2).toFixed(16)
=>"1.2000000000000000"
(1.2).toFixed(17)
=>"1.19999999999999996"  (但IE11正常顯示 "1.20000000000000000")



結論:如果著重於相加後,小數位數不會變多

解決方式一: 
「先乘上剛剛好能剛好將該小數最大位數變成整數的10倍數」-->「進行相加」-->「round處理確保為整數」-->「再轉回小數」
var floatAdd = function (arg1, arg2) {
    var r1, r2, m;
    try {
        r1 = arg1.toString().split(".")[1].length;
    } catch (e) {
        r1 = 0;
    }
    try {
        r2 = arg2.toString().split(".")[1].length;
    } catch (e) {
        r2 = 0;
    }
    m=Math.pow(10,Math.max(r1,r2))  
    return Math.round((arg1*m+arg2*m))/m;
};


解決方式二: 
「小數直接相加」-->「toFixed小數最大位數」
var floatAdd = function (arg1, arg2) {
    var r1, r2, m;
    try {
        r1 = arg1.toString().split(".")[1].length;
    } catch (e) {
        r1 = 0;
    }
    try {
        r2 = arg2.toString().split(".")[1].length;
    } catch (e) {
        r2 = 0;
    }
    m = Math.max(r1, r2);
    return parseFloat((arg1 + arg2).toFixed(m));
};


解決方式三:
使用The Floating-Point Guide提到的方式 「The Floating-Point Guide - Floating-point cheat sheet for JavaScript


2016年12月13日 星期二

PHP socket,client 端傳送、接收資料範例

假設 server 端和 client 端,約定傳輸資料的前 4 個 bytes 為緊接著要傳輸資料的長度。
所以,client 端傳送的資料為 => 4 bytes 的數字(資料本體長度) + 資料本體
同樣,client 端接收到資料為 => 4 bytes 的數字(資料本體長度) + 資料本體
//client 傳送:前4個bytes為要傳送的資料本體長度,後面緊接著資料本體
//server 回應:前4個bytes為回應的資料本體長度,後面緊接著資料本體
$data = "這是要傳送的資料本體";
//socket
$url = "http://example.com:8888";
$url_part = parse_url($url);
$host = $url_part["host"];
$address = gethostbyname($host);
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) {
    $err_msg = socket_strerror(socket_last_error($socket));
    socket_close($socket);
    throw new Exception("socket_create() failed:" . $err_msg);
}
$conn = socket_connect($socket, $address, $api_def->getSocketPort());
if (!$conn) {
    $err_msg = socket_strerror(socket_last_error($socket));
    socket_close($socket);
    throw new Exception("socket_connect() failed:" . $err_msg);
}

//傳送資料
$bin_body = pack("a*", $data);
//$bin_body = $data;
$body_len = strlen($bin_body);
$bin_head = pack("N", $body_len); //unsigned long (always 32 bit, big endian byte order)=>數字轉成一個 4 bytes 的big endian byte order
$bin_data = $bin_head . $bin_body; //4bytes資料本體長度 + 資料本體
$res = socket_write($socket, $bin_data, strlen($bin_data)); //返回成功寫入的bytes數 or false
if (!$res) {
    $err_msg = socket_strerror(socket_last_error($socket));
    socket_close($socket);
    throw new Exception("socket_write() failed:" . $err_msg);
}

//接收資料
$buf = "";
//先取得 4bytes 回應資料本體長度
if (false === ($bytes = socket_recv($socket, $buf, 4, MSG_WAITALL))) { //返回接收到的長度 or false。MSG_WAITALL:阻塞模式,若過程沒異常,一直等到讀到指定長度
    $err_msg = socket_strerror(socket_last_error($socket));
    socket_close($socket);
    throw new Exception("讀取資料長度失敗 socket_recv() failed:" . $err_msg);
}
$len_data = unpack("Nlen", $buf); //N:unsigned long (always 32 bit, big endian byte order)=>解析頭 4 個bytes

//再用取得的資料本體長度,抓回應資料本體
if (false === ($bytes = socket_recv($socket, $buf, $len_data["len"], MSG_WAITALL))) { //返回接收到的長度 or false。MSG_WAITALL:阻塞模式,若過程沒異常,一直等到讀到指定長度
    $err_msg = socket_strerror(socket_last_error($socket));
    socket_close($socket);
    throw new Exception("讀取回應資料本體失敗 socket_recv() failed:" . $err_msg);
}
socket_close($socket);
var_dump($buf);//回應的資料本體



參考:
PHP: 深入pack/unpack - 陈亦的个人页面 - 开源中国社区
[转帖]pack/unpack用法----心得筆記 - Perl Forum
PHP: pack - Manual
PHP: socket_recv - Manual
bytearray - String to byte array in php - Stack Overflow
java的int和byte數組的相互轉換_我們關註網
IO模式设置网络编程常见问题总结—IO模式设置,阻塞与非阻塞的比较,recv参数对性能的影响—O_NONBLOCK(open使用)、IPC_NOWAIT(msgrcv)、MSG_DONTWAIT(re - houlaizhe221的专栏 - 博客频道 - CSDN.NET


2016年12月12日 星期一

位元組順序

因為不同電腦,資料儲存的位元組順序可能不同,所以電腦間要用網路傳輸資料時,要注意位元組的順序,才不會傳輸後,因解讀順序不同,導致資料意義變了。

一個整數資料,依將高數位資料,放在儲存位址的前或後,可區分為 big-endian、little-endian 兩種儲存順序,而在網路傳輸 TCP/IP 協議是使用 big-endian。
  • big-endian(大端序、大尾序):
    多位的整數,高位數存放在儲存位址前面(高位數放在前面的低位址,低位數放在後面的高位址)
  • little-endian(小端序、小尾序):
    多位的整數,低位數(Least Significant Bit、LSB)存放在儲存位址前面(低位數放在前面的低位址,高位數放在後面的高位址)
  • network byte order(網絡位元組序、網絡序):一般為大端序。
例如:一個16進位 int ,0x12345678,
高位數~低位數依序為:12、34、56、78
共占儲存位址4個位元組:0x100~0x103
大端序在每個位元組存放的資料 0x100:12、0x101:34、0x102:56、0x103:78
小端序在每個位元組存放的資料 0x100:78、0x101:56、0x102:34、0x103:12


相關名詞:
  • 最高有效位(Most Significant Bit,msb):
    n位二進位數字中的n-1位。有符號二進位數,負數採用反碼或補碼形式,此時msb用來表示符號,msb為1表示負數,0表示正數。
  • MSB(Most Significant Byte):
    多字節序列中具有最大權重的字節。
  • 最低有效位(Least Significant Bit,lsb):
    一個二進位數字中的第0位(即最低位)。二進位數中的最小的單位,可以用來指示數字很小的變化。
  • LSB(Least Significant Byte):
    多字節序列中最小權重的字節。

使用 PHP 判斷系統是大端序或小端序
//方法1
function isBigEndian() {
    $bin = pack("L", 0x12345678);
    $hex = bin2hex($bin);
    if (ord(pack("H2", $hex)) === 0x78) {
        return FALSE;
    }

    return TRUE;
}
//方法2
function isLittleEndian() {
    $testint = 0x00FF;
    $p = pack('S', $testint);
    return $testint===current(unpack('v', $p));
}
//方法3
function isLittleEndian() {
    return unpack('S',"\x01\x00")[1] === 1;
}


參考:
位元組順序 - 維基百科,自由的百科全書
PHP: 深入pack/unpack - 陈亦的个人页面 - 开源中国社区
How to get the endianness type in PHP?


其他:C-style strings 在記憶體體中位置。
截錄 Big and Little Endian
「Here are some facts you should know about C-style strings and arrays.
1.C-style strings are stored in arrays of characters.
2.Each character requires one byte of memory, since characters are represented in ASCII (in the future, this could change, as Unicode becomes more popular).
3.In an array, the address of consecutive array elements increases. Thus, & arr[ i ] is less than & arr[ i + 1 ].
4.What's not as obvious is that if something is stored in increasing addresses in memory, it's going to be stored in increasing "addresses" in a file. When you write to a file, you usually specify an address in memory, and the number of bytes you wish to write to the file starting at that address.

So, let's imagine some C-style string in memory. You have the word "cat". Let's pretend 'c' is stored at address 1000. Then 'a' is stored at 1001. 't' is at 1002. The null character '\0' is at 1003.
Since C-style strings are arrays of characters, they follow the rules of characters.」



2016年12月5日 星期一

Linux vi 將內容從一個檔複製到另一個檔

假設要將 testA 檔案部分內容,複製到 testB 檔
$ vi testA
先在 testA 檔內複製需要的部分,
例如,用「yy」指令複製目前這一行(此時複製的資料會存在暫存區)
在不退出 vi 的情況下,直接去編輯 testB 檔,
如此在暫存區的複製資料才會留著。
:vi testB

:e testB

此動作會關閉 testA,然後開啟 testB,
所以假設 testA 檔有修改到,但沒儲存,會過不去 testB
若要放棄testA修改,直接編輯testB,指令可改成
:vi! testB

:e! testB

到 testB 檔後,即可直接用「p」指令貼上

若 testA 檔,有好幾個地方,要分別複製到 testB 檔
則在 testA 檔複製時,可指定要放的暫存區(a~z)
先按「"」「a」,表示要使用 a 暫存區
再按「yy」指令複製,則會將資料放在 a 暫存區
同理,
先按「"」「b」,表示要使用 b 暫存區
再按「yy」指令複製,則會將資料放在 b 暫存區
如此便可將不同部分,放在不同暫存區,一次帶到 testB 檔,
然後在 testB 檔時,切換目前使用的暫存區,再使用「p」指令貼上。


[相關:選取特定區域後複製、貼上]
先按「v」後,移動游標,即可反白選取所要的區域字段。
再來可選擇按「d」,剪下選取區域的內容,
或是按「y」,複製選取區域的內容。
最後再用「p」貼上。


參考:
Vi- 編輯與修改
vim - Copy and paste content from one file to another file in VI - Stack Overflow
Checko's Blog: VI 學習 : copy and paste