2022年1月2日 星期日

PHP json_encode() 小數精度問題

環境:PHP 7.3.12
問題:
$json_arr = ["aa" => 0.1];
echo json_encode($json_arr, JSON_UNESCAPED_UNICODE);
輸出結果
{"aa":0.10000000000000001}
浮點數 0.1 轉成 JSON,變成 0.10000000000000001 

以前沒問題的程式,突然發現有這情況。
原來 PHP 從 7.1 開始,json_encode() 改使用 serialize_precision 的設定值處理小數精度。
而我的環境從 PHP 5.6 慢慢升級到 PHP 7.3.12,設定檔還是沿用舊版的。

舊版使用 precision 設定值處理 json_encode() 的浮點數精度。EG(precision)
7.1版後使用 serialize_precision 設定值處理 json_encode() 的浮點數精度。PG(serialize_precision)

舊版 PHP,php.ini 中 precision = 14,serialize_precision = 17
; The number of significant digits displayed in floating point numbers.
; http://php.net/precision
precision = 14

; When floats & doubles are serialized store serialize_precision significant
; digits after the floating point. The default value ensures that when floats
; are decoded with unserialize, the data will remain the same.
serialize_precision = 17

7.1版後,php.ini 中 precision = 14,serialize_precision = -1
; The number of significant digits displayed in floating point numbers.
; http://php.net/precision
precision = 14

; When floats & doubles are serialized, store serialize_precision significant
; digits after the floating point. The default value ensures that when floats
; are decoded with unserialize, the data will remain the same.
; The value is also used for json_encode when encoding double values.
; If -1 is used, then dtoa mode 0 is used which automatically select the best
; precision.
serialize_precision = -1



https://bugs.php.net/bug.php?id=74221 上說
 [2017-03-07 23:06 UTC] nikic@php.net
 You can restore the previous behavior by setting serialize_precision to the value of precision,
 which is 14 by default.
 Alternatively and preferably, you can set serialize_precision to -1.
 In this case PHP will automatically determine the minimal precision
 necessary to still accurately represent the number.

解決方法一:將 serialize_precision 設為 14,也就是舊版 json_encode() 處理使用的 precision = 14 這個值
ini_set('serialize_precision', 14);
$json_arr = ["aa" => 0.1];
echo json_encode($json_arr, JSON_UNESCAPED_UNICODE);
輸出結果
{"aa":0.1}

解決方法二:將 serialize_precision 設為 -1,也就是新版的預設值。
ini_set('serialize_precision', -1);
$json_arr = ["aa" => 0.1];
echo json_encode($json_arr, JSON_UNESCAPED_UNICODE);
輸出結果
{"aa":0.1}


precision、serialize_precision 都可設定為 -1,官網說明是會使用較佳的算法。
https://www.php.net/manual/en/ini.core.php#ini.precision
precision int
The number of significant digits displayed in floating point numbers.
-1 means that an enhanced algorithm for rounding such numbers will be used.
https://www.php.net/manual/en/ini.core.php#ini.serialize-precision
serialize_precision int
The number of significant digits stored while serializing floating point numbers.
-1 means that an enhanced algorithm for rounding such numbers will be used.
https://wiki.php.net/rfc/precise_float_value
This RFC proposes to introduce a new setting EG(precision)=-1 and PG(serialize_precision)=-1
 that uses zend_dtoa()'s mode 0 which uses better algorigthm for rounding float numbers
 (-1 is used to indicate 0 mode).
The RFC also proposes changing ini for JSON precision to PG(serialize_precision).



結論:
設成新版預設值 serialize_precision = -1,應該是最佳的解法。

如果不放心,怕又遇到非預期情況,且可接受使用字串形式,也可先將浮點數轉成字串再處理。
$json_arr = ["aa" => (string) 0.1];
echo json_encode($json_arr, JSON_UNESCAPED_UNICODE);
輸出結果
{"aa":"0.1"}





參考:

Netbeans Git 檔案沒修改,卻顯示修改

安裝的版的 Apache Netbeans 12.5,匯入舊版的資料後,
某些沒修改的檔案,Netbeans 內的 Git 功能,卻顯示修改。
但用 git diff 查看,又無異動的資訊。

解決方法:
直接開啟 git bash 執行指令 
# git status
然後會開始 Refresh index,顯示最新狀態,就好了。

偶而也會遇到暫時修改檔案,
單純用 ctrl+z 復原(可確定不是不可見字元影響),Netbeans 內的 Git 卻仍顯示修改,
經過測試,用此方法亦可解決。

2021年12月30日 星期四

Node.js 的 console log 用法

[常用]

  • console.log()
    輸出訊息到stdout。
    其他別名 console.info()、console.debug()
    是 buffer 緩衝輸出,所以如果 process 突然中斷,可能不會成功輸出到stdout。
  • console.dir()
    輸出物件內容到stdout。
  • console.error()
    輸出訊息到stderr(同步寫入、會阻塞 process)。
    其他別名 console.warn()
  • console.time()、console.timeEnd()
    計算執行花費的時間。
  • console.trace()
    不引發錯誤,顯示程式碼堆疊追踪(stack trace、堆疊回溯)的資料。


[範例1] console.log()、console.error() 可用的參數
%s:字串格式輸出
%d:數字格式輸出
%j:JSON格式輸出

let myStr = "字串";
let myNum = 123;
let myObj ={aa:"abc",bb:999};
let myBool = true;
let myUndefined = undefined;
let myNull = null;

