JS位運(yùn)算異常(位運(yùn)算精度丟失)的原因探究

參考資料

《【轉(zhuǎn)+補(bǔ)充】深入研究js中的位運(yùn)算及用法》
《【JS時(shí)間戳】獲取時(shí)間戳的最快方式探究》

由來(lái)

日常開發(fā)中一直沒遇到過位運(yùn)算導(dǎo)致精度丟失的問題坤检,直到這天,搞10位時(shí)間戳取整的時(shí)候期吓,終于被我撞上了早歇。具體是個(gè)什么場(chǎng)景呢,我們來(lái)還原下案發(fā)現(xiàn)場(chǎng):

  • 首先我們獲取一下10位的時(shí)間戳
const {
    performance
} = require('perf_hooks')
// 通過performance獲取當(dāng)前時(shí)間
const t = performance.timeOrigin + performance.now()
console.log(t)
console.log(t / 1000)
console.log(t / 1000 << 0)

可以看到輸出的結(jié)果為:

1597113682985.075
1597113682.958075
1597113682

得到的t是一個(gè)精確到微秒的時(shí)間戳讨勤。但是請(qǐng)求接口的時(shí)候需要的是一個(gè)10位(精確到秒)的時(shí)間戳箭跳,所以這里需要將它轉(zhuǎn)換為10位,自然就是?1000即可潭千,然后通過位運(yùn)算來(lái)實(shí)現(xiàn)類似Math.trunc的取證效果谱姓,得到了我們要的10位時(shí)間戳。至此完美解決刨晴!那問題又是如何發(fā)生的呢屉来?

  • 然后獲取一下13位時(shí)間戳

按照上面的運(yùn)算規(guī)律,如果我們要獲取13位時(shí)間戳狈癞,是不是直接對(duì)t>>0就可以了呢茄靠?我們來(lái)看一下:

const {
    performance
} = require('perf_hooks')
// 通過performance獲取當(dāng)前時(shí)間
const t = performance.timeOrigin + performance.now()
console.log(t>>0)

輸出結(jié)果如下:

-614151127

WTF!!!看到了咩!5啊慨绳!居然輸出了一個(gè)負(fù)數(shù)!U媸脐雪!我們想要的結(jié)果應(yīng)該是1597113682985才對(duì)啊恢共!為什么會(huì)出現(xiàn)了負(fù)數(shù)呢U角铩!旁振!

由此,怪物出現(xiàn)啦涨岁!我們今天就來(lái)解讀(xiang fu)一下它拐袜!

原因分析+知識(shí)惡補(bǔ)

想到這里,我們一定就會(huì)怪是位運(yùn)算的鍋梢薪!那這個(gè)鍋該怎么讓位運(yùn)算背起來(lái)呢蹬铺!我們來(lái)研究一下!

首先我們知道秉撇,JS中沒有真正的整型甜攀,數(shù)據(jù)都是以double(64bit)的標(biāo)準(zhǔn)格式存儲(chǔ)的秋泄,這里就不再贅述了,要想搞透其中的原理规阀,請(qǐng)打開【傳送門】

  • 什么是位運(yùn)算恒序?

位運(yùn)算是在數(shù)字底層(即表示數(shù)字的 32 個(gè)數(shù)位)進(jìn)行運(yùn)算的。由于位運(yùn)算是低級(jí)的運(yùn)算操作谁撼,所以速度往往也是最快的(相對(duì)其它運(yùn)算如加減乘除來(lái)說)歧胁,并且借助位運(yùn)算有時(shí)我們還能實(shí)現(xiàn)更簡(jiǎn)單的程序邏輯,缺點(diǎn)是很不直觀,許多場(chǎng)合不能夠使用厉碟。

