contributed by < quantabase13
>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
bool is_ascii(const char str[], size_t size)
{
if (size == 0)
return false;
int i = 0;
while ((i + 8) <= size) {
uint64_t payload;
memcpy(&payload, str + i, 8);
if (payload & 0x8080808080808080/*MMM*/)
return false;
i += 8;
}
while (i < size) {
if (str[i] & 0x80)
return false;
i++;
}
return true;
}
is_ascii
的 while
loop 裡聲明了長度為64 bits 的 payload
,並且透過 memcpy
從地址 str + i
複製8個 bytes 到 payload。由於一個 char
長1個 byte,payload
裝有8個 bytes,因此對 payload
做 bitwise operation 可以一次檢測 8 個 char 是否屬於 ASCII code。
uint8_t hexchar2val(uint8_t in)
{
const uint8_t letter = in & 0x40/*MASK*/;
const uint8_t shift = (letter >> 3/*AAA*/) | (letter >> 6 /*BBB*/);
return (in + shift) & 0xf;
}
數字的 ASCII code 高4位為0011,大寫英文字母的高4位為 0100, 小寫英文字母的高4位為 0110。
於是取 MASk
為0x40的話,letter
在 in
為字母時數值為 0100 ,in
為數字時數值為 0。另外,不論大小寫字母,低四位皆是從0001開始遞增,因此要輸出其代表的十進位數值的話,只要將低四位+9即可(因為 a 表示十進位的10)。
整體程式碼的思路如下:
shift
值(0 => in
為數字, 9 => in
為字母)shift
的值
const uint32_t D = 3;
#define M ((uint64_t)(UINT64_C(0xFFFFFFFFFFFFFFFF/*XXX*/) / (D) + 1))
/* compute (n mod d) given precomputed M */
uint32_t quickmod(uint32_t n)
{ uint64_t quotient = ((__uint128_t) M * n) >> 64;
return n - quotient * D;
}
此題參考 jemalloc 原始碼中 include/jemalloc/internal/div.h和src/div.c 的註解,可以知道實現的思路(以下的除法是實際除法,不是 C 的 rounding 版本):
其中
由於
了解上述數學式後,就可以對此題的程式碼作相同的數學分析:
此處
其中
我們只需讓
固定
當
取
會發現
由於
因此就算
所以可以用
bool divisible(uint32_t n)
{
return n * M <= M - 1/*YYY*/;
}
因為
#include <ctype.h>
#include <stddef.h>
#include <stdint.h>
#define PACKED_BYTE(b) (((uint64_t)(b) & (0xff)) * 0x0101010101010101u)
/* vectorized implementation for in-place strlower */
void strlower_vector(char *s, size_t n)
{
size_t k = n / 8;
for (size_t i = 0; i < k; i++, s += 8) {
uint64_t *chunk = (uint64_t *) s;
if ((*chunk & PACKED_BYTE(0x80/*VV1*/)) == 0) { /* is ASCII? */
uint64_t A = *chunk + PACKED_BYTE(128 - 'A' + 0/*VV2*/);
uint64_t Z = *chunk + PACKED_BYTE(128 - 'Z' + (-1)/*VV3*/);
uint64_t mask = ((A ^ Z) & PACKED_BYTE(0x80/*VV4*/)) >> 2;
*chunk ^= mask;
} else
strlower(s, 8);
}
k = n % 8;
if (k)
strlower(s, k);
}
從程式碼的後半段開始倒著分析,可以發現關鍵在
uint64_t mask = ((A ^ Z) & PACKED_BYTE(0x80/*VV4*/)) >> 2;
這行,因為 0x80 >> 2 = 1 << 5,而大寫字母 A ^= (1 << 5) 就會轉為小寫。所以只要控制 (A ^ Z) 的值就能決定8個 char 中那一個要轉小寫。
PACKED_BYTE(0x80) =
1xxxxxxx、0xxxxxxx。
於是我們必須設定只有當 char 確實是大寫字母時, A 和 Z 的 most significant bit 會不同。
可以使用
的結果來判斷。只有當 char 為大寫字母時兩個數的 most significant bit 不同。所以我們令:
透過 (A ^ B)&0x80 來決定對應 mask 的其中8 bits。
以向量的形式表示,就能寫成:
uint64_t A = *chunk + PACKED_BYTE(128 - 'A' + 0/*VV2*/);
uint64_t Z = *chunk + PACKED_BYTE(128 - 'Z' + (-1)/*VV3*/);
uint64_t mask = ((A ^ Z) & PACKED_BYTE(0x80/*VV4*/)) >> 2;
int singleNumber(int *nums, int numsSize)
{
int lower = 0, higher = 0;
for (int i = 0; i < numsSize; i++) {
lower ^= nums[i];
lower &= ~higher/*KKK*/;
higher ^= nums[i];
higher &= ~lower/*JJJ*/;
}
return lower;
}
以下參考csdn yutianzuijin:
如果題目改成除了 Single Number 外每個數皆出現兩次,那只要這麼寫即可:
int singleNumber2(int *nums, int numsSize)
{
int lower = 0, higher = 0;
for (int i = 0; i < numsSize; i++) {
lower ^= nums[i];
}
return lower;
}
每個 bit 的狀態隨 input 的變化可以用以下真值表表示:
bit state | input | output |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 1 | 0 |
1 | 0 | 1 |
可以發現因為只有兩個狀態需要記錄(數出現0次或1次),所以只需要1個 bit 記錄1個 bit 的變化。 | ||
換成原題的話,有三個狀態(數出現0、1、2次),所以需要2個 bit 記錄狀態(00->01->10->00)。 | ||
記錄狀態的 bit 由higher 和 lower 構成,bit 隨input 的變化可以用以下真值表表示: | ||
higher | lower | input |
- | - | - |
0 | 0 | 0 |
0 | 0 | 1 |
0 | 1 | 0 |
0 | 1 | 1 |
1 | 0 | 0 |
1 | 0 | 1 |
lower_bit output 即是所求的 Single Number。 | ||
可以用以下邏輯表達式表達 lower_bit output: | ||
$$ | ||
\begin{aligned} | ||
lower&=lower\cdot\bar{higher}\cdot\bar{input}+\bar{lower}\cdot\bar{higher}\cdot{input}\ | ||
&=\bar{higher} \cdot(lower\cdot\bar{input}+\bar{lower}\cdot{input})\ | ||
&=\bar{higher}\cdot(lower\oplus{input}) | ||
\end{aligned} | ||
$$ | ||
另外,higher 更新時的所用的 lower 值為新的 lower 值。將新的 lower 值作為輸入,可以畫出以下真值表: | ||
higher | lower | input |
- | - | - |
0 | 0 | 0 |
0 | 1 | 1 |
0 | 1 | 0 |
0 | 0 | 1 |
1 | 0 | 0 |
1 | 0 | 1 |
可以用以下邏輯表達式表達 higher_bit output: | ||
$$ | ||
\begin{aligned} | ||
higher&=\bar{lower}\cdot{higher}\cdot\bar{input}+\bar{lower}\cdot\bar{higher}\cdot{input}\ | ||
&=\bar{lower} \cdot(higher\cdot\bar{input}+\bar{higher}\cdot{input})\ | ||
&=\bar{lower}\cdot(higher\oplus{input}) | ||
\end{aligned} | ||
$$ | ||
至此我們得出 higher 和 lower bit 的邏輯表達式,轉換成 C 程式後即得原問題的解。 |