Soru Swift'deki bir UInt16'ya iki bayt UInt8 dizisini dönüştürme


Swift ile bir uint8_t dizisinden bir tamsayıya bayt dönüştürmek istiyorum.

"C" Örnek:

char bytes[2] = {0x01, 0x02};
NSData *data = [NSData dataWithBytes:bytes length:2];
NSLog(@"data: %@", data); // data: <0102>

uint16_t value2 = *(uint16_t *)data.bytes;
NSLog(@"value2: %i", value2); // value2: 513

Hızlı girişimi:

let bytes:[UInt8] = [0x01, 0x02]
println("bytes: \(bytes)") // bytes: [1, 2]
let data = NSData(bytes: bytes, length: 2)
println("data: \(data)") // data: <0102>

let integer1 = *data.bytes // This fails
let integer2 = *data.bytes as UInt16 // This fails

let dataBytePointer = UnsafePointer<UInt16>(data.bytes)
let integer3 = dataBytePointer as UInt16 // This fails
let integer4 = *dataBytePointer as UInt16 // This fails
let integer5 = *dataBytePointer // This fails

Swift'deki bir UInt8 dizisinden bir UInt16 değeri oluşturmak için doğru sözdizimi veya kod nedir?

NSData sürümüyle ilgileniyorum ve geçici bir dizi kullanmayan bir çözüm arıyorum.


25
2017-08-12 14:35


Menşei


Şüphesiz Swift gibi yeni bir dil, NSData tamsayı değişkenleri. Aradığım şey bu. - zaph
[0x01, 0x02] 0x0102 veya 0x0201'in uint16'sı mı? Bu, işlemcinin endianitesine bağlıdır. Java'da tanımlanmıştır. C açık. Swift'de bilmiyorum. Numara *(uint16)(uint8[2]) adres hizalamalarından da muzdariptir. - Joop Eggen
@JoopEggen Endian-ness sorun değil. - zaph
Daha da iyisi, verilere endeksleme yapmaktır: let u16 = UnsafePointer<UInt16>(data.bytes)[index] - zaph


Cevaplar:


Eğer gitmek istiyorsan NSData o zaman böyle çalışacaktı:

let bytes:[UInt8] = [0x01, 0x02]
println("bytes: \(bytes)") // bytes: [1, 2]
let data = NSData(bytes: bytes, length: 2)
print("data: \(data)") // data: <0102>

var u16 : UInt16 = 0 ; data.getBytes(&u16)
// Or:
let u16 = UnsafePointer<UInt16>(data.bytes).memory

println("u16: \(u16)") // u16: 513

Alternatif:

let bytes:[UInt8] = [0x01, 0x02]
let u16 = UnsafePointer<UInt16>(bytes).memory
print("u16: \(u16)") // u16: 513

Her iki değişken de baytların ana bayt düzeninde olduğunu varsayar.

Swift 3 (Xcode 8) için güncelleştirme:

let bytes: [UInt8] = [0x01, 0x02]
let u16 = UnsafePointer(bytes).withMemoryRebound(to: UInt16.self, capacity: 1) {
    $0.pointee
}
print("u16: \(u16)") // u16: 513

40
2017-08-12 14:51



İlgileniyorum NSData sürüm ama bir geçici dizi kullanmayan bir çözüm arıyorum. - zaph
@Zaph: Hangi sıcaklık dizisi? belki let u16 = UnsafePointer<UInt16>(data.bytes).memory aradığınız nedir? - Martin R
Tam olarak aradığım şey bu! Teşekkürler! let u16 = UnsafePointer<UInt16>(data.bytes).memory - zaph
@Zaph, aslında daha iyi performansa sahip olduğunuzdan ve aslında buna ihtiyacınız olduğundan emin olun. Güvenli olmayan kod kullanmak, sonuçta daha kötü bir performansa sahip olabilir, çünkü derleyici kodunuzun ne yaptıklarından habersiz olabilir ve kodunuzu daha verimli bir şekilde değiştiremez. - pqnet
@pqnet Bu seviyede performans konusunda endişe duymuyorum, ölçümlerin bir ihtiyaç gösterdiği ve sorunun nerede olduğu belirlendiğinde performansın en iyi şekilde ele alınması. Güvende olduğu gibi, emin bir şey istiyorum, lütfen böyle bir çözüm sağlayın. Bu durumda ve aynı sınıftaki birçok ham veri baytının, JSON, XML veya başka bir üst düzey biçimlendirme olmadan alındığını unutmayın. - zaph