位運(yùn)算只對(duì)整數(shù)起作用喊巍,如果一個(gè)運(yùn)算子不是整數(shù),會(huì)自動(dòng)轉(zhuǎn)為整數(shù)后再運(yùn)行箍鼓。雖然在 JavaScript 內(nèi)部崭参,數(shù)值都是以64位浮點(diǎn)數(shù)的形式儲(chǔ)存,但是做位運(yùn)算的時(shí)候款咖,是以32位帶符號(hào)的整數(shù)進(jìn)行運(yùn)算的何暮,并且返回值也是一個(gè)32位帶符號(hào)的整數(shù)。而32位整型的取值范圍是:-2147483648 到 2147483647之剧,最大值轉(zhuǎn)換為時(shí)間后是:2038-01-19 11:14:07郭卫,所以我們?cè)谶@里做個(gè)預(yù)測(cè),2038年1月19號(hào)11:14:07后背稼,如果地球還在贰军,可能會(huì)有一批程序出現(xiàn)BUG。顯然10位時(shí)間戳目前還在這個(gè)范圍內(nèi)蟹肘,而超過10位后就已經(jīng)超出了界限词疼,這也就是為什么會(huì)因?yàn)槲贿\(yùn)算導(dǎo)致精度丟失的原因所在了。

  • 關(guān)于二進(jìn)制

  • ECMAScript中的所有數(shù)值都以IEEE-754 64位格式存儲(chǔ)帘腹,但位操作符并不直接操作64位的值贰盗,而是以32位帶符號(hào)的整數(shù)進(jìn)行運(yùn)算的,并且返回值也是一個(gè)32位帶符號(hào)的整數(shù)
  • 這種位數(shù)轉(zhuǎn)換使得在對(duì)特殊的NaN和Infinity值應(yīng)用位操作時(shí)阳欲,這兩個(gè)值都會(huì)被當(dāng)成0來(lái)處理
  • 如果對(duì)非數(shù)值應(yīng)用位操作符舵盈,會(huì)先使用Number()將該值轉(zhuǎn)換成數(shù)值再應(yīng)用位操作,得到的結(jié)果是一個(gè)數(shù)值
//'|'表示按位或球化,一個(gè)整數(shù)與0按位或運(yùn)算可以得到它本身秽晚,一個(gè)小數(shù)與0按位或運(yùn)算可以得到取整效果
console.log( 1.3 | 0);//1
console.log( 1.8 | 0);//1
console.log( Infinity | 0);//0
console.log( -Infinity | 0);//0
console.log( NaN | 0);//0
console.log('12px' | 0);//0
console.log('12' | 0);//12

以下來(lái)源于w3shool:
ECMAScript 整數(shù)有兩種類型,即有符號(hào)整數(shù)(允許用正數(shù)和負(fù)數(shù))和無(wú)符號(hào)整數(shù)(只允許用正數(shù))筒愚。在 ECMAScript 中赴蝇,所有整數(shù)字面量默認(rèn)都是有符號(hào)整數(shù),這意味著什么呢巢掺?

有符號(hào)整數(shù)使用 31 位表示整數(shù)的數(shù)值句伶,用第 32 位表示整數(shù)的符號(hào)劲蜻,0 表示正數(shù),1 表示負(fù)數(shù)考余。數(shù)值范圍從 -2147483648 到 2147483647先嬉。

可以以兩種不同的方式存儲(chǔ)二進(jìn)制形式的有符號(hào)整數(shù),一種用于存儲(chǔ)正數(shù)秃殉,一種用于存儲(chǔ)負(fù)數(shù)坝初。正數(shù)是以真二進(jìn)制形式存儲(chǔ)的,前 31 位中的每一位都表示 2 的冪钾军,從第 1 位(位 0)開始鳄袍,表示 20,第 2 位(位 1)表示 21吏恭。沒用到的位用 0 填充拗小,即忽略不計(jì)。例如樱哼,下圖展示的是數(shù) 18 的表示法哀九。

那在js中二進(jìn)制和十進(jìn)制如何轉(zhuǎn)換呢?如下

console.log((18).toString(2));//"10010"
console.log(0b00000000000000000000000000010010);//18

// 十進(jìn)制 => 二進(jìn)制
let num = 10;
console.log(num.toString(2));
// 二進(jìn)制 => 十進(jìn)制
let num1 = 1001;
console.log(parseInt(num1, 2)); 

負(fù)數(shù)同樣以二進(jìn)制存儲(chǔ)搅幅,但使用的格式是二進(jìn)制補(bǔ)碼阅束。計(jì)算一個(gè)數(shù)值的二進(jìn)制補(bǔ)碼,需要經(jīng)過下列3個(gè)步驟:

  1. 求這個(gè)數(shù)值絕對(duì)值的二進(jìn)制碼
  2. 求二進(jìn)制反碼茄唐,即將0替換成1息裸,將1替換成0
  3. 得到的二進(jìn)制反碼加1

例如,要確定-18的二進(jìn)制表示沪编,首先必須得到18的二進(jìn)制表示呼盆,如下所示:
0000 0000 0000 0000 0000 0000 0001 0010

接下來(lái),計(jì)算二進(jìn)制反碼蚁廓,如下所示:
1111 1111 1111 1111 1111 1111 1110 1101

最后访圃,在二進(jìn)制反碼上加 1,如下所示:
1111 1111 1111 1111 1111 1111 1110 1101 +
0000000000000000000000000000 0001 =
1111 1111 1111 1111 1111 1111 1110 1110

