SQLite的ROUND函數的坑和解決方案

SQLite內置的round函數,有時候其反應和我們預期的反應不一樣,粗略說來,就是double的精度問題,無法精確的表達所有實數,只是在近似的表達着它們。

下面就是round函數抽風的例子和替代的解決方案:

sqlite> select  5.75*0.9, round(5.75*0.9, 2),  FLOOR( 5.75*100*0.9+0.5)/100.0;
+----------+--------------------+--------------------------------+
| 5.75*0.9 | round(5.75*0.9, 2) | FLOOR( 5.75*100*0.9+0.5)/100.0 |
+----------+--------------------+--------------------------------+
| 5.175    | 5.17               | 5.18                           |
+----------+--------------------+--------------------------------+
1 row in set

sqlite> select 10.25*1.1, ROUND(10.25*1.1, 2), FLOOR(10.25*100*1.1+0.5)/100.0;
+-----------+---------------------+--------------------------------+
| 10.25*1.1 | ROUND(10.25*1.1, 2) | FLOOR(10.25*100*1.1+0.5)/100.0 |
+-----------+---------------------+--------------------------------+
| 11.275    | 11.28               | 11.28                          |
+-----------+---------------------+--------------------------------+
1 row in set
有的情況下(python使用sqlalchemy時,在創建視圖時,如果視圖使用了FLOOR,視圖創建失敗),我們無法使用SQLite的FLOOR函數和CEIL函數,此時我們還有一種替代方案:
先將REAL類型的數值轉換爲字符串,轉換後的字符串會有一個小數點,然後將小數點替換爲一個無效字符(比如下劃線'_'),然後再將其轉換爲數值,進行運算。
具體方案如下:

sqlite> select REPLACE(5.75*100*0.9+0.5, '.', '_')/100.0;
+-------------------------------------------+
| REPLACE(5.75*100*0.9+0.5, '.', '_')/100.0 |
+-------------------------------------------+
| 5.18                                      |
+-------------------------------------------+
1 row in set

其轉換過程大致是這樣的:
運算後的數是一個浮點數:
sqlite> select  TYPEOF(5.75*100*0.9+0.5);
+--------------------------+
| TYPEOF(5.75*100*0.9+0.5) |
+--------------------------+
| real                     |
+--------------------------+
1 row in set

將其轉換爲字符串:
sqlite> select   LOWER(5.75*100*0.9+0.5);
+-------------------------+
| LOWER(5.75*100*0.9+0.5) |
+-------------------------+
| 518.0                   |
+-------------------------+
1 row in set

替換字符串中的小數點:
sqlite> select REPLACE(5.75*100*0.9+0.5, '.', '_');
+-------------------------------------+
| REPLACE(5.75*100*0.9+0.5, '.', '_') |
+-------------------------------------+
| 518_0                               |
+-------------------------------------+
1 row in set

將替換後的字符串參與運算:
sqlite> select REPLACE(5.75*100*0.9+0.5, '.', '_')/100.0;
+-------------------------------------------+
| REPLACE(5.75*100*0.9+0.5, '.', '_')/100.0 |
+-------------------------------------------+
| 5.18                                      |
+-------------------------------------------+
1 row in set

sqlite> 
因爲FLOOR函數得到的是一個integer,我們如果想模擬FLOOR函數,可以將替換後的字符串除以1,因爲沒有小數,SQLite就自動將其轉換爲integer了。
如下所示:

sqlite> select REPLACE(5.75*100*0.9+0.5, '.', '_')/1;
+---------------------------------------+
| REPLACE(5.75*100*0.9+0.5, '.', '_')/1 |
+---------------------------------------+
| 518                                   |
+---------------------------------------+
1 row in set

可以看到,因爲除完之後沒有小數,它的類型直接變成了integer。
sqlite> select TYPEOF(REPLACE(5.75*100*0.9+0.5, '.', '_')/1);
+-----------------------------------------------+
| TYPEOF(REPLACE(5.75*100*0.9+0.5, '.', '_')/1) |
+-----------------------------------------------+
| integer                                       |
+-----------------------------------------------+
1 row in set

而直接除100的話,因爲有小數,它的類型就變成了real。
sqlite> select TYPEOF(REPLACE(5.75*100*0.9+0.5, '.', '_')/100);
+-------------------------------------------------+
| TYPEOF(REPLACE(5.75*100*0.9+0.5, '.', '_')/100) |
+-------------------------------------------------+
| real                                            |
+-------------------------------------------------+
1 row in set

用integer除以integer的話,就是取整了:
sqlite> select REPLACE(5.75*100*0.9+0.5, '.', '_')/1/100;
+-------------------------------------------+
| REPLACE(5.75*100*0.9+0.5, '.', '_')/1/100 |
+-------------------------------------------+
| 5                                         |
+-------------------------------------------+
1 row in set

sqlite> select TYPEOF(REPLACE(5.75*100*0.9+0.5, '.', '_')/1/100);
+---------------------------------------------------+
| TYPEOF(REPLACE(5.75*100*0.9+0.5, '.', '_')/1/100) |
+---------------------------------------------------+
| integer                                           |
+---------------------------------------------------+
1 row in set

sqlite> 
完。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章