小尾数转换为字符串


13

介绍

在使用BMP(位图)生成器的过程中,我遇到了将数字转换成小端十六进制字符串的问题。这是我用JavaScript创建的函数-但想知道小代码如何能类似地工作

let liEnd= num => num.toString(16).padStart(8,'0').match(/../g).reverse().join``;
console.log(liEnd(304767)) // 304767 dec = 0x4a67f hex

挑战

写函数,该函数将在输入上使用32位无符号整数,并生成具有小端序的8位十六进制字符串。做到这一点的示例算法:

  • 将numb转换为十六进制字符串,例如: 304767 -> '4a67f'
  • 添加填充零以获取8个字符的字符串: '0004a67f'
  • 将字符串拆分为四个2个字符的片段: '00','04','a6','7f'
  • 逆序排列 '7f','a6','04','00'
  • 拼接并返回结果: '7fa60400'

输入和输出示例

输入数字(或带dec数字的字符串)在的左侧->,输出十六进制字符串在右侧

2141586432 -> 0004a67f
304767     -> 7fa60400

Answers:


7

05AB1E10 9字节

žJ+h¦2ôRJ

在线尝试!

-1个字节受果冻答案的启发。

žJ+   add 2^32 to input
h     convert to hex
¦     drop leading 1
2ô    split in groups of 2
R     reverse groups
J     and join them

6

Python 3,37个字节

lambda n:n.to_bytes(4,"little").hex()

在线尝试!

基于算术的递归解决方案(50 49字节,也适用于Python 2

f=lambda n,i=4:i*'1'and"%02x"%(n%256)+f(n>>8,i-1)

在线尝试!

-1字节感谢@JonathanAllan


我想说将递归代码作为Python 2条目提交:)
Jonathan Allan

f=lambda n,i=4:i*'1'and'%02x'%(n%256)+f(n>>8,i-1)保存一个字节:)
乔纳森·艾伦,

@JonathanAllan谢谢。我对所有Python 2技巧都不熟悉,但是看不到如何使其更短。
乔尔,

不会,但是37号将无法在py 2中使用
乔纳森·艾伦

是的 其中一些内置仅适用于Python-3。
乔尔,

6

R54 53字节

format.hexmode(scan()%/%256^(0:3)%%256%*%256^(3:0),8)

在线尝试!

每2个字符的组实际上是以256 scan()%/%256^(0:3)%%256为底的数字的十六进制表示形式。转换为以4反转的4进制数为基数256的数字, ...%*%256^(3:0)将它们连接为一个整数,然后format.hexmode(...,8)将该数字转换为以8位数表示的十六进制表示形式。


5

JavaScript(ES7), 59  57字节

字符串操作。

n=>(n+2**32).toString(16).match(/\B../g).reverse().join``

在线尝试!

怎么样?

我们首先将ñ+232转换为十六进制,以确保包括所有前导0

(304767 + 2**32).toString(16) // --> '10004a67f'

在线尝试!

我们使用正则表达式/\B../g来匹配所有2位数字的组,而忽略了前导1个感谢\B(非单词border)。

'10004a67f'.match(/\B../g) // --> [ '00', '04', 'a6', '7f' ]

在线尝试!

我们reverse()join()得到最后的字符串。


JavaScript(ES6),61个字节

递归函数。

f=(n,k=4)=>k?[(x=n&255)>>4&&'']+x.toString(16)+f(n>>8,k-1):''

在线尝试!


⭐-您获得最佳答案的明星-我喜欢它,虽然简短,但仍然干净且“人性化” :)
KamilKiełczewski19年


5

C#(Visual C#交互式编译器),54字节

x=>$"{(x=x>>16|x<<16)>>8&16711935|(x&16711935)<<8:x8}"

@PeterCordes节省了4个字节

在线尝试!

说明

