// Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. #ifndef MFEM_HASH #define MFEM_HASH #include "../config/config.hpp" #include "array.hpp" #include "globals.hpp" #include #include namespace mfem { /** A concept for items that should be used in HashTable and be accessible by * hashing two IDs. */ struct Hashed2 { int p1, p2; int next; }; /** A concept for items that should be used in HashTable and be accessible by * hashing four IDs. */ struct Hashed4 { int p1, p2, p3; // NOTE: p4 is neither hashed nor stored int next; }; /** HashTable is a container for items that require associative access through * pairs (or quadruples) of indices: * * (p1, p2) -> item * (p1, p2, p3, p4) -> item * * An example of this are edges and faces in a mesh. Each edge is uniquely * identified by two parent vertices and so can be easily accessed from * different elements using this class. Similarly for faces. * * The order of the p1, p2, ... indices is not relevant as they are sorted * each time this class is invoked. * * There are two main methods this class provides. The Get(...) methods always * return an item given the two or four indices. If the item didn't previously * exist, the methods creates a new one. The Find(...) methods, on the other * hand, just return NULL or -1 if the item doesn't exist. * * Each new item is automatically assigned a unique ID - the index of the item * inside the BlockArray. The IDs may (but need not) be used as p1, p2, ... of * other items. * * The item type (T) needs to follow either the Hashed2 or the Hashed4 * concept. It is easiest to just inherit from these structs. * * All items in the container can also be accessed sequentially using the * provided iterator. * * Notes: * The data structure and implementation is based on a BlockArray which * provides an efficient item storage that avoids heap fragmentation, and * index-based item access. The hash table implemented on top of the * BlockArray provides fast associative (key -> value) access by grouping * items into bins (buckets) of O(1) size. * - "id" denotes the index of an item in the underlying BlockArray, * - "idx" denotes the index of a bin, determined by hashing a key with * the function `Hash`. */ template class HashTable : public BlockArray { protected: typedef BlockArray Base; public: /** @brief Main constructor of the HashTable class. @param[in] block_size The size of the storage blocks of the underlying BlockArray. @param[in] init_hash_size The initial size of the hash table. Must be a power of 2. */ HashTable(int block_size = 16*1024, int init_hash_size = 32*1024); /// @brief Deep copy HashTable(const HashTable& other); /// @brief Copy assignment not supported HashTable& operator=(const HashTable&) = delete; ~HashTable(); /** @brief Item accessor with key (or parents) the pair 'p1', 'p2'. Default construct an item of type T if no value correspond to the requested key. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @return The index "id" of the key in the BlockArray. @warning This method should only be called if T inherits from Hashed2. */ T* Get(int p1, int p2); /** @brief Item accessor with key (or parents) the quadruplet 'p1', 'p2', 'p3', 'p4'. The key 'p4' is optional. Default construct an item of type T if no value corresponds to the requested key. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @param[in] p3 Third part of the key. @param[in] p4 Fourth part of the key (optional). @return The index "id" of the key in the BlockArray. @warning This method should only be called if T inherits from Hashed4. */ T* Get(int p1, int p2, int p3, int p4 = -1 /* p4 optional */); /// Get id of item whose parents are p1, p2... Create it if it doesn't exist. /** @brief Get the "id" of an item, this "id" corresponding to the index of the item in the underlying BlockArray object. Default construct an item and id if no value corresponds to the requested key. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @return The index "id" of the key in the BlockArray. @warning This method should only be called if T inherits from Hashed2. */ int GetId(int p1, int p2); /** @brief Get the "id" of an item, this "id" corresponding to the index of the item in the underlying BlockArray object. Default construct an item and id if no value correspond to the requested key. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @param[in] p3 Third part of the key. @param[in] p4 Fourth part of the key (optional). @return The index "id" of the key in the BlockArray. @warning This method should only be called if T inherits from Hashed4. */ int GetId(int p1, int p2, int p3, int p4 = -1); /// Find item whose parents are p1, p2... Return NULL if it doesn't exist. /** @brief Item accessor with key (or parents) the pair 'p1', 'p2'. Return nullptr if no value correspond to the requested key. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @return The item associated to the key (p1,p2). @warning This method should only be called if T inherits from Hashed2. */ T* Find(int p1, int p2); /** @brief Item accessor with key (or parents) the quadruplet 'p1', 'p2', 'p3', 'p4'. The key 'p4' is optional. Return nullptr if no value correspond to the requested key. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @param[in] p3 Third part of the key. @param[in] p4 Fourth part of the key (optional). @return The item associated to the key (p1,p2,p3,p4). @warning This method should only be called if T inherits from Hashed4. */ T* Find(int p1, int p2, int p3, int p4 = -1); /** @brief Item const accessor with key (or parents) the pair 'p1', 'p2'. Return nullptr if no value correspond to the requested key. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @return The item associated to the key (p1,p2). @warning This method should only be called if T inherits from Hashed2. */ const T* Find(int p1, int p2) const; /** @brief Item const accessor with key (or parents) the quadruplet 'p1', 'p2', 'p3', 'p4'. The key 'p4' is optional. Return nullptr if no value correspond to the requested key. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @param[in] p3 Third part of the key. @param[in] p4 Fourth part of the key (optional). @return The item associated to the key (p1,p2,p3,p4). @warning This method should only be called if T inherits from Hashed4. */ const T* Find(int p1, int p2, int p3, int p4 = -1) const; /// Find id of item whose parents are p1, p2... Return -1 if it doesn't exist. /** @brief Find the "id" of an item, this "id" corresponding to the index of the item in the underlying BlockArray object. Default construct an item and id if no value correspond to the requested key. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @return The index "id" of the key in the BlockArray. @warning This method should only be called if T inherits from Hashed2. */ int FindId(int p1, int p2) const; /** @brief Find the "id" of an item, this "id" corresponding to the index of the item in the underlying BlockArray object. Default construct an item and id if no value correspond to the requested key. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @param[in] p3 Third part of the key. @param[in] p4 Fourth part of the key (optional). @return The index "id" of the key in the BlockArray. @warning This method should only be called if T inherits from Hashed4. */ int FindId(int p1, int p2, int p3, int p4 = -1) const; /// @brief Return the number of elements currently stored in the HashTable. int Size() const { return Base::Size() - unused.Size(); } /// @brief Return the total number of ids (used and unused) in the HashTable. int NumIds() const { return Base::Size(); } /// @brief Return the number of free/unused ids in the HashTable. int NumFreeIds() const { return unused.Size(); } /** @brief Return true if item 'id' exists in (is used by) the container. @param[in] id Index of the item in the underlying BlockArray. @warning It is assumed that 0 <= id < NumIds(). */ bool IdExists(int id) const { return (Base::At(id).next != -2); } /** @brief Remove an item from the hash table. @param[in] id Index of the item in the underlying BlockArray. @warning Its id will be reused by newly added items. */ void Delete(int id); /// @brief Remove all items. void DeleteAll(); /** @brief Allocate an item at 'id'. Enlarge the underlying BlockArray if necessary. @param[in] id Index of the item in the underlying BlockArray. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @warning This is a special purpose method used when loading data from a file. Does nothing if the slot 'id' has already been allocated. */ void Alloc(int id, int p1, int p2); /** @brief Reinitialize the internal list of unallocated items. @warning This is a special purpose method used when loading data from a file. */ void UpdateUnused(); /** @brief Change the key associated with an item. In other words, makes an item hashed under different parent IDs. @param[in] id Index of the item in the underlying BlockArray. @param[in] new_p1 First part of the new key. @param[in] new_p2 Second part of the new key. @warning This method should only be called if T inherits from Hashed2. */ void Reparent(int id, int new_p1, int new_p2); /** @brief Change the key associated with an item. In other words, makes an item hashed under different parent IDs. @param[in] id Index of the item in the underlying BlockArray. @param[in] new_p1 First part of the new key. @param[in] new_p2 Second part of the new key. @param[in] new_p3 Third part of the new key. @param[in] new_p4 Fourth part of the new key (optional). @warning This method should only be called if T inherits from Hashed4. */ void Reparent(int id, int new_p1, int new_p2, int new_p3, int new_p4 = -1); /// @brief Return total size of allocated memory (tables plus items), in bytes. std::size_t MemoryUsage() const; /// @brief Write details of the memory usage to the mfem output stream. void PrintMemoryDetail() const; /// @brief Print a histogram of bin sizes for debugging purposes. void PrintStats() const; class iterator : public Base::iterator { protected: friend class HashTable; typedef typename Base::iterator base; iterator() { } iterator(const base &it) : base(it) { while (base::good() && (*this)->next == -2) { base::next(); } } public: iterator &operator++() { while (base::next(), base::good() && (*this)->next == -2) { } return *this; } }; class const_iterator : public Base::const_iterator { protected: friend class HashTable; typedef typename Base::const_iterator base; const_iterator() { } const_iterator(const base &it) : base(it) { while (base::good() && (*this)->next == -2) { base::next(); } } public: const_iterator &operator++() { while (base::next(), base::good() && (*this)->next == -2) { } return *this; } }; iterator begin() { return iterator(Base::begin()); } iterator end() { return iterator(); } const_iterator cbegin() const { return const_iterator(Base::cbegin()); } const_iterator cend() const { return const_iterator(); } protected: /** The hash table: each bin is a linked list of items. For each non-empty bin, this arrays stores the 'id' of the first item in the list, or -1 if the bin is empty. */ int* table; /** mask = table_size-1. Used for fast modulo operation in Hash(), to wrap the raw hashed index around the current table size (which must be a power of two). */ int mask; /** List of deleted items in the BlockArray. New items are created with these ids first, before they are appended to the block array. */ Array unused; /** @brief hash function for Hashed2 items. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @return The hash key "idx" identifying a bin/bucket. NOTE: the constants are arbitrary @warning This method should only be called if T inherits from Hashed2. */ inline int Hash(size_t p1, size_t p2) const { return (984120265ul*p1 + 125965121ul*p2) & mask; } /** @brief hash function for Hashed4 items. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @param[in] p3 Third part of the key. @return The hash key "idx" identifying a bin/bucket. NOTE: The constants are arbitrary. NOTE: p4 is not hashed nor stored as p1, p2, p3 identify a face uniquely. @warning This method should only be called if T inherits from Hashed4. */ inline int Hash(size_t p1, size_t p2, size_t p3) const { return (984120265ul*p1 + 125965121ul*p2 + 495698413ul*p3) & mask; } // Delete() and Reparent() use one of these: /// @brief Hash function for items of type T that inherit from Hashed2. inline int Hash(const Hashed2& item) const { return Hash(item.p1, item.p2); } /// @brief Hash function for items of type T that inherit from Hashed4. inline int Hash(const Hashed4& item) const { return Hash(item.p1, item.p2, item.p3); } /** @brief Search the index of the item associated to the key (p1,p2) starting from the item with index @a id. @param[in] id Index of the item in the underlying BlockArray. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @return The index "id" of the key in the BlockArray. @warning This method should only be called if T inherits from Hashed2. */ int SearchList(int id, int p1, int p2) const; /** @brief Search the index of the item associated to the key (p1,p2,p3,(p4)) starting from the item with index @a id. @param[in] id Index of the item in the underlying BlockArray. @param[in] p1 First part of the key. @param[in] p2 Second part of the key. @param[in] p3 Third part of the key. @return The index "id" of the key in the BlockArray. @warning This method should only be called if T inherits from Hashed4. */ int SearchList(int id, int p1, int p2, int p3) const; /** @brief Insert the item 'id' into bin 'idx'. @param[in] idx The bin/bucket index. @param[in] id The index of the item in the BlockArray. @param[in] item The item to insert at the beginning of the linked list. @warning The method only works with bin 'idx' and does not check the overall fill factor of the hash table. If appropriate, use CheckRehash() for that. */ inline void Insert(int idx, int id, T &item); /** @brief Unlink an item @a id from the linked list of bin @a idx. @param[in] idx The bin/bucket index. @param[in] id The index of the item in the BlockArray. @warning The method aborts if the item is not found. */ void Unlink(int idx, int id); /** @brief Check table fill factor and resize if necessary. The method checks the average size of the bins (i.e., the fill factor). If the fill factor is > 2, the table is enlarged (see DoRehash()). */ inline void CheckRehash(); /** @brief Double the size of the hash table (i.e., double the number of bins) and reinsert all items into the new bins. NOTE: Rehashing is computationally expensive (O(N) in the number of items), but since it is only done rarely (when the number of items doubles), the amortized complexity of inserting an item is still O(1). */ void DoRehash(); /** @brief Return the size of the bin "idx". @param[in] idx The index of the bin. @return The size of the bin. */ int BinSize(int idx) const; }; /// Hash function for data sequences. /** Depends on GnuTLS for SHA-256 hashing. */ class HashFunction { protected: void *hash_data; /// Add a sequence of bytes for hashing void HashBuffer(const void *buffer, size_t num_bytes); /// Integer encoding method; result is independent of endianness and type template HashFunction &EncodeAndHashInts(int_type_const_iter begin, int_type_const_iter end); /// Double encoding method: encode in little-endian byte-order template HashFunction &EncodeAndHashDoubles(double_const_iter begin, double_const_iter end); public: /// Default constructor: initialize the hash function HashFunction(); /// Destructor ~HashFunction(); /// Add a sequence of bytes for hashing HashFunction &AppendBytes(const void *seq, size_t num_bytes) { HashBuffer(seq, num_bytes); return *this; } /// Add a sequence of integers for hashing, given as a c-array. /** Before hashing the sequence is encoded so that the result is independent of endianness and type: int, long, unsigned, etc. */ template HashFunction &AppendInts(const int_type *ints, size_t num_ints) { return EncodeAndHashInts(ints, ints + num_ints); } /// Add a sequence of integers for hashing, given as a fixed-size c-array. /** Before hashing the sequence is encoded so that the result is independent of endianness and type: int, long, unsigned, etc. */ template HashFunction &AppendInts(const int_type (&ints)[num_ints]) { return EncodeAndHashInts(ints, ints + num_ints); } /// Add a sequence of integers for hashing, given as a container. /** Before hashing the sequence is encoded so that the result is independent of endianness and type: int, long, unsigned, etc. */ template HashFunction &AppendInts(const int_type_container &ints) { return EncodeAndHashInts(ints.begin(), ints.end()); } /// Add a sequence of doubles for hashing, given as a c-array. /** Before hashing the sequence is encoded so that the result is independent of endianness. */ HashFunction &AppendDoubles(const double *doubles, size_t num_doubles) { return EncodeAndHashDoubles(doubles, doubles + num_doubles); } /// Add a sequence of doubles for hashing, given as a fixed-size c-array. /** Before hashing the sequence is encoded so that the result is independent of endianness. */ template HashFunction &AppendDoubles(const double (&doubles)[num_doubles]) { return EncodeAndHashDoubles(doubles, doubles + num_doubles); } /// Add a sequence of doubles for hashing, given as a container. /** Before hashing the sequence is encoded so that the result is independent of endianness. */ template HashFunction &AppendDoubles(const double_container &doubles) { return EncodeAndHashDoubles(doubles.begin(), doubles.end()); } /** @brief Return the hash string for the current sequence and reset (clear) the sequence. */ std::string GetHash() const; }; // implementation template HashTable::HashTable(int block_size, int init_hash_size) : Base(block_size) { mask = init_hash_size-1; MFEM_VERIFY(!(init_hash_size & mask), "init_size must be a power of two."); table = new int[init_hash_size]; for (int i = 0; i < init_hash_size; i++) { table[i] = -1; } } template HashTable::HashTable(const HashTable& other) : Base(other), mask(other.mask) { int size = mask+1; table = new int[size]; memcpy(table, other.table, size*sizeof(int)); other.unused.Copy(unused); } template HashTable::~HashTable() { delete [] table; } namespace internal { inline void sort3(int &a, int &b, int &c) { if (a > b) { std::swap(a, b); } if (a > c) { std::swap(a, c); } if (b > c) { std::swap(b, c); } } inline void sort4(int &a, int &b, int &c, int &d) { if (a > b) { std::swap(a, b); } if (a > c) { std::swap(a, c); } if (a > d) { std::swap(a, d); } sort3(b, c, d); } inline void sort4_ext(int &a, int &b, int &c, int &d) { if (d < 0) // support optional last index { sort3(a, b, c); } else { sort4(a, b, c, d); } } } // internal template inline T* HashTable::Get(int p1, int p2) { return &(Base::At(GetId(p1, p2))); } template inline T* HashTable::Get(int p1, int p2, int p3, int p4) { return &(Base::At(GetId(p1, p2, p3, p4))); } template int HashTable::GetId(int p1, int p2) { // search for the item in the hashtable if (p1 > p2) { std::swap(p1, p2); } int idx = Hash(p1, p2); int id = SearchList(table[idx], p1, p2); if (id >= 0) { return id; } // not found - use an unused item or create a new one int new_id; if (unused.Size()) { new_id = unused.Last(); unused.DeleteLast(); } else { new_id = Base::Append(); } T& item = Base::At(new_id); item.p1 = p1; item.p2 = p2; // insert into hashtable Insert(idx, new_id, item); CheckRehash(); return new_id; } template int HashTable::GetId(int p1, int p2, int p3, int p4) { // search for the item in the hashtable internal::sort4_ext(p1, p2, p3, p4); int idx = Hash(p1, p2, p3); int id = SearchList(table[idx], p1, p2, p3); if (id >= 0) { return id; } // not found - use an unused item or create a new one int new_id; if (unused.Size()) { new_id = unused.Last(); unused.DeleteLast(); } else { new_id = Base::Append(); } T& item = Base::At(new_id); item.p1 = p1; item.p2 = p2; item.p3 = p3; // insert into hashtable Insert(idx, new_id, item); CheckRehash(); return new_id; } template inline T* HashTable::Find(int p1, int p2) { int id = FindId(p1, p2); return (id >= 0) ? &(Base::At(id)) : NULL; } template inline T* HashTable::Find(int p1, int p2, int p3, int p4) { int id = FindId(p1, p2, p3, p4); return (id >= 0) ? &(Base::At(id)) : NULL; } template inline const T* HashTable::Find(int p1, int p2) const { int id = FindId(p1, p2); return (id >= 0) ? &(Base::At(id)) : NULL; } template inline const T* HashTable::Find(int p1, int p2, int p3, int p4) const { int id = FindId(p1, p2, p3, p4); return (id >= 0) ? &(Base::At(id)) : NULL; } template int HashTable::FindId(int p1, int p2) const { if (p1 > p2) { std::swap(p1, p2); } return SearchList(table[Hash(p1, p2)], p1, p2); } template int HashTable::FindId(int p1, int p2, int p3, int p4) const { internal::sort4_ext(p1, p2, p3, p4); return SearchList(table[Hash(p1, p2, p3)], p1, p2, p3); } template int HashTable::SearchList(int id, int p1, int p2) const { while (id >= 0) { const T& item = Base::At(id); if (item.p1 == p1 && item.p2 == p2) { return id; } id = item.next; } return -1; } template int HashTable::SearchList(int id, int p1, int p2, int p3) const { while (id >= 0) { const T& item = Base::At(id); if (item.p1 == p1 && item.p2 == p2 && item.p3 == p3) { return id; } id = item.next; } return -1; } template inline void HashTable::CheckRehash() { const int fill_factor = 2; // is the table overfull? if (Base::Size() > (mask+1) * fill_factor) { DoRehash(); } } template void HashTable::DoRehash() { delete [] table; // double the table size int new_table_size = 2*(mask+1); table = new int[new_table_size]; for (int i = 0; i < new_table_size; i++) { table[i] = -1; } mask = new_table_size-1; #if defined(MFEM_DEBUG) && !defined(MFEM_USE_MPI) mfem::out << _MFEM_FUNC_NAME << ": rehashing to size " << new_table_size << std::endl; #endif // reinsert all items for (iterator it = begin(); it != end(); ++it) { Insert(Hash(*it), it.index(), *it); } } template inline void HashTable::Insert(int idx, int id, T &item) { // add item at the beginning of the linked list item.next = table[idx]; table[idx] = id; } template void HashTable::Unlink(int idx, int id) { // remove item from the linked list int* p_id = table + idx; while (*p_id >= 0) { T& item = Base::At(*p_id); if (*p_id == id) { *p_id = item.next; return; } p_id = &(item.next); } MFEM_ABORT("HashTable<>::Unlink: item not found!"); } template void HashTable::Delete(int id) { T& item = Base::At(id); Unlink(Hash(item), id); item.next = -2; // mark item as unused unused.Append(id); // add its id to the unused ids } template void HashTable::DeleteAll() { Base::DeleteAll(); for (int i = 0; i <= mask; i++) { table[i] = -1; } unused.DeleteAll(); } template void HashTable::Alloc(int id, int p1, int p2) { // enlarge the BlockArray to hold 'id' while (id >= Base::Size()) { Base::At(Base::Append()).next = -2; // append "unused" items } T& item = Base::At(id); if (item.next == -2) { item.next = -1; item.p1 = p1; item.p2 = p2; Insert(Hash(p1, p2), id, item); CheckRehash(); } } template void HashTable::UpdateUnused() { unused.DeleteAll(); for (int i = 0; i < Base::Size(); i++) { if (Base::At(i).next == -2) { unused.Append(i); } } } template void HashTable::Reparent(int id, int new_p1, int new_p2) { T& item = Base::At(id); Unlink(Hash(item), id); if (new_p1 > new_p2) { std::swap(new_p1, new_p2); } item.p1 = new_p1; item.p2 = new_p2; // reinsert under new parent IDs int new_idx = Hash(new_p1, new_p2); Insert(new_idx, id, item); } template void HashTable::Reparent(int id, int new_p1, int new_p2, int new_p3, int new_p4) { T& item = Base::At(id); Unlink(Hash(item), id); internal::sort4_ext(new_p1, new_p2, new_p3, new_p4); item.p1 = new_p1; item.p2 = new_p2; item.p3 = new_p3; // reinsert under new parent IDs int new_idx = Hash(new_p1, new_p2, new_p3); Insert(new_idx, id, item); } template std::size_t HashTable::MemoryUsage() const { return (mask+1) * sizeof(int) + Base::MemoryUsage() + unused.MemoryUsage(); } template void HashTable::PrintMemoryDetail() const { mfem::out << Base::MemoryUsage() << " + " << (mask+1) * sizeof(int) << " + " << unused.MemoryUsage(); } template int HashTable::BinSize(int idx) const { int count = 0; int id = table[idx]; while (id >= 0) { const T& item = Base::At(id); id = item.next; count++; } return count; } template void HashTable::PrintStats() const { int table_size = mask+1; mfem::out << "Hash table size: " << table_size << "\n"; mfem::out << "Item count: " << Size() << "\n"; mfem::out << "BlockArray size: " << Base::Size() << "\n"; const int H = 16; int hist[H]; for (int i = 0; i < H; i++) { hist[i] = 0; } for (int i = 0; i < table_size; i++) { int bs = BinSize(i); if (bs >= H) { bs = H-1; } hist[bs]++; } mfem::out << "Bin size histogram:\n"; for (int i = 0; i < H; i++) { mfem::out << " size " << i << ": " << hist[i] << " bins" << std::endl; } } template HashFunction &HashFunction::EncodeAndHashInts(int_type_const_iter begin, int_type_const_iter end) { // For hashing, an integer k is encoded as follows: // * 1 byte = sign_bit(k) + num_bytes(k), where // - sign_bit(k) = (k >= 0) ? 0 : 128 // - num_bytes(k) = minimum number of bytes needed to represent abs(k) // with the convention that num_bytes(0) = 0. // * num_bytes(k) bytes = the bytes of abs(k), starting with the least // significant byte. static_assert( std::is_integral< /**/ typename std::remove_reference::type /**/ >::value, "invalid iterator type"); // Skip encoding if hashing is not available: if (hash_data == nullptr) { return *this; } constexpr int max_buffer_bytes = 64*1024; unsigned char buffer[max_buffer_bytes]; int buffer_counter = 0; while (begin != end) { int byte_counter = 0; auto k = *begin; buffer[buffer_counter] = (k >= 0) ? 0 : (k = -k, 128); while (k != 0) { byte_counter++; buffer[buffer_counter + byte_counter] = (unsigned char)(k % 256); k /= 256; // (k >>= 8) results in error, e.g. for 'char' } buffer[buffer_counter] |= byte_counter; buffer_counter += (byte_counter + 1); ++begin; if (begin == end || buffer_counter + (1 + sizeof(*begin)) > max_buffer_bytes) { HashBuffer(buffer, buffer_counter); buffer_counter = 0; } } return *this; } template HashFunction &HashFunction::EncodeAndHashDoubles(double_const_iter begin, double_const_iter end) { // For hashing, a double is encoded in little endian byte-order. static_assert( std::is_same::value, "invalid iterator type"); // Skip encoding if hashing is not available: if (hash_data == nullptr) { return *this; } constexpr int max_buffer_bytes = 64*1024; unsigned char buffer[max_buffer_bytes]; int buffer_counter = 0; while (begin != end) { auto k = reinterpret_cast(*begin); for (int i = 0; i != 7; i++) { buffer[buffer_counter++] = (unsigned char)(k & 255); k >>= 8; } buffer[buffer_counter++] = (unsigned char)k; ++begin; if (begin == end || buffer_counter + 8 > max_buffer_bytes) { HashBuffer(buffer, buffer_counter); buffer_counter = 0; } } return *this; } } // namespace mfem #endif