因此相嵌,-18 的二進(jìn)制就是 1111 1111 1111 1111 1111 1111 1110 1110
而其相反數(shù)18的二進(jìn)制為0000 0000 0000 0000 0000 0000 0001 0010

ECMAScript會(huì)盡力向我們隱藏所有這些信息腿时,在以二進(jìn)制字符串形式輸出一個(gè)負(fù)數(shù)時(shí),我們看到的只是這個(gè)負(fù)數(shù)絕對(duì)值的二進(jìn)制碼前面加上了一個(gè)負(fù)號(hào)

var num = -18;
console.log(num.toString(2));//'-10010'
  • 浮點(diǎn)數(shù)的二進(jìn)制

JavaScript 只有一種數(shù)字類型 ( Number )

JavaScript采用 IEEE 754 標(biāo)準(zhǔn)雙精度浮點(diǎn)(double64)饭宾,64位中有1位符號(hào)位批糟,11位存儲(chǔ)指數(shù),52位存儲(chǔ)浮點(diǎn)數(shù)的有效數(shù)字
有時(shí)候小數(shù)在二進(jìn)制中表示是無(wú)限的捏雌,所以從53位開始就會(huì)舍入(舍入規(guī)則是0舍1入)跃赚,這樣就造成了“浮點(diǎn)精度問題”(由于舍入規(guī)則有時(shí)大點(diǎn)笆搓,有時(shí)小點(diǎn))

IEEE標(biāo)準(zhǔn)中float的存儲(chǔ)規(guī)則

IEEE標(biāo)準(zhǔn)中double的存儲(chǔ)規(guī)則

更多詳細(xì)介紹性湿,請(qǐng)參看傳送門

我們將1596596596.3742654.toString(2)轉(zhuǎn)為二進(jìn)制字符串表示如下:
1011111001010100010000101110100.0101111111001111110111
但實(shí)際在內(nèi)存中的存儲(chǔ)如下:

  1. 首先將整數(shù)部分1596596596轉(zhuǎn)為二進(jìn)制:1011111001010100010000101110100
  2. 將小數(shù)部分轉(zhuǎn)為二進(jìn)制:0.010111111100111111011011011101010000011000111100010111
  3. 所以其二進(jìn)制拼接后為:1011111001010100010000101110100.010111111100111111011011011101010000011000111100010111纬傲,但顯然位數(shù)超出了64位的限制,而且小數(shù)點(diǎn)也不可能存儲(chǔ)的為小數(shù)點(diǎn)(只有0和1胺羝怠)
  4. 所以將小數(shù)點(diǎn)左移30位后轉(zhuǎn)為科學(xué)計(jì)數(shù)法:1.011111001010100010000101110100010111111100111111011011011101010000011000111100010111 * 2^30
  5. 正數(shù)叹括,符號(hào)位為0,我們?cè)谧罡呶环?hào)位中填0
  6. 指數(shù)部分宵荒,通過左移得到的汁雷,指數(shù)為正,因此62位填1报咳,然后將指數(shù)30-1=29侠讯,二進(jìn)制為101001,在左邊添0暑刃,所以61~52位湊夠了10位厢漩,因此指數(shù)部分為100 0010 1001
  7. 至于尾數(shù)部分,直接將科學(xué)計(jì)數(shù)法后小數(shù)點(diǎn)后面的數(shù)扔進(jìn)去即可(因?yàn)槌?2位長(zhǎng)度岩臣,所以更多的位數(shù)會(huì)舍去溜嗜,最后一位會(huì)0舍1入),所以尾數(shù)部分為:0111110010101000100001011101000101111111001111110111
  8. 至此架谎,這個(gè)浮點(diǎn)數(shù)的二進(jìn)制就存儲(chǔ)為:0100 0010 1001 0111 1100 1010 1000 1000 0101 1101 0001 0111 1111 0011 1111 0111炸宵,轉(zhuǎn)為16進(jìn)制為:0x4297CA885D17F3F7
  • JS中的精度丟失

說到這里就不得不簡(jiǎn)單提一下數(shù)字精度丟失的問題。上面也知道谷扣,JS中所有的數(shù)字都是用double方式進(jìn)行存儲(chǔ)的土全,所以必然會(huì)存在精度丟失問題。

以下轉(zhuǎn)自文章:JavaScript數(shù)字精度丟失問題總結(jié)