x=>                                                    //Lambda taking in an uint
     (x=x>>16|x<<16)                                   //Swap the first two and the last two bytes of the uint (0x7fa60400 -> 0x04007fa6)
                    >>8&16711935|(x&16711935)<<8       //Swap each pair of bytes in every group of 2 bytes (0x04007fa6 -> 0x0004a67f)
  $"{                                           :x8}"  //Format as hex string, padded with leading zeroes to length 8

如果遮罩之前移动可以将4278255360遮罩常数缩小到167119350xff00ff)吗?还是需要额外的费用?而且,如果不是,则长度相同,但对人类更有意义。0xff00ff00
彼得·科德斯

@PeterCordes它还有一个额外的优势,就是能够删除方括号,因为>>它的优先级高于&,总共节省了4个字节。谢谢!
无知的体现,

凉。在您的“说明”部分中,建议使用十六进制编写常量。
彼得·科德斯

4

Japt -P,10 字节

sG ùT8 ò w

尝试一下

sG ùT8 ò w     :Implicit input of integer
s              :Convert to string
 G             :  In base-16
   ù           :Left pad
    T          :  With 0
     8         :  To length 8
       ò       :Split into 2s
         w     :Reverse
               :Implicitly join and output

怎么-P办?
SS安妮

🚀您的答案在顶部(您能否添加解释?)
KamilKiełczewski19年

@ JL2210 来自docs:“ -P:如果输出是数组,则输出不带分隔符(即与结合P)。 ”。因此,该标志用于隐式而不是显式连接以节省字节。:)
凯文·克鲁伊森

2
@KamilKiełczewski,添加了说明。
毛茸茸的


4

Python 2,43个字节

lambda n:[("%08x"%n)[i^6]for i in range(8)]

在线尝试!

-4字节归功于benrg

输出字符列表。通过按顺序检索索引处的输入的十六进制数字进行计算6, 7, 4, 5, 2, 3, 0, 1


2
[i^6]for i in range(8)保存一些字节。
benrg

是否允许输出列表而不是字符串?
Qwertiy

输出为列表并不真正符合问题imo
qwr

3

C(gcc)不区分字节序,无标准库,92 91字节

h(n)是一位整数integer-> hex helper函数。
f(x,p)接受一个整数和一个char[8]指针。结果是8个字节的char数据。(除非呼叫者这样做,否则不以 0结尾。)

假设:ASCII字符集。2的补码,int因此右移最终会降低符号位,如果设置了高位,则转换uint32_tint不会改变位模式。 int至少为32位。(Wider可能会让它在1的补码或符号幅度C实现上工作)。

非假设:有关实现字节顺序或的符号的任何信息char

i;h(n){n&=15;return n>9?n+87:n+48;}f(x,p)char*p;{for(i=5;--i;x>>=8)*p++=h(x>>4),*p++=h(x);}

在线尝试! 包括测试调用方,该调用方printf("%.8s\n", buf)用于打印输出缓冲区,而无需对其进行0终止。

取消高尔夫:

int h(n){n&=15;return n>9 ? n+'a'-10 : n+'0';}      // single digit integer -> hex

int i;
void ungolfed_f(x,p)char*p;{
    for(i=5; --i; x>>=8)   // LS byte first across bytes
        *p++=h(x>>4),      // MS nibble first within bytes
        *p++=h(x);
}

n&=15;室内做h(x)收支平衡。那里有6个字节,而每个字节只有3个字节,&15以隔离两个呼叫站点的低字节。

,是一个顺序点(或现代术语中的等效点),因此*p++= stuff在被,运算符分隔时,在一个语句中执行两次是安全的。

>>在有符号整数上,实现定义为算术或逻辑。GNU C将其定义为算术2的补码。但是,在任何2的补码机上都没关系,因为我们从不看待移入的0或符号位的副本。原始的MSB最终将不变地下降到低字节。符号/幅度不是这种情况,我不确定1的补数。

因此,这可能仅适用于2的补码C实现。(或者哪里int更宽的超过32位,从而第31位是幅度的只是一部分。)无符号- >符号转换也munges的位图案为负整数,因此&15在一个int只能提取物对2的补码的原始无符号值的半字节。同样,除非int更宽的超过32位,所以所有输入都是非负的。

高尔夫版本的UB从非空洞函数的末尾掉落。不返回值,只是避免声明它void而不是default int。现代编译器将在启用优化的情况下打破这一点。


动机:我正在考虑一个x86或ARM Thumb asm答案,认为用C手动完成它可能很有趣,也许以编译器生成的asm为起点。请参见/programming/53823756/how-to-convert-a-number-to-hex,了解速度高效的x86 asm,包括仅2条指令的AVX512VBMI版本(但需要用于vpmultishiftqb和vpshufb的控制向量)因此对于高尔夫运动来说并不是很好)。通常,SIMD会花一些额外的工作才能在Little-endian x86上将字节反转为打印顺序,因此此字节反转的十六进制输出实际上比正常情况下更容易。


其他想法

我考虑过char*在小端字节C实现(例如x86或ARM)上通过引用获取整数并使用遍历其字节。但是我认为这不会节省太多。

使用sprintf在同一时间做1个字节,64个高尔夫球后字节:

int i;
void f(x,p)char*p;{
        for(i=4;sprintf(p,"%.2x",x&255),--i;x>>=8)
                p+=2;
}

但是,如果我们使用类似printf的函数,我们不妨进行字节交换并对@ JL2210的答案%x进行整个打印


⭐-您的星级获得了不错的答案
KamilKiełczewski19年

3

x86 SIMD机器代码(AVX512-VBMI),36个字节

(其中的16个字节是十六进制查找表)

该函数接受一个整数,xmm0并在中返回8个字节的ASCII字符数据xmm0,以便调用者将其存储在所需的任何位置。(例如,在与属性字节交织后进入视频内存,或进入正在构造的字符串中,等等)

在C中,__m128i retval = lehex(_mm_cvtsi32_si128(x))按x86-64 System V调用约定或MS Windows进行调用vectorcall

# disassembly with machine-code bytes (the answer) and NASM source code.
0000000000401000 <lehex>:
  401000:       c5 f1 72 d0 04          vpsrld      xmm1, xmm0, 4         ; AVX1
  401005:       c5 f1 60 c8             vpunpcklbw  xmm1, xmm1, xmm0      ; AVX1
  401009:    62 f2 75 08 8d 05 01 00 00 00 vpermb  xmm0, xmm1, [rel .hex_lut]
  401013:       c3                      ret    

0000000000401014 <lehex.hex_lut>:
  401014:     30 31 ...  61 62 ...     .hex_lut:  db "0123456789abcdef"

总计= 0x24 = 36个字节。

请参阅如何将数字转换为十六进制?SO的工作原理。 (将SSE2用于shift / punpck,然后vpermb保存我们需要的工作pshufb。AVX1而不是SSE2 / SSSE3也避免了movaps寄存器复制。)

请注意,punpcklbw按此顺序使用源操作数将在最低字节元素中为我们提供低输入字节的最高有效半字节,然后在最低源字节中为最低有效半字节。(在该SO答案中,bswap在输入上使用a来获得仅具有SSE2的标准打印顺序的结果。但是在这里,我们想要该顺序:每个字节内的低位元素高四位,但仍然是低端字节序。)

如果我们有更多的数据常量,则可以通过mov edx, imm32使用一个然后使用[rdx+16]或任何寻址模式来节省寻址模式空间。或者vpbroadcastb xmm0, [rdx+1]

但是我认为16字节的十六进制LUT + vpermb仍然比实现n>9 : n+'a'-10 : n+'0'条件更好:AVX512BW字节掩码(比较起来为mask vpaddb,,merge-masked vpaddb)需要3个常量和至少3条指令,而AVX1或SSE2需要更多的常数。(有关SOSE2版本的信息,请参见如何在SO上将数字转换为十六进制)。而且,每条AVX512BW指令的长度至少为6个字节(4字节EVEX +操作码+ modrm),并且在寻址模式下会有位移。

实际上,这至少需要4条指令,因为在比较之前,我们需要使用清除高垃圾andps(或vpandd使用4字节广播内存操作数的EVEX )。而且每一个都需要不同的向量常数。AVX512具有广播存储器操作数,但仅适用于32位及以上的元素。例如EVEXvpaddb的最后一个操作数是xmm3/m128,而不是xmm3/m128/m8bcst。(作为加载uop的一部分,英特尔的加载端口只能免费进行32位和64位广播,因此英特尔设计了AVX512BW来反映这一点,并且根本无法编码字节或字广播的存储操作数,而不是给它们选择进行dword广播,因此您仍然可以将常量压缩为4个字节:/。)

我使用AVX512VBMIvpermb而不是SSSE3 / AVX1的pshufb原因有两个:

  • vpermb忽略选择器的高位。 (v)pshufb根据控制向量的高位将字节清零,并且可能需要额外的空间pandandps实际隔离半字节。对于XMM / 16字节大小,vpermb仅查看随机控制元素的低4位,即[3:0]操作”部分中Intel表示法的位。
  • vpermb可以将要改组的数据(查找表)用作内存操作数。 (v)pshufb的xmm / mem操作数是随机播放控制向量。

请注意,AVX512VBMI仅在CannonLake / Ice Lake上可用,因此您可能需要像英特尔的SDE这样的模拟器来对其进行测试。


⭐ -你得到不错的复星
卡米尔Kiełczewski

@KamilKiełczewski:哈哈,谢谢。将数字有效地转换为十六进制是我最喜欢的事情之一。这是一个很好的用例,用于一些巧妙的技巧和位操作。
彼得·科德斯

3

Scala58 40 36字节

"%08X"format Integer.reverseBytes(_)

在线尝试!

仍使用内置函数来反转的字节Int,但用于format将格式化Int为十六进制。无需致电toHexString

删除上的括号format。现在,这意味着可以使用隐式接受参数_


2

第四(gforth)52 51 40字节

: f hex 0 4. do <# # # 0. #> type loop ;

在线尝试!

代码说明

: f           \ start a new word definition
  hex         \ set the current base to base 16
  0           \ convert the input number to a double-cell integer
  4. do       \ start a counted loop from 0 to 3
    <# # #    \ start a formatted numeric string and move last 2 digits to format area
    0.        \ move remaining digits down the stack
    #>        \ delete top two stack value and convert format area to string
    type      \ output string
  loop        \ end loop
;             \ end word definition



2

Excel,91字节

=RIGHT(DEC2HEX(A1,8),2)&MID(DEC2HEX(A1,8),5,2)&MID(DEC2HEX(A1,8),3,2)&LEFT(DEC2HEX(A1,8),2)

2

K412 11个字节

解:

,/$|4_0x0\:

例子:

q)k),/$|4_0x0\:304767
"7fa60400"
q)0W
"0004a67f"

说明:

问题几乎完全是这样:

,/$|4_0x0\: / the solution
      0x0\: / split to bytes
    4_      / drop first 4 bytes
   |        / reverse
  $         / convert to string
,/          / flatten

笔记:

  • -1个字节,因为默认情况下,K4数字为long(64位),因此丢弃4个字节(32位)

answer您的答案是最重要的
KamilKiełczewski19年

2

PHP,31字节

<?=unpack(H8,pack(V,$argn))[1];

在线尝试!

利用PHP的packunpack,我将“ 32位小尾数字节顺序”格式(V)的无符号输入打包为二进制字符串,然后使用“十六进制字符串,高半字节优先”格式(H)对其进行解压缩并打印结果。

这似乎是罕见的情况,其中PHP的内置功能实际上比实现简单算法要短!


在大多数PHP项目中,PHP的pack()/ unpack()函数对于您需要的0次而言真是太棒了。恭喜,您找到了它们的用途!
640KB

1

木炭,11字节

⪫⮌⪪﹪%08xN²ω

在线尝试!链接是详细版本的代码。说明:

        N   Input as a number
   ﹪%08x    Format using literal string
  ⪪      ²  Split into pairs of characters
 ⮌          Reverse
⪫         ω Join
            Implicitly print

19个字节,不求助于Python格式:

⪫…⮌⪪⍘⁺X²¦³⁶N¹⁶¦²¦⁴ω

在线尝试!链接是详细版本的代码。说明:

           N        Input as a number
     ⁺              Plus
       ²            Literal 2
      X             To power
         ³⁶         Literal 36
    ⍘               Convert to base
            ¹⁶      Literal 16
   ⪪           ²    Split into pairs of digits
  ⮌                 Reverse the list
 …               ⁴  Take the first 4 pairs
⪫                 ω Join together
                    Implicitly print

answer您的答案是最重要的
KamilKiełczewski19年


1

J,10个字节

8{._1{3!:3

在线尝试!

怎么样

3!:3是十六进制表示形式的J“外国合”,在此记录。也就是说,它是转换为十六进制的内置函数。但是,它输出的并不是我们想要的。例如,正在运行:

3!:3 (304767)

产生:

e300000000000000
0400000000000000
0100000000000000
0000000000000000
7fa6040000000000

我上面链接到的文档页面解释了其他各行的含义。无论如何,很明显我们想要最后一行的前8个字符。

_1{ 得到最后一行。

8{. 获取它的前8个字符。


answer您的答案是最重要的
KamilKiełczewski19年


1

Windows Batch,90个字节

@for /l %%x in (24,-8,0)do @set/aa=%1^>^>%%x^&255&cmd/cexit !a!&<nul set/p=!=exitcode:~-2!

使用/ v运行命令行以启用延迟的扩展。


1

x86 32位机器码,24 21字节

changelog:-3个字节:用@peter ferrie用DAS hack替换标准的add / cmp / jbe / add

64位:仍为24个字节。长模式删除了DAS操作码。
16位模式:默认操作数大小为16位,但问题说明本质上为32位。包括硬编码的8个十六进制数字。


字节反转,bswap然后按标准顺序手动int-> hex(最重要的半字节在前,以升序将十六进制数字写入char输出缓冲区)。这避免了展开循环以在字节vs.内的半字节之间切换顺序。跨字节。

可以void lehex(char buf[8] /*edi*/, uint32_t x /*esi*/);像x86-64 System V一样调用,但不能在64位模式下使用。(它需要EDI中的输出指针用于stosb。输入号可以位于ECX或EAX以外的任何寄存器中。)

     1                             lehex:
     2 00000000 0FCE                   bswap  esi
     3 00000002 6A08                   push   8            ; 8 hex digits
     4 00000004 59                     pop    ecx
     5                             .loop:                ;do{
     6 00000005 C1C604                 rol    esi, 4       ; rotate high nibble to the bottom
     7                             
     8 00000008 89F0                   mov    eax, esi
     9 0000000A 240F                   and    al, 0x0f     ; isolate low nibble
    10 0000000C 3C0A                   cmp al, 10          ; set CF according to digit <= 9
    11 0000000E 1C69                   sbb al, 0x69        ; read CF, set CF and conditionally set AF
    12 00000010 2F                     das                 ; magic, which happens to work
    13                             
    14 00000011 AA                     stosb               ; *edi++ = al
    15 00000012 E2F1                   loop  .loop       ; }while(--ecx)
    16                             
    17 00000014 C3                     ret

大小= 0x15 = 21字节

TIO FASM 32位x86测试用例,带有一个asm调用程序,该调用程序使用write系统调用在两次调用两次以将2个字符串附加到缓冲区中之后写入输出。测试所有十六进制数字0..F,包括数字与字母之间的边界处的9和A。

DAS黑客 - 86具有半进位标志,用于开展低四位的。对于打包BCD之类的东西很有用,例如DAS指令,打算在减去两个2位数的BCD整数后使用。由于AL的低位字节不在0-9范围内,因此我们绝对在这里滥用它。

注意手册中“操作”部分的“ if (old_AL > 99H) or (old_CF = 1)THEN” AL ← AL − 60H;部分;sbb 始终在此处设置CF,以便始终发生该部分。那和大写字母的ASCII范围是促使选择sub al, 0x69

  • cmp 0xD, 0xA 没有设置CF
  • sbb 0xD - 0x69包装为AL = 0xA4作为DAS的输入。(并设置CF,清除AF)
  • DAS的第一部分没有AL-= 6(因为4> 9为false且AF = 0)
  • 第二部分为AL-= 0x60,剩下的0x44是的ASCII码'D'

与数字:

  • cmp 0x3, 0xA 设置CF
  • sbb 3 - 0x69 - 1= AL = 0x99并设置CF和AF
  • DAS的第一部分中没有AL-= 6(9> 9为假,但设置了AF),保留0x93
  • 第二部分AL-= 0x60,保留0x33,即的ASCII码'3'

0x6a在SBB中减去将为每个<= 9的数字设置AF,因此所有数字都遵循相同的逻辑。并保留每个字母十六进制数字清除它。即正确利用DAS的9 / A拆分处理。


通常(为了提高性能),您将使用查找表进行标量循环,或者可能使用无分支2x leacmp/cmov条件加法。但是2字节al, imm8指令是代码大小的一大胜利。


x86-64版本versionand al, 0xf和之间只是不同的部分stosb

;; x86-64 int -> hex  in 8 bytes
    10 0000000C 0430                   add    al, '0'
    11 0000000E 3C39                   cmp    al, '9'
    12 00000010 7602                   jbe  .digit
    13 00000012 0427                     add    al, 'a'-10 - '0'     ; al =  al>9 ? al+'a'-10 : al+'0'
    14                             .digit:

请注意,add al, '0' 总是运行,而条件增加只会增加之间的区别'a'-10'0',使其只是if代替if/ else

经过测试,可以使用与C答案相同的main调用方(使用和)进行工作。char buf[8]printf("%.8s\n", buf)


你可以在这里创建在线工作片段吗?
卡米尔·基茨凯夫斯基(KamilKiełczewski),

@KamilKiełczewski:TIO使(AFAIK)不可能用C编写调用程序来测试asm函数,因此我通常不必理会,但是请确保,因为您提出了要求,并且sys_write可以轻松输出定长字符串。哦,有趣的是,我没有意识到TIO上的FASM可以让您创建32位可执行文件,而NASM不尊重它-felf32。无论如何,我还是更喜欢x86-64,并且此答案不会保存32位代码中的任何字节。
彼得·科德斯

⭐ -你得到不错的复星
卡米尔Kiełczewski

1
@ JL2210:你的意思是sprintf?除了基于格式字符串的函数,我不认为libc有任何方便的int-> string函数,只有strtoul这样的string-> int。但是,是的,如果您能找到某种方法为动态库中的函数的GOT条目计数字节(除了6字节的call [rel printf wrt ..got]调用站点),则bswap / printf可能会更短。最小化的静态链接的可执行文件可以比动态文件小得多,至少在ld使用默认默认设置生成时。但是我认为静态链接而不计算其代码大小并不合理。
彼得·科德斯

1
@ JL2210:请记住,这是一个x86 机器代码答案,而不是asm文本源大小。我以前的机器代码答案中没有使用过libc函数,只有Linux系统调用(例如在Fibonacci中)和IDK中,我将如何计算成本或者我是否想编写带有libc的机器代码答案。在x86机器代码中有一些用例,其中libc不可用,例如在引导加载程序中。
彼得·科德斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.