Ne dersin

let bytes:[UInt8] = [0x01, 0x02]
let result = (UInt16(bytes[1]) << 8) + UInt16(bytes[0])

Bir döngü ile, bu daha büyük bayt dizilerine kolayca genelleşir ve okunabilirlik için bir işlevle sarılabilir:

let bytes:[UInt8] = [0x01, 0x02, 0x03, 0x04]

func bytesToUInt(byteArray: [UInt8]) -> UInt {
  assert(byteArray.count <= 4)
  var result: UInt = 0
  for idx in 0..<(byteArray.count) {
    let shiftAmount = UInt((byteArray.count) - idx - 1) * 8
    result += UInt(byteArray[idx]) << shiftAmount
  }
  return result
}

println(bytesToUInt(bytes))    // result is 16909060

5
2017-08-12 14:57



Evet ve let b:UInt16 = UInt16(dataBytePointer[0]) * 256 + UInt16(dataBytePointer[1]) ayrıca çalışır. Çok inelegant ve UInt32 için daha da kötü görünmeye başlıyor. - zaph
UInt32 için neden daha kötü? Dizide döngü yaparak ve vardiya tutarını geçerli dizine göre ölçekleyerek çoklu baytları birleştirebilirsiniz. - pjs
Daha kötüsü, ifadenin daha uzun ve daha karmaşık hale gelmesi nedeniyle. - zaph
Sadece 4'e kadar giderseniz genelleme yapmaya gerek yok. Sadece bir satırdaki formülü yazarak vardiyaları elle yazabilirsiniz. Genel bir formda ısrar ederseniz, döngü yerine tekrarlama kullanın, daha iyi görünecektir. - pqnet
@pqnet: UInt16, UInt32 veya UInt64 için sırasıyla 2, 4 veya 8'e genelleştirmenin kolay olduğunu sevdim. Ayrıca, döngü / fonksiyonun uygulanması, Zaph'ın, desen çok net olduğunda, neredeyse tek taraflı olarak otomatikleştirilebileceği yönündeki bir durumun daha kötü olduğuna dair bir yanıt olarak düşünmüştüm. Ben şahsen NSData çözümünü temel almak için bir tercih belirtilmiş olsa da, her şeyden önce bir Swift çözümünü tercih ediyorum. Ben temel dilde tutmak ve güvenli olmayan işaretçi yapıları kaçınarak hayranıyım. - pjs


Hızlı yazım sözünü bilmiyorum ama ne gibi bir şey var:

let a:UInt16 = UInt16(bytes[0]) * 256 + UInt16(bytes[1])

3
2017-08-12 14:47



Kapat, bu işe yarar: let a:UInt16 = UInt16(bytes[0]) * 256 + UInt16(bytes[1]). - zaph
@Zaph Kodunuzu kopyalayacağım, eminim ki benimkinden daha doğru - pqnet


Baytlar bir NSData yapabileceğiniz nesne (varsayalım data:NSData):

var number: UInt16 = 0
data.getBytes(&number, length: sizeof(UInt16))

getBytes yöntem bellek konumunda iki bayta kadar yazıyor number (C'ye benzer memcpy. Bu, uygulamanızı çökertmezse data yeterli bayt yok.

(Düzenle: tampon başlangıcından başlayarak aralık kullanmanıza gerek yok)


3
2017-08-13 19:40



Swift'in güvenli ve doğrudan gibi işlemlere izin vereceğini umuyoruz, İsteğe bağlı söyleyebilir misiniz? Bu hala bir tane var olan baytların 0 olup olmadığını ya da sadece bir tane ya da hiç olmadığını merak ediyor. Yani bir uzunluk kontrolü hala gerekli. İlk önce uzunluk kontrolünü yap ve sonra bırak u16 = UnsafePointer<UInt16>(data.bytes)[index] ayrıca güvenlidir. Bir kez uzunluk kontrolü yaptıktan sonra, sadece indeks tarafından verilere daha fazla endekslenebilir. - zaph
@Zaph ne olursa olsun, senin kodun. Bunun sorumlusu ben değilim - pqnet
Üzgünüm, okuma ve anlamadığım için benim hatam. - zaph
@Zaph yanlış anlama iki kişinin hatasıdır. Üzgünüm gerçekten beni oraya kandırdığını düşünüyordum - pqnet


Martin R'nin yanıtı harika ve güzel bir şekilde beta 6 için güncellendi. Ancak, tamponunuzun başlangıcında olmayan baytlara ulaşmanız gerekiyorsa önerilen withMemoryRebound yöntem, yeniden adlandırılacak bir aralık sunmuyor. Buna çözümüm, ör. Bir diziden ikinci UInt16'yı seçin:

var val: UInt16 = 0
let buf = UnsafeMutableBufferPointer(start: &val, count: 1)
_ = dat.copyBytes(to: buf, from: Range(2...3))

2
2017-08-18 12:12





Küçük endian kodlaması varsayarsak.

UInt16'ya [UInt8] 'den dönüştürmek için,

var x: [UInt8] = [0x01, 0x02]
var y: UInt16 = 0
y += UInt16(x[1]) << 0o10
y += UInt16(x[0]) << 0o00

UInt32'ye dönüştürme için, bu model

var x: [UInt8] = [0x01, 0x02, 0x03, 0x04]
var y: UInt32 = 0
y += UInt32(x[3]) << 0o30
y += UInt32(x[2]) << 0o20
y += UInt32(x[1]) << 0o10
y += UInt32(x[0]) << 0o00

Vites miktarının sekizli gösterimi, kaç baytın kaydırıldığına dair güzel bir gösterge verir (8, 0o10 olur, 16, 0o20 olur vb.).

Bu UInt16 için aşağıya indirilebilir:

var x: [UInt8] = [0x01, 0x02]
let y: UInt16 = reverse(x).reduce(UInt16(0)) {
    $0 << 0o10 + UInt16($1)
}

ve UInt32 için aşağıdakine:

var x: [UInt8] = [0x01, 0x02, 0x03, 0x04]
let y: UInt32 = reverse(x).reduce(UInt32(0)) {
    $0 << 0o10 + UInt32($1)
}

İndirgenmiş sürüm ayrıca UInt64 için de çalışır ve ayrıca bayt kodlamanın tüm baytları kullanamadığı değerleri kullanır; [0x01, 0x02, 0x03]


0
2018-03-15 08:43



Bu 2049'u üretir, ancak doğru sonuç 513 olmalıdır. 010 ila 8'i değiştirmek mevcut cevabı üretir. 010, baz 10, 0o10, baz 8'dir. Ayrıca aşırı derecede akıllı olsa da, aynı zamanda pahalıdır ve ne olduğunu görmek için biraz zaman alır. - zaph
Cevabı düzeltin, böylece şimdi sekizlik gösterimi doğru şekilde kullanıyor. Onu kaçırdığımı söylediğin için teşekkürler. - Etan
Sekizli? Neden sekizli, sadece belirsiz olmak için? 8 daha duyarlı / anlaşılabilir değil mi? Tek şey daha karanlık, Tar'da görüldüğü gibi sekizli asciidir. - zaph
Cevabı niçin açıklamak için güncelledim. - Etan
Sekizli kullanarak haklı çıkarmak için tüm bunlardan geçmeniz gerekiyorsa, muhtemelen akıllıdır. En iyi kod ortalama okuyucu için açıktır. - zaph


Swift 3 veya daha sonra baytları dönüştürebilirsiniz [UInt8] için Data ve al UInt16 kullanarak değer withUnsafeBytes { $0.pointee }

Swift 3 veya üstü

extension Data {
    var uint16: UInt16 {
        return withUnsafeBytes { $0.pointee }
    }
}

extension Array where Element == UInt8 {
    var data: Data { return Data(self) }
}

let bytes: [UInt8] = [1, 2]
let uint16 = bytes.data.uint16
print(uint16) // 513

0
2017-12-12 02:57