此時(shí)只能模仿十進(jìn)制進(jìn)行四舍五入了抑钟,但是二進(jìn)制只有 0 和 1 兩個(gè)涯曲,于是變?yōu)?0 舍 1 入。這即是計(jì)算機(jī)中部分浮點(diǎn)數(shù)運(yùn)算時(shí)出現(xiàn)誤差在塔,丟失精度的根本原因幻件。

大整數(shù)的精度丟失和浮點(diǎn)數(shù)本質(zhì)上是一樣的,尾數(shù)位最大是 52 位蛔溃,因此 JS 中能精準(zhǔn)表示的最大整數(shù)是 Math.pow(2, 53)绰沥,十進(jìn)制即 9007199254740992

大于9007199254740992的可能會(huì)丟失精度:
9007199254740992 >> 10000000000000...000 ``// 共計(jì) 53 個(gè) 0
9007199254740992 + 1 >> 10000000000000...001 ``// 中間 52 個(gè) 0
9007199254740992 + 2 >> 10000000000000...010 ``// 中間 51 個(gè) 0

實(shí)際上
9007199254740992 + 1 ``// 丟失
9007199254740992 + 2 ``// 未丟失
9007199254740992 + 3 ``// 丟失
9007199254740992 + 4 ``// 未丟失

以上,可以知道看似有窮的數(shù)字, 在計(jì)算機(jī)的二進(jìn)制表示里卻是無(wú)窮的贺待,由于存儲(chǔ)位數(shù)限制因此存在“舍去”徽曲,精度丟失就發(fā)生了。

想了解更深入的分析可以看這篇論文(你品麸塞!你細(xì)品M撼肌):What Every Computer Scientist Should Know About Floating-Point Arithmetic
關(guān)于精度和范圍的內(nèi)容可查看【JS的數(shù)值精度和數(shù)值范圍】


位運(yùn)算導(dǎo)致數(shù)據(jù)異常的過程分析

通過前面的知識(shí)補(bǔ)充,我們已經(jīng)知道:

位運(yùn)算只對(duì)整數(shù)起作用,如果一個(gè)運(yùn)算子不是整數(shù)奥此,會(huì)自動(dòng)轉(zhuǎn)為整數(shù)后再運(yùn)行弧哎。雖然在 JavaScript 內(nèi)部,數(shù)值都是以64位浮點(diǎn)數(shù)的形式儲(chǔ)存稚虎,但是做位運(yùn)算的時(shí)候撤嫩,是以32位帶符號(hào)的整數(shù)進(jìn)行運(yùn)算的,并且返回值也是一個(gè)32位帶符號(hào)的整數(shù)蠢终。

ECMAScript 中序攘,所有整數(shù)字面量默認(rèn)都是有符號(hào)整數(shù),這意味著什么呢寻拂?有符號(hào)整數(shù)使用 31 位表示整數(shù)的數(shù)值程奠,用第 32 位表示整數(shù)的符號(hào),0 表示正數(shù)祭钉,1 表示負(fù)數(shù)梦染。數(shù)值范圍從-2147483648 到 2147483647

這也就是為什么對(duì)于整數(shù)部位為10位的時(shí)間戳朴皆,通過位運(yùn)算可以進(jìn)行取整(因?yàn)槟壳皶r(shí)間戳159xxxxxxx<2147483647)帕识,不存在時(shí)間戳超過范圍的問題。但是對(duì)于13位時(shí)間戳遂铡,如1596615447123>2147483647肮疗,此時(shí)再通過位運(yùn)算操作的時(shí)候就會(huì)導(dǎo)致異常,如:

let t = 1596615447015.007
console.log(Math.trunc(t), Math.trunc(t / 1000)) // 1596615447015 1596615447
console.log(t / 1000 | 0) // 1596615447
console.log(t | 0) // -1112387097

這主要是因?yàn)樵谶M(jìn)行位運(yùn)算之前扒接,JS會(huì)先將64bit的浮點(diǎn)數(shù)1596615447015.01轉(zhuǎn)為32bit的有符號(hào)整型后進(jìn)行運(yùn)算的伪货,這個(gè)轉(zhuǎn)換過程如下:

32bit整型存儲(chǔ)結(jié)構(gòu)
  1. 首先1596615447015.333的二進(jìn)制表示為10111001110111101101100100101000111100111.0101010101,其在內(nèi)存中的存儲(chǔ)結(jié)構(gòu)如下:
    1. 正數(shù),最高位符號(hào)位0
    2. 科學(xué)計(jì)數(shù)法小數(shù)點(diǎn)左移钾怔,指數(shù)位最高位為1
    3. 小數(shù)點(diǎn)左移40位碱呼,則剩余指數(shù)部分為40-1=39的10位二進(jìn)制00 0010 0111
    4. 所以前12位為0100 0010 0111
  2. 剩余52位從小數(shù)點(diǎn)后開始取52位(不足52位在最后補(bǔ)0,超過則最后一位0舍1入)為0111001110111101101100100101000111100111010101010100
  3. 所以1596615447015.333的二進(jìn)制存儲(chǔ)表示為:0100 0010 0111 0111 0011 1011 1101 1011 0010 0101 0001 1110 0111 0101 0101 0100宗侦,轉(zhuǎn)為16進(jìn)制表示為:0x42773BDB251E7554
  4. 開始將其轉(zhuǎn)為32bit的int類型愚臀,首先根據(jù)指數(shù)位100 0010 0111可知,小數(shù)點(diǎn)右移39+1=40位矾利,剩余小數(shù)位數(shù)舍掉姑裂,則52位尾數(shù)部分得到的是73BDB251E7,即二進(jìn)制表示為0111 0011 1011 1101 1011 0010 0101 0001 1110 0111
  5. 截取上面二進(jìn)制的后32位得到:1011 1101 1011 0010 0101 0001 1110 0111男旗,系統(tǒng)會(huì)將這32位數(shù)當(dāng)作轉(zhuǎn)換后的int類型舶斧,由于最高位為1,即這是一個(gè)負(fù)數(shù)
  6. 對(duì)于系統(tǒng)來(lái)說察皇,如果是負(fù)數(shù)茴厉,則用這個(gè)負(fù)數(shù)的補(bǔ)碼表示,即這個(gè)負(fù)數(shù)絕對(duì)值的二進(jìn)制按位取反,然后最后一位執(zhí)行不進(jìn)位+1的來(lái)的矾缓,所以對(duì)于上面這個(gè)二進(jìn)制师痕,將其轉(zhuǎn)為10進(jìn)制的過程如下:
    1. 最高位符號(hào)位為1,表示負(fù)數(shù)
    2. 既然是負(fù)數(shù)而账,最后一位不退位-1,得到:011 1101 1011 0010 0101 0001 1110 0110
    3. 取補(bǔ)碼:100 0010 0100 1101 1010 1110 0001 1001
    4. 表示為十進(jìn)制:-1112387097
  7. 至此因篇,就可以解釋為什么1596615447015.333 | 0 = -1112387097了泞辐。

為了驗(yàn)證上述過程,我們?cè)倥e一個(gè)例子:1590015447015.123 >> 0 = 877547495

  1. 1590015447015.123的二進(jìn)制表示為:10111001000110100010011100100111111100111.000111111
  2. 舍去其小數(shù)部分后竞滓,從后往前取32位為:00110100010011100100111111100111
  3. 最高位為0咐吼,正數(shù),直接轉(zhuǎn)為10進(jìn)制為:877547495

將將將將商佑!沒錯(cuò)的吧锯茄!所以JS的這個(gè)坑還真是。茶没。肌幽。 讓人Orz

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市抓半,隨后出現(xiàn)的幾起案子喂急,更是在濱河造成了極大的恐慌,老刑警劉巖笛求,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件廊移,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡探入,警方通過查閱死者的電腦和手機(jī)狡孔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蜂嗽,“玉大人苗膝,你說我怎么就攤上這事≈簿桑” “怎么了荚醒?”我有些...
    開封第一講書人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)隆嗅。 經(jīng)常有香客問我界阁,道長(zhǎng),這世上最難降的妖魔是什么胖喳? 我笑而不...
    開封第一講書人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任泡躯,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘较剃。我一直安慰自己咕别,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開白布写穴。 她就那樣靜靜地躺著惰拱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪啊送。 梳的紋絲不亂的頭發(fā)上偿短,一...
    開封第一講書人閱讀 51,604評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音馋没,去河邊找鬼昔逗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛篷朵,可吹牛的內(nèi)容都是我干的勾怒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼声旺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼笔链!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起腮猖,我...
    開封第一講書人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤卡乾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后缚够,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體幔妨,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年谍椅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了误堡。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雏吭,死狀恐怖锁施,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情杖们,我是刑警寧澤悉抵,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站摘完,受9級(jí)特大地震影響姥饰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜孝治,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一列粪、第九天 我趴在偏房一處隱蔽的房頂上張望审磁。 院中可真熱鬧,春花似錦岂座、人聲如沸态蒂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)钾恢。三九已至,卻和暖如春鸳址,著一層夾襖步出監(jiān)牢的瞬間瘩蚪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工氯质, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人祠斧。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓闻察,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親琢锋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子辕漂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355