console.log("輸出:%s %s %s %s %s %s", myStr, myNum, myObj, myBool ,myUndefined, myNull);
console.log("輸出:%d %d %d %d %d %d", myStr, myNum, myObj, myBool ,myUndefined, myNull);
console.log("輸出:%j %j %j %j %j %j", myStr, myNum, myObj, myBool ,myUndefined, myNull);
結果:
輸出:字串 123 [object Object] true undefined null
輸出:NaN 123 NaN 1 NaN 0
輸出:"字串" 123 {"aa":"abc","bb":999} true undefined null



[範例2] 計算執行花費的時間
console.time('myTimerA');
.....其他程式碼.....
console.timeEnd('myTimerA');
結果:
myTimerA: 0.411ms



[範例3] stack trace 堆疊追踪
console.trace("堆疊追踪test");
結果:
Trace: 堆疊追踪test
    at Object.<anonymous> (/home/cg/root/5749468/main.js:26:9)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)
    at Module.runMain (module.js:604:10)
    at run (bootstrap_node.js:389:7)
    at startup (bootstrap_node.js:149:9)
    at bootstrap_node.js:504:3




參考:

2021年11月18日 星期四

JavaScript 的 call、apply、bind

call、apply、bind:將物件變成另一個函式中的this

[測試範例的 物件 和 函式]
物件:var objTest
函式:function test(a, b)

var objTest = {
  x: "xyz"
}

function test(a, b) {
  console.log("this.x=", this.x);
  console.log("a=", a);
  console.log("b=", b);
  console.log("arguments=", Array.from(arguments));
  //console.log("arguments=", [...arguments]);
}
test(1, 2);
//this.x= undefined
//a= 1
//b= 2
//arguments= [1, 2]


[call]
使用call,會直接執行function。
test.call(objTest);
//this.x= xyz
//a= undefined
//b= undefined
//arguments= []

test.call(objTest, 10);
//this.x= xyz
//a= 10
//b= undefined
//arguments= [10]

test.call(objTest, 10, 20);
//this.x= xyz
//a= 10
//b= 20
//arguments= [10, 20]


[apply]
使用apply,會直接執行function。跟call不同,傳入的參數為陣列。
test.apply(objTest);
//this.x= xyz
//a= undefined
//b= undefined
//arguments= []

test.apply(objTest, [10]);
//this.x= xyz
//a= 10
//b= undefined
//arguments= [10]

test.apply(objTest, [10, 20]);
//this.x= xyz
//a= 10
//b= 20
//arguments= [10, 20]


[bind]
使用bind,會回傳綁定後的function,綁定時可選擇是否綁定參數值,沒綁定的參數值,可使用時再傳入。
//只綁定this物件
var ff = test.bind(objTest);
ff();
//this.x= xyz
//a= undefined
//b= undefined
//arguments= []

ff(10);
//this.x= xyz
//a= 10
//b= undefined
//arguments= [10]

ff(10, 20);
//this.x= xyz
//a= 10
//b= 20
//arguments= [10, 20]
//綁定了this物件,和第一個參數a=10,則使用時,參數a不須也無法再傳值,a固定為10
var ff = test.bind(objTest, 10);
ff();
//this.x= xyz
//a= 10
//b= undefined
//arguments= [10]

ff(20); //已綁定了第一個參數a=10,所以剩下的參數只剩一個,此處傳的是b=20
//this.x= xyz
//a= 10
//b= 20
//arguments= [10, 20]

ff(50, 60); //多傳的參數60,不會用到
//this.x= xyz
//a= 10
//b= 50
//arguments= [10, 50, 60]
//綁定了this物件,和第一個參數a=10,第二個參數b=10,則使用時,參數a、參數b不須也無法再傳值,a固定為10,b固定為20
var ff = test.bind(objTest, 10, 20);
ff();
//this.x= xyz
//a= 10
//b= 20
//arguments= [10, 20]

ff(91, 92);
//this.x= xyz
//a= 10
//b= 20
//arguments= (4) [10, 20, 91, 92]



[應用]
  • 處理偽陣列(類似陣列,但不是陣列的資料,當成陣列處理)
    function aa() {
      //arguments 不是陣列,不能使用 slice、push...
      //arguments.slice(1)
      //Uncaught TypeError: arguments.slice is not a function
      console.log([].slice.call(arguments, 1));
    }
    
    aa("a", "b", "c", "d");
    //['b', 'c', 'd']
  • 取陣列最大最小值
    var arr = [5, 2, 8, -9];
    //原本要處理成 Math.max(5, 2, 8, -9),剛好可用 apply 用陣列傳遞參數的特性。
    //現在也可以使用 ...展開運算子(Spread syntax),直接寫成 Math.max(...arr)。
    //Math.max.apply(null, arr),第一個物件參數沒用到,隨意傳個東西,
    //第一個物件參數傳null、undefined,在非嚴格模式(non-strict),會被置換成global object(在瀏覽器即為window物件)
    console.log(Math.max.apply(null, arr));
    //8



參考:

Linux 用 less 指令儲存檔案

查看內容時,使用「|」指令:
| <m> shell-command
              <m>  represents  any  mark letter.  Pipes a section of the input file to the given shell command.
              The section of the file to be piped is between the first line on the current screen and the posi‐
              tion  marked  by the letter.  <m> may also be ^ or $ to indicate beginning or end of file respec‐
              tively.  If <m> is . or newline, the current screen is piped.
  1. 按「|」,進入 shell-command
  2. 按「^」,表示從頭到目前位置看到的內容;
    按「$」,表示從頭到尾的內容。
  3. 輸入「cat > 要另存的檔案名稱」,按 enter 後即會儲存。


查看內容時,使用「s」指令:
s filename
      Save the input to a file.  This only works if the input is a pipe, not an ordinary file.
這只在查看的內容為 pipe 時,才可用,
例如:
$ cat test.txt | less
打開檔案後,按「s」,再輸入「要另存的檔案名稱」,按 enter 後即會儲存。




參考: