Repository: RetroAchievements/RAIntegration
Pull request #834 (2021-11-21 00:01:23)

support for searching memory for float values
![image](https://user-images.githubusercontent.com/32680403/142744309-76b64f15-2d4f-42e5-bd53-16029ba5c516.png)



Patch:
diff --git a/src/RA_Defs.cpp b/src/RA_Defs.cpp
index 8d7f8d1b..a92445f0 100644
--- a/src/RA_Defs.cpp
+++ b/src/RA_Defs.cpp
@@ -49,6 +49,15 @@ static std::wstring U32ToFloatString(unsigned nValue, char nFloatType)
     value.value.u32 = nValue;
     rc_transform_memref_value(&value, nFloatType);
 
+    if (value.value.f32 < 0.000001)
+    {
+        if (value.value.f32 > 0.0)
+            return ra::StringPrintf(L"%e", value.value.f32);
+
+        if (value.value.f32 < 0.0 && value.value.f32 > -0.000001)
+            return ra::StringPrintf(L"%e", value.value.f32);
+    }
+
     std::wstring sValue = ra::StringPrintf(L"%f", value.value.f32);
     while (sValue.back() == L'0')
         sValue.pop_back();
diff --git a/src/services/SearchResults.cpp b/src/services/SearchResults.cpp
index dc189b0f..4de184a9 100644
--- a/src/services/SearchResults.cpp
+++ b/src/services/SearchResults.cpp
@@ -1,5 +1,6 @@
 #include "SearchResults.h"
 
+#include "RA_Defs.h"
 #include "RA_StringUtils.h"
 
 #include "ra_utility.h"
@@ -10,6 +11,8 @@
 
 #include <algorithm>
 
+#include <rcheevos\src\rcheevos\rc_internal.h>
+
 // define this to use the generic filtering code for all search types
 // if defined, specialized templated code will be used for little endian searches
 #undef DISABLE_TEMPLATED_SEARCH
@@ -19,7 +22,8 @@ namespace services {
 
 namespace impl {
 
-_NODISCARD static constexpr bool CompareValues(_In_ unsigned int nLeft, _In_ unsigned int nRight, _In_ ComparisonType nCompareType) noexcept
+template<typename T>
+_NODISCARD static constexpr bool CompareValues(_In_ T nLeft, _In_ T nRight, _In_ ComparisonType nCompareType) noexcept
 {
     switch (nCompareType)
     {
@@ -93,6 +97,36 @@ public:
         return false;
     }
 
+    virtual bool ValidateFilterValue(SearchResults& srNew) const noexcept(false)
+    {
+        // convert the filter string into a value
+        if (!srNew.GetFilterString().empty())
+        {
+            const wchar_t* pStart = srNew.GetFilterString().c_str();
+            wchar_t* pEnd;
+
+            // try decimal first
+            srNew.m_nFilterValue = std::wcstoul(pStart, &pEnd, 10);
+            if (pEnd && *pEnd)
+            {
+                // decimal parse failed, try hex
+                srNew.m_nFilterValue = std::wcstoul(pStart, &pEnd, 16);
+                if (*pEnd)
+                {
+                    // hex parse failed
+                    return false;
+                }
+            }
+        }
+        else if (srNew.GetFilterType() == SearchFilterType::Constant)
+        {
+            // constant cannot be empty string
+            return false;
+        }
+
+        return true;
+    }
+
     // populates a vector of addresses that match the specified filter when applied to a previous search result
     virtual void ApplyFilter(SearchResults& srNew, const SearchResults& srPrevious) const
     {
@@ -230,13 +264,52 @@ public:
         return L"";
     }
 
-    virtual void UpdateValue(const SearchResults& pResults, SearchResults::Result& pResult,
+    virtual bool UpdateValue(const SearchResults& pResults, SearchResults::Result& pResult,
         _Out_ std::wstring* sFormattedValue, const ra::data::context::EmulatorContext& pEmulatorContext) const
     {
+        const unsigned int nPreviousValue = pResult.nValue;
         pResult.nValue = pEmulatorContext.ReadMemory(pResult.nAddress, pResult.nSize);
 
         if (sFormattedValue)
             *sFormattedValue = GetFormattedValue(pResults, pResult);
+
+        return (pResult.nValue != nPreviousValue);
+    }
+
+    virtual bool MatchesFilter(const SearchResults& pResults, const SearchResults& pPreviousResults,
+        SearchResults::Result& pResult) const noexcept(false)
+    {
+        unsigned int nPreviousValue = 0;
+        if (pResults.GetFilterType() == SearchFilterType::Constant)
+        {
+            nPreviousValue = pResults.GetFilterValue();
+        }
+        else
+        {
+            SearchResults::Result pPreviousResult{ pResult };
+            GetValue(pPreviousResults, pPreviousResult);
+
+            switch (pResults.GetFilterType())
+            {
+                case SearchFilterType::LastKnownValue:
+                case SearchFilterType::InitialValue:
+                    nPreviousValue = pPreviousResult.nValue;
+                    break;
+
+                case SearchFilterType::LastKnownValuePlus:
+                    nPreviousValue = pPreviousResult.nValue + pResults.GetFilterValue();
+                    break;
+
+                case SearchFilterType::LastKnownValueMinus:
+                    nPreviousValue = pPreviousResult.nValue - pResults.GetFilterValue();
+                    break;
+
+                default:
+                    return false;
+            }
+        }
+
+        return CompareValues(pResult.nValue, nPreviousValue, pResults.GetFilterComparison());
     }
 
 protected:
@@ -411,6 +484,11 @@ protected:
     }
 #endif
 
+    static void SetFilterValue(SearchResults& srResults, unsigned int nValue) noexcept
+    {
+        srResults.m_nFilterValue = nValue;
+    }
+
     static bool HasFirstAddress(const SearchResults& srResults) noexcept
     {
         return !srResults.m_vBlocks.empty();
@@ -608,11 +686,11 @@ class EightBitSearchImpl : public SearchImpl
 {
 protected:
     void ApplyConstantFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
-        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nAdjustment,
+        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nConstantValue,
         std::vector<ra::ByteAddress>& vMatches) const override
     {
         ApplyCompareFilterLittleEndian<uint8_t, true>(pBytes, pBytesStop,
-            pPreviousBlock, nComparison, nAdjustment, vMatches);
+            pPreviousBlock, nComparison, nConstantValue, vMatches);
     }
 
     void ApplyCompareFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
@@ -634,16 +712,16 @@ public:
     unsigned int BuildValue(const unsigned char* ptr) const noexcept override
     {
         GSL_SUPPRESS_F6 Expects(ptr != nullptr);
-        return (ptr[1] << 8) | ptr[0];
+        GSL_SUPPRESS_TYPE1 return *reinterpret_cast<const uint16_t*>(ptr);
     }
 
 protected:
     void ApplyConstantFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
-        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nAdjustment,
+        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nConstantValue,
         std::vector<ra::ByteAddress>& vMatches) const override
     {
         ApplyCompareFilterLittleEndian<uint16_t, true>(pBytes, pBytesStop,
-            pPreviousBlock, nComparison, nAdjustment, vMatches);
+            pPreviousBlock, nComparison, nConstantValue, vMatches);
     }
 
     void ApplyCompareFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
@@ -653,7 +731,6 @@ protected:
         ApplyCompareFilterLittleEndian<uint16_t, false>(pBytes, pBytesStop,
             pPreviousBlock, nComparison, nAdjustment, vMatches);
     }
-
 };
 
 class ThirtyTwoBitSearchImpl : public SearchImpl
@@ -666,16 +743,16 @@ public:
     unsigned int BuildValue(const unsigned char* ptr) const noexcept override
     {
         GSL_SUPPRESS_F6 Expects(ptr != nullptr);
-        return (ptr[3] << 24) | (ptr[2] << 16) | (ptr[1] << 8) | ptr[0];
+        GSL_SUPPRESS_TYPE1 return *reinterpret_cast<const unsigned int*>(ptr);
     }
 
 protected:
     void ApplyConstantFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
-        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nAdjustment,
+        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nConstantValue,
         std::vector<ra::ByteAddress>& vMatches) const override
     {
         ApplyCompareFilterLittleEndian<uint32_t, true>(pBytes, pBytesStop,
-            pPreviousBlock, nComparison, nAdjustment, vMatches);
+            pPreviousBlock, nComparison, nConstantValue, vMatches);
     }
 
     void ApplyCompareFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
@@ -685,7 +762,6 @@ protected:
         ApplyCompareFilterLittleEndian<uint32_t, false>(pBytes, pBytesStop,
             pPreviousBlock, nComparison, nAdjustment, vMatches);
     }
-
 };
 
 class ThirtyTwoBitAlignedSearchImpl : public ThirtyTwoBitSearchImpl
@@ -710,11 +786,11 @@ public:
 
 protected:
     void ApplyConstantFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
-        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nAdjustment,
+        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nConstantValue,
         std::vector<ra::ByteAddress>& vMatches) const override
     {
         ApplyCompareFilterLittleEndian<uint32_t, true, 4>(pBytes, pBytesStop,
-            pPreviousBlock, nComparison, nAdjustment, vMatches);
+            pPreviousBlock, nComparison, nConstantValue, vMatches);
     }
 
     void ApplyCompareFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
@@ -759,11 +835,11 @@ public:
 
 protected:
     void ApplyConstantFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
-        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nAdjustment,
+        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nConstantValue,
         std::vector<ra::ByteAddress>& vMatches) const override
     {
         ApplyCompareFilterLittleEndian<uint16_t, true, 2>(pBytes, pBytesStop,
-            pPreviousBlock, nComparison, nAdjustment, vMatches);
+            pPreviousBlock, nComparison, nConstantValue, vMatches);
     }
 
     void ApplyCompareFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
@@ -795,6 +871,23 @@ class SixteenBitBigEndianSearchImpl : public SixteenBitSearchImpl
         GSL_SUPPRESS_F6 Expects(ptr != nullptr);
         return (ptr[0] << 8) | ptr[1];
     }
+
+protected:
+    void ApplyConstantFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
+        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nConstantValue,
+        std::vector<ra::ByteAddress>& vMatches) const override
+    {
+        SearchImpl::ApplyConstantFilter(pBytes, pBytesStop,
+            pPreviousBlock, nComparison, nConstantValue, vMatches);
+    }
+
+    void ApplyCompareFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
+        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nAdjustment,
+        std::vector<ra::ByteAddress>& vMatches) const override
+    {
+        SearchImpl::ApplyCompareFilter(pBytes, pBytesStop,
+            pPreviousBlock, nComparison, nAdjustment, vMatches);
+    }
 };
 
 class ThirtyTwoBitBigEndianSearchImpl : public ThirtyTwoBitSearchImpl
@@ -806,6 +899,23 @@ class ThirtyTwoBitBigEndianSearchImpl : public ThirtyTwoBitSearchImpl
         GSL_SUPPRESS_F6 Expects(ptr != nullptr);
         return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
     }
+
+protected:
+    void ApplyConstantFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
+        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nConstantValue,
+        std::vector<ra::ByteAddress>& vMatches) const override
+    {
+        SearchImpl::ApplyConstantFilter(pBytes, pBytesStop,
+            pPreviousBlock, nComparison, nConstantValue, vMatches);
+    }
+
+    void ApplyCompareFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
+        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nAdjustment,
+        std::vector<ra::ByteAddress>& vMatches) const override
+    {
+        SearchImpl::ApplyCompareFilter(pBytes, pBytesStop,
+            pPreviousBlock, nComparison, nAdjustment, vMatches);
+    }
 };
 
 class AsciiTextSearchImpl : public SearchImpl
@@ -814,6 +924,18 @@ public:
     // indicate that search results can be very wide
     MemSize GetMemSize() const noexcept override { return MemSize::Text; }
 
+    bool ValidateFilterValue(SearchResults& srNew) const noexcept override
+    {
+        if (srNew.GetFilterString().empty())
+        {
+            // can't match 0 characters!
+            if (srNew.GetFilterType() == SearchFilterType::Constant)
+                return false;
+        }
+
+        return true;
+    }
+
     // populates a vector of addresses that match the specified filter when applied to a previous search result
     void ApplyFilter(SearchResults& srNew, const SearchResults& srPrevious) const override
     {
@@ -821,16 +943,8 @@ public:
         std::vector<unsigned char> vSearchText;
         GetSearchText(vSearchText, srNew.GetFilterString());
 
-        if (vSearchText.empty())
-        {
-            // can't match 0 characters!
-            if (srNew.GetFilterType() == SearchFilterType::Constant)
-                return;
-        }
-        else
-        {
+        if (!vSearchText.empty())
             nCompareLength = vSearchText.size();
-        }
 
         unsigned int nLargestBlock = 0U;
         for (auto& block : GetBlocks(srPrevious))
@@ -957,7 +1071,7 @@ public:
         return sText;
     }
 
-    void UpdateValue(const SearchResults&, SearchResults::Result& pResult,
+    bool UpdateValue(const SearchResults&, SearchResults::Result& pResult,
         _Out_ std::wstring* sFormattedValue, const ra::data::context::EmulatorContext& pEmulatorContext) const override
     {
         std::array<unsigned char, 16> pBuffer;
@@ -966,13 +1080,219 @@ public:
         std::wstring sText;
         GetASCIIText(sText, pBuffer.data(), pBuffer.size());
 
+        const unsigned int nPreviousValue = pResult.nValue;
         pResult.nValue = ra::StringHash(sText);
 
         if (sFormattedValue)
             sFormattedValue->swap(sText);
+
+        return (pResult.nValue != nPreviousValue);
+    }
+
+    bool MatchesFilter(const SearchResults& pResults, const SearchResults& pPreviousResults,
+        SearchResults::Result& pResult) const override
+    {
+        std::wstring sPreviousValue;
+        if (pResults.GetFilterType() == SearchFilterType::Constant)
+            sPreviousValue = pResults.GetFilterString();
+        else
+            sPreviousValue = GetFormattedValue(pPreviousResults, pResult);
+
+        const std::wstring sValue = GetFormattedValue(pResults, pResult);
+        const int nResult = wcscmp(sValue.c_str(), sPreviousValue.c_str());
+
+        switch (pResults.GetFilterComparison())
+        {
+            case ComparisonType::Equals:
+                return (nResult == 0);
+
+            case ComparisonType::NotEqualTo:
+                return (nResult != 0);
+
+            case ComparisonType::GreaterThan:
+                return (nResult > 0);
+
+            case ComparisonType::LessThan:
+                return (nResult < 0);
+
+            case ComparisonType::GreaterThanOrEqual:
+                return (nResult >= 0);
+
+            case ComparisonType::LessThanOrEqual:
+                return (nResult <= 0);
+
+            default:
+                return false;
+        }
+    }
+};
+
+class FloatSearchImpl : public ThirtyTwoBitSearchImpl
+{
+public:
+    MemSize GetMemSize() const noexcept override { return MemSize::Float; }
+
+    std::wstring GetFormattedValue(const SearchResults&, const SearchResults::Result& pResult) const override
+    {
+        return ra::data::MemSizeFormat(pResult.nValue, pResult.nSize, MemFormat::Dec);
+    }
+
+    bool ValidateFilterValue(SearchResults& srNew) const noexcept override
+    {
+        // convert the filter string into a value
+        if (!srNew.GetFilterString().empty())
+        {
+            const wchar_t* pStart = srNew.GetFilterString().c_str();
+            wchar_t* pEnd;
+
+            const float fFilterValue = std::wcstof(pStart, &pEnd);
+            if (pEnd && *pEnd)
+            {
+                // parse failed
+                return false;
+            }
+
+            SetFilterValue(srNew, DeconstructFloatValue(fFilterValue));
+        }
+        else if (srNew.GetFilterType() == SearchFilterType::Constant)
+        {
+            // constant cannot be empty string
+            return false;
+        }
+
+        return true;
+    }
+
+    GSL_SUPPRESS_TYPE1
+    bool MatchesFilter(const SearchResults& pResults, const SearchResults& pPreviousResults,
+        SearchResults::Result& pResult) const noexcept override
+    {
+        float fPreviousValue = 0.0;
+        if (pResults.GetFilterType() == SearchFilterType::Constant)
+        {
+            const auto nFilterValue = pResults.GetFilterValue();
+            const auto* pFilterValue = reinterpret_cast<const unsigned char*>(&nFilterValue);
+            fPreviousValue = BuildFloatValue(pFilterValue);
+        }
+        else
+        {
+            SearchResults::Result pPreviousResult{ pResult };
+            GetValue(pPreviousResults, pPreviousResult);
+            const auto* pPreviousValue = reinterpret_cast<const unsigned char*>(&pPreviousResult.nValue);
+            fPreviousValue = BuildFloatValue(pPreviousValue);
+        }
+
+        const auto* pValue = reinterpret_cast<const unsigned char*>(&pResult.nValue);
+        const auto fValue = BuildFloatValue(pValue);
+        return CompareValues(fValue, fPreviousValue, pResults.GetFilterComparison());
+    }
+
+protected:
+    virtual float BuildFloatValue(const unsigned char* ptr) const noexcept
+    {
+        GSL_SUPPRESS_F6 Expects(ptr != nullptr);
+        GSL_SUPPRESS_TYPE1 return *reinterpret_cast<const float*>(ptr);
+    }
+
+    virtual unsigned int DeconstructFloatValue(float fValue) const noexcept
+    {
+        return ra::data::FloatToU32(fValue, MemSize::Float);
+    }
+
+    GSL_SUPPRESS_TYPE1
+    void ApplyConstantFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
+        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nConstantValue,
+        std::vector<ra::ByteAddress>& vMatches) const override
+    {
+        if (nComparison == ComparisonType::Equals || nComparison == ComparisonType::NotEqualTo)
+        {
+            // for direct equality, we can just compare the raw bytes without converting
+            ThirtyTwoBitSearchImpl::ApplyConstantFilter(pBytes, pBytesStop,
+                pPreviousBlock, nComparison, nConstantValue, vMatches);
+            return;
+        }
+
+        const auto nBlockAddress = pPreviousBlock.GetFirstAddress();
+        const auto nStride = GetStride();
+        const auto* pMatchingAddresses = pPreviousBlock.GetMatchingAddressPointer();
+
+        const auto* pConstantValue = reinterpret_cast<const unsigned char*>(&nConstantValue);
+        const float fConstantValue = BuildFloatValue(pConstantValue);
+
+        for (const auto* pScan = pBytes; pScan < pBytesStop; pScan += nStride)
+        {
+            const float fValue1 = BuildFloatValue(pScan);
+            if (CompareValues(fValue1, fConstantValue, nComparison))
+            {
+                const ra::ByteAddress nAddress = nBlockAddress +
+                    ConvertFromRealAddress(gsl::narrow_cast<unsigned int>(pScan - pBytes));
+                if (pPreviousBlock.HasMatchingAddress(pMatchingAddresses, nAddress))
+                    vMatches.push_back(nAddress);
+            }
+        }
+    }
+
+    GSL_SUPPRESS_TYPE1
+    void ApplyCompareFilter(const uint8_t* pBytes, const uint8_t* pBytesStop,
+        const MemBlock& pPreviousBlock, ComparisonType nComparison, unsigned nAdjustment,
+        std::vector<ra::ByteAddress>& vMatches) const override
+    {
+        if (nComparison == ComparisonType::Equals || nComparison == ComparisonType::NotEqualTo)
+        {
+            // for direct equality, we can just compare the raw bytes without converting
+            ThirtyTwoBitSearchImpl::ApplyCompareFilter(pBytes, pBytesStop,
+                pPreviousBlock, nComparison, nAdjustment, vMatches);
+            return;
+        }
+
+        const auto* pBlockBytes = pPreviousBlock.GetBytes();
+        const auto nBlockAddress = pPreviousBlock.GetFirstAddress();
+        const auto nStride = GetStride();
+        const auto* pMatchingAddresses = pPreviousBlock.GetMatchingAddressPointer();
+
+        const auto* pAdjustment = reinterpret_cast<const unsigned char*>(&nAdjustment);
+        const float fAdjustment = BuildFloatValue(pAdjustment);
+
+        for (const auto* pScan = pBytes; pScan < pBytesStop; pScan += nStride, pBlockBytes += nStride)
+        {
+            const float fValue1 = BuildFloatValue(pScan);
+            const float fValue2 = BuildFloatValue(pBlockBytes) + fAdjustment;
+            if (CompareValues(fValue1, fValue2, nComparison))
+            {
+                const ra::ByteAddress nAddress = nBlockAddress +
+                    ConvertFromRealAddress(gsl::narrow_cast<unsigned int>(pScan - pBytes));
+                if (pPreviousBlock.HasMatchingAddress(pMatchingAddresses, nAddress))
+                    vMatches.push_back(nAddress);
+            }
+        }
+    }
+};
+
+class MBF32SearchImpl : public FloatSearchImpl
+{
+public:
+    MemSize GetMemSize() const noexcept override { return MemSize::MBF32; }
+
+protected:
+    GSL_SUPPRESS_TYPE1
+    float BuildFloatValue(const unsigned char* ptr) const noexcept override
+    {
+        GSL_SUPPRESS_F6 Expects(ptr != nullptr);
+        rc_typed_value_t value;
+        value.type = RC_VALUE_TYPE_UNSIGNED;
+        value.value.u32 = *reinterpret_cast<const unsigned int*>(ptr);
+        rc_transform_memref_value(&value, RC_MEMSIZE_MBF32);
+
+        return value.value.f32;
+    }
+
+    unsigned int DeconstructFloatValue(float fValue) const noexcept override
+    {
+        return ra::data::FloatToU32(fValue, MemSize::MBF32);
     }
 };
 
+
 static FourBitSearchImpl s_pFourBitSearchImpl;
 static EightBitSearchImpl s_pEightBitSearchImpl;
 static SixteenBitSearchImpl s_pSixteenBitSearchImpl;
@@ -982,6 +1302,8 @@ static ThirtyTwoBitAlignedSearchImpl s_pThirtyTwoBitAlignedSearchImpl;
 static SixteenBitBigEndianSearchImpl s_pSixteenBitBigEndianSearchImpl;
 static ThirtyTwoBitBigEndianSearchImpl s_pThirtyTwoBitBigEndianSearchImpl;
 static AsciiTextSearchImpl s_pAsciiTextSearchImpl;
+static FloatSearchImpl s_pFloatSearchImpl;
+static MBF32SearchImpl s_pMBF32SearchImpl;
 
 uint8_t* MemBlock::AllocateMatchingAddresses() noexcept
 {
@@ -1166,6 +1488,12 @@ void SearchResults::Initialize(ra::ByteAddress nAddress, size_t nBytes, SearchTy
         case SearchType::AsciiText:
             m_pImpl = &ra::services::impl::s_pAsciiTextSearchImpl;
             break;
+        case SearchType::Float:
+            m_pImpl = &ra::services::impl::s_pFloatSearchImpl;
+            break;
+        case SearchType::MBF32:
+            m_pImpl = &ra::services::impl::s_pMBF32SearchImpl;
+            break;
     }
 
     const unsigned int nPadding = m_pImpl->GetPadding();
@@ -1187,11 +1515,6 @@ void SearchResults::Initialize(ra::ByteAddress nAddress, size_t nBytes, SearchTy
     }
 }
 
-bool SearchResults::Result::Compare(unsigned int nPreviousValue, ComparisonType nCompareType) const noexcept
-{
-    return impl::CompareValues(nValue, nPreviousValue, nCompareType);
-}
-
 bool SearchResults::ContainsAddress(ra::ByteAddress nAddress) const
 {
     if (m_pImpl)
@@ -1200,38 +1523,6 @@ bool SearchResults::ContainsAddress(ra::ByteAddress nAddress) const
     return false;
 }
 
-_Use_decl_annotations_
-void SearchResults::Initialize(const SearchResults& srSource, ComparisonType nCompareType,
-    SearchFilterType nFilterType, unsigned int nFilterValue)
-{
-    m_nType = srSource.m_nType;
-    m_pImpl = srSource.m_pImpl;
-    m_nCompareType = nCompareType;
-    m_nFilterType = nFilterType;
-    m_nFilterValue = nFilterValue;
-
-    m_pImpl->ApplyFilter(*this, srSource);
-}
-
-_Use_decl_annotations_
-void SearchResults::Initialize(const SearchResults& srMemory, const SearchResults& srAddresses,
-    ComparisonType nCompareType, SearchFilterType nFilterType, unsigned int nFilterValue)
-{
-    if (&srMemory == &srAddresses)
-    {
-        Initialize(srMemory, nCompareType, nFilterType, nFilterValue);
-        return;
-    }
-
-    // create a new merged SearchResults object that has the matching addresses from srAddresses,
-    // and the memory blocks from srMemory.
-    SearchResults srMerge;
-    srMerge.MergeSearchResults(srMemory, srAddresses);
-
-    // then do a standard comparison against the merged SearchResults
-    Initialize(srMerge, nCompareType, nFilterType, nFilterValue);
-}
-
 void SearchResults::MergeSearchResults(const SearchResults& srMemory, const SearchResults& srAddresses)
 {
     m_vBlocks.reserve(srAddresses.m_vBlocks.size());
@@ -1273,7 +1564,7 @@ void SearchResults::MergeSearchResults(const SearchResults& srMemory, const Sear
 }
 
 _Use_decl_annotations_
-void SearchResults::Initialize(const SearchResults& srSource, ComparisonType nCompareType,
+bool SearchResults::Initialize(const SearchResults& srSource, ComparisonType nCompareType,
     SearchFilterType nFilterType, const std::wstring& sFilterValue)
 {
     m_nType = srSource.m_nType;
@@ -1282,18 +1573,19 @@ void SearchResults::Initialize(const SearchResults& srSource, ComparisonType nCo
     m_nFilterType = nFilterType;
     m_sFilterValue = sFilterValue;
 
+    if (!m_pImpl->ValidateFilterValue(*this))
+        return false;
+
     m_pImpl->ApplyFilter(*this, srSource);
+    return true;
 }
 
 _Use_decl_annotations_
-void SearchResults::Initialize(const SearchResults& srMemory, const SearchResults& srAddresses,
+bool SearchResults::Initialize(const SearchResults& srMemory, const SearchResults& srAddresses,
     ComparisonType nCompareType, SearchFilterType nFilterType, const std::wstring& sFilterValue)
 {
     if (&srMemory == &srAddresses)
-    {
-        Initialize(srMemory, nCompareType, nFilterType, sFilterValue);
-        return;
-    }
+        return Initialize(srMemory, nCompareType, nFilterType, sFilterValue);
 
     // create a new merged SearchResults object that has the matching addresses from srAddresses,
     // and the memory blocks from srMemory.
@@ -1301,7 +1593,7 @@ void SearchResults::Initialize(const SearchResults& srMemory, const SearchResult
     srMerge.MergeSearchResults(srMemory, srAddresses);
 
     // then do a standard comparison against the merged SearchResults
-    Initialize(srMerge, nCompareType, nFilterType, sFilterValue);
+    return Initialize(srMerge, nCompareType, nFilterType, sFilterValue);
 }
 
 size_t SearchResults::MatchingAddressCount() const noexcept
@@ -1339,6 +1631,16 @@ bool SearchResults::GetMatchingAddress(gsl::index nIndex, _Out_ SearchResults::R
     return m_pImpl->GetMatchingAddress(*this, nIndex, result);
 }
 
+bool SearchResults::GetMatchingAddress(const SearchResults::Result& pSrcResult, _Out_ SearchResults::Result& result) const noexcept
+{
+    memcpy(&result, &pSrcResult, sizeof(SearchResults::Result));
+
+    if (m_pImpl == nullptr)
+        return false;
+
+    return m_pImpl->GetValue(*this, result);
+}
+
 bool SearchResults::GetBytes(ra::ByteAddress nAddress, unsigned char* pBuffer, size_t nCount) const noexcept
 {
     if (m_pImpl != nullptr)
@@ -1375,14 +1677,24 @@ std::wstring SearchResults::GetFormattedValue(ra::ByteAddress nAddress, MemSize
     return m_pImpl ? m_pImpl->GetFormattedValue(*this, nAddress, nSize) : L"";
 }
 
-void SearchResults::UpdateValue(SearchResults::Result& pResult, _Out_ std::wstring* sFormattedValue,
-    const ra::data::context::EmulatorContext& pEmulatorContext) const
+bool SearchResults::UpdateValue(SearchResults::Result& pResult,
+    _Out_ std::wstring* sFormattedValue, const ra::data::context::EmulatorContext& pEmulatorContext) const
 {
     if (m_pImpl)
         return m_pImpl->UpdateValue(*this, pResult, sFormattedValue, pEmulatorContext);
 
     if (sFormattedValue)
         sFormattedValue->clear();
+
+    return false;
+}
+
+bool SearchResults::MatchesFilter(const SearchResults& pPreviousResults, SearchResults::Result& pResult) const
+{
+    if (m_pImpl)
+        return m_pImpl->MatchesFilter(*this, pPreviousResults, pResult);
+
+    return true;
 }
 
 MemSize SearchResults::GetSize() const noexcept
diff --git a/src/services/SearchResults.h b/src/services/SearchResults.h
index da71815f..e57c59c7 100644
--- a/src/services/SearchResults.h
+++ b/src/services/SearchResults.h
@@ -19,6 +19,8 @@ enum class SearchType
     ThirtyTwoBitAligned,
     SixteenBitBigEndian,
     ThirtyTwoBitBigEndian,
+    Float,
+    MBF32,
     AsciiText,
 };
 
@@ -185,27 +187,6 @@ public:
     /// <param name="nType">Type of search to initialize.</param>
     void Initialize(ra::ByteAddress nAddress, size_t nBytes, SearchType nType);
 
-    /// <summary>
-    /// Initializes a result set by comparing against another result set.
-    /// </summary>
-    /// <param name="srSource">The result set to filter.</param>
-    /// <param name="nCompareType">Type of comparison to apply.</param>
-    /// <param name="nFilterType">Type of filter to apply.</param>
-    /// <param name="nFilterValue">Parameter for filter being applied.</param>
-    void Initialize(_In_ const SearchResults& srSource, _In_ ComparisonType nCompareType,
-        _In_ SearchFilterType nFilterType, _In_ unsigned int nFilterValue);
-
-    /// <summary>
-    /// Initializes a result set by comparing against another result set using the address filter from a third set.
-    /// </summary>
-    /// <param name="srSource">The result set to filter.</param>
-    /// <param name="srAddresses">The result set specifying which addresses to examine.</param>
-    /// <param name="nCompareType">Type of comparison to apply.</param>
-    /// <param name="nFilterType">Type of filter to apply.</param>
-    /// <param name="nFilterValue">Parameter for filter being applied.</param>
-    void Initialize(_In_ const SearchResults& srSource, _In_ const SearchResults& srAddresses,
-        _In_ ComparisonType nCompareType, _In_ SearchFilterType nFilterType, _In_ unsigned int nFilterValue);
-
     /// <summary>
     /// Initializes a result set by comparing against another result set.
     /// </summary>
@@ -213,7 +194,8 @@ public:
     /// <param name="nCompareType">Type of comparison to apply.</param>
     /// <param name="nFilterType">Type of filter to apply.</param>
     /// <param name="sFilterValue">Parameter for filter being applied.</param>
-    void Initialize(_In_ const SearchResults& srSource, _In_ ComparisonType nCompareType,
+    /// <returns><c>true</c> if initialization was successful, <c>false</c> if the filter value was not supported</returns>
+    bool Initialize(_In_ const SearchResults& srSource, _In_ ComparisonType nCompareType,
         _In_ SearchFilterType nFilterType, _In_ const std::wstring& sFilterValue);
 
     /// <summary>
@@ -224,7 +206,8 @@ public:
     /// <param name="nCompareType">Type of comparison to apply.</param>
     /// <param name="nFilterType">Type of filter to apply.</param>
     /// <param name="sFilterValue">Parameter for filter being applied.</param>
-    void Initialize(_In_ const SearchResults& srSource, _In_ const SearchResults& srAddresses,
+    /// <returns><c>true</c> if initialization was successful, <c>false</c> if the filter value was not supported</returns>
+    bool Initialize(_In_ const SearchResults& srSource, _In_ const SearchResults& srAddresses,
         _In_ ComparisonType nCompareType, _In_ SearchFilterType nFilterType, _In_ const std::wstring& sFilterValue);
 
     /// <summary>
@@ -237,9 +220,6 @@ public:
         ra::ByteAddress nAddress{};
         unsigned int nValue{};
         MemSize nSize{};
-
-        bool Compare(unsigned int nPreviousValue, ComparisonType nCompareType) const noexcept;
-        bool Compare(const std::wstring& sPreviousValue, ComparisonType nCompareType) const noexcept;
     };
 
     /// <summary>
@@ -249,6 +229,14 @@ public:
     /// <returns><c>true</c> if result was populated, <c>false</c> if the index was invalid.</returns>
     bool GetMatchingAddress(gsl::index nIndex, _Out_ Result& result) const noexcept;
 
+    /// <summary>
+    /// Gets and item from the results matching a result from another set of results.
+    /// </summary>
+    /// <param name="pSrcResult">The result to find a match for.</param>
+    /// <param name="result">The result.</param>
+    /// <returns><c>true</c> if result was populated, <c>false</c> if the index was invalid.</returns>
+    bool GetMatchingAddress(const SearchResults::Result& pSrcResult, _Out_ Result& result) const noexcept;
+
     /// <summary>
     /// Gets the raw bytes at the specified address.
     /// </summary>
@@ -266,10 +254,20 @@ public:
     /// Updates the current value of the provided result.
     /// </summary>
     /// <param name="pResult">The result to update.</param>
-    /// <param name="sFormattedValue">Pointer to a string to populate with a textual representation of the value.</param>
-    void UpdateValue(SearchResults::Result& pResult, _Out_ std::wstring* sFormattedValue,
+    /// <param name="pPreviousResult">The previous value of the result.</param>
+    /// <param name="sFormattedValue">Pointer to a string to populate with a textual representation of the value. [optional]</param>
+    /// <returns><c>true</c> if the value changed, <c>false</c> if not.</returns>
+    bool UpdateValue(SearchResults::Result& pResult, _Out_ std::wstring* sFormattedValue,
         const ra::data::context::EmulatorContext& pEmulatorContext) const;
 
+    /// <summary>
+    /// Determines if a value compared to its previous value matches the filter.
+    /// </summary>
+    /// <param name="pPreviousResults">The previous results to compare against.</param>
+    /// <param name="pResult">The result to test.</param>
+    /// <returns><c>true</c> if the value still matches the filter, <c>false</c> if not.</returns>
+    bool MatchesFilter(const SearchResults& pPreviousResults, SearchResults::Result& pResult) const;
+
     /// <summary>
     /// Gets the type of search performed.
     /// </summary>
diff --git a/src/ui/viewmodels/MemorySearchViewModel.cpp b/src/ui/viewmodels/MemorySearchViewModel.cpp
index e94937c4..24a81740 100644
--- a/src/ui/viewmodels/MemorySearchViewModel.cpp
+++ b/src/ui/viewmodels/MemorySearchViewModel.cpp
@@ -99,6 +99,8 @@ MemorySearchViewModel::MemorySearchViewModel()
     m_vSearchTypes.Add(ra::etoi(ra::services::SearchType::ThirtyTwoBitAligned), L"32-bit (aligned)");
     m_vSearchTypes.Add(ra::etoi(ra::services::SearchType::SixteenBitBigEndian), L"16-bit BE");
     m_vSearchTypes.Add(ra::etoi(ra::services::SearchType::ThirtyTwoBitBigEndian), L"32-bit BE");
+    m_vSearchTypes.Add(ra::etoi(ra::services::SearchType::Float), L"Float");
+    m_vSearchTypes.Add(ra::etoi(ra::services::SearchType::MBF32), L"MBF32");
     m_vSearchTypes.Add(ra::etoi(ra::services::SearchType::AsciiText), L"ASCII Text");
 
     m_vComparisonTypes.Add(ra::etoi(ComparisonType::Equals), L"=");
@@ -352,7 +354,7 @@ void MemorySearchViewModel::DoFrame()
         pCurrentResults.pResults.GetMatchingAddress(nIndex++, pResult);
 
         const auto nPreviousValue = pResult.nValue;
-        UpdateResult(*pRow, pCurrentResults.pResults, pResult, pEmulatorContext);
+        UpdateResult(*pRow, pCurrentResults.pResults, pResult, false, pEmulatorContext);
 
         // when updating an existing result, check to see if it's been modified and update the tracker
         if (!pRow->bHasBeenModified && pResult.nValue != nPreviousValue)
@@ -501,14 +503,14 @@ void MemorySearchViewModel::BeginNewSearch()
     SetValue(ResultMemSizeProperty, ra::etoi(pResult.pResults.GetSize()));
 }
 
-void MemorySearchViewModel::AddNewPage()
+void MemorySearchViewModel::AddNewPage(SearchResult&& pNewPage)
 {
     // remove any search history after the current node
     while (m_vSearchResults.size() - 1 > m_nSelectedSearchResult)
         m_vSearchResults.pop_back();
 
     // add a new search history entry
-    m_vSearchResults.emplace_back();
+    m_vSearchResults.emplace_back(std::move(pNewPage));
     if (m_vSearchResults.size() > SEARCH_MAX_HISTORY + 1)
     {
         // always discard the second search result in case the user wants to do an initial search later
@@ -525,51 +527,18 @@ void MemorySearchViewModel::ApplyFilter()
     if (m_vSearchResults.empty())
         return;
 
-    unsigned int nValue = 0U;
-    if (GetValue(CanEditFilterValueProperty))
-    {
-        const auto& sValue = GetFilterValue();
-        if (sValue.empty())
-        {
-            ra::ui::viewmodels::MessageBoxViewModel::ShowErrorMessage(L"Invalid filter value");
-            return;
-        }
+    const std::wstring sEmptyString;
+    const auto* sValue = GetValue(CanEditFilterValueProperty) ? &GetFilterValue() : &sEmptyString;
 
-        switch (GetSearchType())
-        {
-            case ra::services::SearchType::AsciiText:
-                break;
+    SearchResult& pPreviousResult = m_vSearchResults.back();
+    SearchResult pResult;
 
-            default:
-            {
-                const wchar_t* pStart = sValue.c_str();
-
-                // try decimal first
-                wchar_t* pEnd;
-                nValue = std::wcstoul(pStart, &pEnd, 10);
-                if (pEnd && *pEnd)
-                {
-                    // decimal parse failed, try hex
-                    nValue = std::wcstoul(pStart, &pEnd, 16);
-                    if (*pEnd)
-                    {
-                        // hex parse failed
-                        ra::ui::viewmodels::MessageBoxViewModel::ShowErrorMessage(L"Invalid filter value");
-                        return;
-                    }
-                }
-            }
-            break;
-        }
+    if (!ApplyFilter(pResult, pPreviousResult, GetComparisonType(), GetValueType(), *sValue))
+    {
+        ra::ui::viewmodels::MessageBoxViewModel::ShowErrorMessage(L"Invalid filter value");
+        return;
     }
 
-    AddNewPage();
-
-    SearchResult const& pPreviousResult = *(m_vSearchResults.end() - 2);
-    SearchResult& pResult = m_vSearchResults.back();
-
-    ApplyFilter(pResult, pPreviousResult, GetComparisonType(), GetValueType(), nValue, GetFilterValue());
-
     // if this isn't the first filter being applied, and the result count hasn't changed
     const auto nMatches = pResult.pResults.MatchingAddressCount();
     if (nMatches == pPreviousResult.pResults.MatchingAddressCount() && m_vSearchResults.size() > 2)
@@ -583,9 +552,7 @@ void MemorySearchViewModel::ApplyFilter()
             // didn't change, so don't generate a new result set. do clear the modified addresses list.
             if (pResult.pResults.GetFilterComparison() == ComparisonType::Equals)
             {
-                m_vSearchResults.pop_back();
-                m_vSearchResults.back().vModifiedAddresses.clear();
-                --m_nSelectedSearchResult;
+                pPreviousResult.vModifiedAddresses.clear();
                 return;
             }
         }
@@ -621,41 +588,22 @@ void MemorySearchViewModel::ApplyFilter()
     pResult.sSummary = builder.ToWString();
     SetValue(FilterSummaryProperty, pResult.sSummary);
 
+    AddNewPage(std::move(pResult));
     ChangePage(m_nSelectedSearchResult);
 }
 
-void MemorySearchViewModel::ApplyFilter(SearchResult& pResult, const SearchResult& pPreviousResult,
-    ComparisonType nComparisonType, ra::services::SearchFilterType nValueType, unsigned int nValue, const std::wstring& sValue)
+bool MemorySearchViewModel::ApplyFilter(SearchResult& pResult, const SearchResult& pPreviousResult,
+    ComparisonType nComparisonType, ra::services::SearchFilterType nValueType, const std::wstring& sValue)
 {
     if (nValueType == ra::services::SearchFilterType::InitialValue)
     {
         SearchResult const& pInitialResult = m_vSearchResults.front();
-
-        switch (pPreviousResult.pResults.GetSearchType())
-        {
-            case ra::services::SearchType::AsciiText:
-                pResult.pResults.Initialize(pInitialResult.pResults, pPreviousResult.pResults,
-                    nComparisonType, nValueType, sValue);
-                break;
-
-            default:
-                pResult.pResults.Initialize(pInitialResult.pResults, pPreviousResult.pResults,
-                    nComparisonType, nValueType, nValue);
-                break;
-        }
+        return pResult.pResults.Initialize(pInitialResult.pResults, pPreviousResult.pResults,
+            nComparisonType, nValueType, sValue);
     }
     else
     {
-        switch (pPreviousResult.pResults.GetSearchType())
-        {
-            case ra::services::SearchType::AsciiText:
-                pResult.pResults.Initialize(pPreviousResult.pResults, nComparisonType, nValueType, sValue);
-                break;
-
-            default:
-                pResult.pResults.Initialize(pPreviousResult.pResults, nComparisonType, nValueType, nValue);
-                break;
-        }
+        return pResult.pResults.Initialize(pPreviousResult.pResults, nComparisonType, nValueType, sValue);
     }
 }
 
@@ -704,8 +652,7 @@ void MemorySearchViewModel::ApplyContinuousFilter()
     // apply the current filter
     SearchResult pNewResult;
     ApplyFilter(pNewResult, pResult, pResult.pResults.GetFilterComparison(),
-        pResult.pResults.GetFilterType(), pResult.pResults.GetFilterValue(),
-        pResult.pResults.GetFilterString());
+        pResult.pResults.GetFilterType(), pResult.pResults.GetFilterString());
     pNewResult.sSummary = pResult.sSummary;
     const auto nNewResults = pNewResult.pResults.MatchingAddressCount();
 
@@ -792,7 +739,7 @@ void MemorySearchViewModel::UpdateResults()
 
         pRow->SetAddress(ra::Widen(sAddress));
 
-        UpdateResult(*pRow, pCurrentResults.pResults, pResult, pEmulatorContext);
+        UpdateResult(*pRow, pCurrentResults.pResults, pResult, true, pEmulatorContext);
 
         const auto pCodeNote = pGameContext.FindCodeNote(pResult.nAddress, pResult.nSize);
         if (!pCodeNote.empty())
@@ -834,27 +781,18 @@ void MemorySearchViewModel::UpdateResults()
 }
 
 void MemorySearchViewModel::UpdateResult(SearchResultViewModel& vmResult,
-    const ra::services::SearchResults& pResults,
-    ra::services::SearchResults::Result& pResult,
-    const ra::data::context::EmulatorContext& pEmulatorContext)
+    const ra::services::SearchResults& pResults, ra::services::SearchResults::Result& pResult,
+    bool bForceFilterCheck, const ra::data::context::EmulatorContext& pEmulatorContext)
 {
-    const auto nPreviousValue = pResult.nValue;
     std::wstring sFormattedValue;
-
-    pResults.UpdateValue(pResult, &sFormattedValue, pEmulatorContext);
-    vmResult.SetCurrentValue(sFormattedValue);
-
-    switch (pResults.GetSearchType())
+    if (pResults.UpdateValue(pResult, &sFormattedValue, pEmulatorContext) || bForceFilterCheck)
     {
-        case ra::services::SearchType::AsciiText:
-            // TODO: implement for strings
-            vmResult.bMatchesFilter = true;
-            break;
+        if (pResults.GetFilterType() == ra::services::SearchFilterType::InitialValue)
+            vmResult.bMatchesFilter = pResults.MatchesFilter(m_vSearchResults.front().pResults, pResult);
+        else
+            vmResult.bMatchesFilter = pResults.MatchesFilter(m_vSearchResults.at(m_nSelectedSearchResult - 1).pResults, pResult);
 
-        default:
-            // when continuous filtering, the values should always match - don't bother testing
-            vmResult.bMatchesFilter = m_bIsContinuousFiltering || TestFilter(pResult, pResults, nPreviousValue);
-            break;
+        vmResult.SetCurrentValue(sFormattedValue);
     }
 }
 
@@ -897,29 +835,6 @@ void MemorySearchViewModel::OnCodeNoteChanged(ra::ByteAddress nAddress, const st
     }
 }
 
-bool MemorySearchViewModel::TestFilter(const ra::services::SearchResults::Result& pResult,
-    const ra::services::SearchResults& pResults, unsigned int nPreviousValue) noexcept
-{
-    switch (pResults.GetFilterType())
-    {
-        case ra::services::SearchFilterType::Constant:
-            return pResult.Compare(pResults.GetFilterValue(), pResults.GetFilterComparison());
-
-        case ra::services::SearchFilterType::InitialValue:
-        case ra::services::SearchFilterType::LastKnownValue:
-            return pResult.Compare(nPreviousValue, pResults.GetFilterComparison());
-
-        case ra::services::SearchFilterType::LastKnownValuePlus:
-            return pResult.Compare(nPreviousValue + pResults.GetFilterValue(), pResults.GetFilterComparison());
-
-        case ra::services::SearchFilterType::LastKnownValueMinus:
-            return pResult.Compare(nPreviousValue - pResults.GetFilterValue(), pResults.GetFilterComparison());
-
-        default:
-            return false;
-    }
-}
-
 static constexpr bool CanEditFilterValue(ra::services::SearchFilterType nFilterType)
 {
     switch (nFilterType)
@@ -1091,12 +1006,8 @@ void MemorySearchViewModel::ExcludeSelected()
     if (m_vSelectedAddresses.empty())
         return;
 
-    AddNewPage();
-
-    SearchResult pPreviousResult = *(m_vSearchResults.end() - 2);
-
-    m_vSearchResults.pop_back(); // remove temporary item
-    SearchResult& pResult = m_vSearchResults.emplace_back(pPreviousResult); // clone previous item
+    const SearchResult& pPreviousResult = m_vSearchResults.back();
+    SearchResult pResult{ pPreviousResult }; // clone previous item
 
     for (const auto nAddress : m_vSelectedAddresses)
         pResult.pResults.ExcludeAddress(nAddress);
@@ -1116,9 +1027,11 @@ void MemorySearchViewModel::ExcludeSelected()
     m_vSelectedAddresses.clear();
     SetValue(HasSelectionProperty, false);
 
+    const size_t nMatchingAddressCount = pResult.pResults.MatchingAddressCount();
+    AddNewPage(std::move(pResult));
     ChangePage(m_nSelectedSearchResult);
 
-    if (pResult.pResults.MatchingAddressCount() < gsl::narrow_cast<size_t>(nScrollOffset) + SEARCH_ROWS_DISPLAYED)
+    if (nMatchingAddressCount < gsl::narrow_cast<size_t>(nScrollOffset) + SEARCH_ROWS_DISPLAYED)
         nScrollOffset = 0;
 
     SetValue(ScrollOffsetProperty, nScrollOffset);
diff --git a/src/ui/viewmodels/MemorySearchViewModel.hh b/src/ui/viewmodels/MemorySearchViewModel.hh
index 98d095a3..0f9a6c12 100644
--- a/src/ui/viewmodels/MemorySearchViewModel.hh
+++ b/src/ui/viewmodels/MemorySearchViewModel.hh
@@ -459,12 +459,9 @@ private:
     void ApplyContinuousFilter();
     void UpdateResults();
     void UpdateResult(SearchResultViewModel& pRow, const ra::services::SearchResults& pResults,
-        ra::services::SearchResults::Result& pResult,
+        ra::services::SearchResults::Result& pResult, bool bForceFilterCheck,
         const ra::data::context::EmulatorContext& pEmulatorContext);
 
-    void AddNewPage();
-    void ChangePage(size_t nNewPage);
-
     void OnPredefinedFilterRangeChanged();
     void OnFilterRangeChanged();
 
@@ -487,13 +484,15 @@ private:
         std::wstring sSummary;
     };
 
+    void AddNewPage(SearchResult&& pNewPage);
+    void ChangePage(size_t nNewPage);
+
     size_t m_nSelectedSearchResult = 0U;
     std::vector<SearchResult> m_vSearchResults;
     std::set<unsigned int> m_vSelectedAddresses;
 
-    static bool TestFilter(const ra::services::SearchResults::Result& pResult, const ra::services::SearchResults& pResults, unsigned int nPreviousValue) noexcept;
-    void ApplyFilter(SearchResult& pResult, const SearchResult& pPreviousResult,
-        ComparisonType nComparisonType, ra::services::SearchFilterType nValueType, unsigned int nValue, const std::wstring& sValue);
+    bool ApplyFilter(SearchResult& pResult, const SearchResult& pPreviousResult,
+        ComparisonType nComparisonType, ra::services::SearchFilterType nValueType, const std::wstring& sValue);
 };
 
 } // namespace viewmodels


Test patch:
diff --git a/tests/services/SearchResults_Tests.cpp b/tests/services/SearchResults_Tests.cpp
index 797ca0aa..308ca0ee 100644
--- a/tests/services/SearchResults_Tests.cpp
+++ b/tests/services/SearchResults_Tests.cpp
@@ -314,7 +314,7 @@ public:
         Assert::AreEqual({ 3U }, results1.MatchingAddressCount());
 
         SearchResults results2;
-        results2.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, 0xABU);
+        results2.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L"0xAB");
 
         Assert::AreEqual({ 1U }, results2.MatchingAddressCount());
         Assert::IsFalse(results2.ContainsAddress(0U));
@@ -330,6 +330,25 @@ public:
         Assert::AreEqual(0xABU, result.nValue);
     }
 
+    TEST_METHOD(TestInitializeFromResultsEightBitEqualsInvalidConstant)
+    {
+        std::array<unsigned char, 5> memory{ 0x00, 0x12, 0x34, 0xAB, 0x56 };
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results1;
+        results1.Initialize(1U, 3U, ra::services::SearchType::EightBit);
+        Assert::AreEqual({ 3U }, results1.MatchingAddressCount());
+
+        SearchResults results2;
+        Assert::IsFalse(results2.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L""));
+        Assert::IsFalse(results2.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L"Banana"));
+        Assert::IsFalse(results2.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L"3.14"));
+        Assert::IsFalse(results2.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L"$1234"));
+        Assert::IsTrue(results2.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L"ABBA")); // can be read as hex
+        Assert::IsTrue(results2.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L"0xAB"));
+    }
+
     TEST_METHOD(TestInitializeFromResultsEightBitEqualsConstantGap)
     {
         std::array<unsigned char, 6> memory{ 0x00, 0x12, 0x00, 0x00, 0x56, 0x00 };
@@ -341,13 +360,13 @@ public:
         Assert::AreEqual({ 6U }, results1.MatchingAddressCount());
 
         SearchResults results2;
-        results2.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, 0x00U);
+        results2.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L"0");
         Assert::AreEqual({ 4U }, results2.MatchingAddressCount());
 
         memory.at(1) = 0x00; // not previously matched, should be ignored
         memory.at(3) = 0x01; // previously matched, should no longer match
         SearchResults results3;
-        results3.Initialize(results2, ComparisonType::Equals, ra::services::SearchFilterType::Constant, 0x00U);
+        results3.Initialize(results2, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L"0");
 
         Assert::AreEqual({ 3U }, results3.MatchingAddressCount());
         Assert::IsTrue(results3.ContainsAddress(0U));
@@ -385,7 +404,7 @@ public:
         Assert::AreEqual({ 3U }, results1.MatchingAddressCount());
 
         SearchResults results;
-        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::Constant, 0xABU);
+        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::Constant, L"0xAB");
 
         Assert::AreEqual({ 2U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -417,7 +436,7 @@ public:
         Assert::AreEqual({ 3U }, results1.MatchingAddressCount());
 
         SearchResults results;
-        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::Constant, 0x34U);
+        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::Constant, L"0x34");
 
         Assert::AreEqual({ 2U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -449,7 +468,7 @@ public:
         Assert::AreEqual({ 3U }, results1.MatchingAddressCount());
 
         SearchResults results;
-        results.Initialize(results1, ComparisonType::GreaterThan, ra::services::SearchFilterType::Constant, 0x34U);
+        results.Initialize(results1, ComparisonType::GreaterThan, ra::services::SearchFilterType::Constant, L"0x34");
 
         Assert::AreEqual({ 1U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -476,7 +495,7 @@ public:
         Assert::AreEqual({ 3U }, results1.MatchingAddressCount());
 
         SearchResults results;
-        results.Initialize(results1, ComparisonType::GreaterThanOrEqual, ra::services::SearchFilterType::Constant, 0x34U);
+        results.Initialize(results1, ComparisonType::GreaterThanOrEqual, ra::services::SearchFilterType::Constant, L"0x34");
 
         Assert::AreEqual({ 2U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -508,7 +527,7 @@ public:
         Assert::AreEqual({ 3U }, results1.MatchingAddressCount());
 
         SearchResults results;
-        results.Initialize(results1, ComparisonType::LessThan, ra::services::SearchFilterType::Constant, 0x34U);
+        results.Initialize(results1, ComparisonType::LessThan, ra::services::SearchFilterType::Constant, L"0x34");
 
         Assert::AreEqual({ 1U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -535,7 +554,7 @@ public:
         Assert::AreEqual({ 3U }, results1.MatchingAddressCount());
 
         SearchResults results;
-        results.Initialize(results1, ComparisonType::LessThanOrEqual, ra::services::SearchFilterType::Constant, 0x34U);
+        results.Initialize(results1, ComparisonType::LessThanOrEqual, ra::services::SearchFilterType::Constant, L"0x34");
 
         Assert::AreEqual({ 2U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -570,7 +589,7 @@ public:
         memory.at(1) = 0xAB;
         memory.at(3) = 0x12;
         SearchResults results;
-        results.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, 0xABU);
+        results.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L"0xAB");
 
         Assert::AreEqual({ 1U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -599,7 +618,7 @@ public:
         memory.at(1) = 0x14;
         memory.at(2) = 0x55;
         SearchResults results2;
-        results2.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results2.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 3U }, results2.MatchingAddressCount());
         Assert::IsTrue(results2.ContainsAddress(0U));
@@ -610,7 +629,7 @@ public:
 
         // no change - tests the "entire block matches" optimization
         SearchResults results3;
-        results3.Initialize(results2, ComparisonType::Equals, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results3.Initialize(results2, ComparisonType::Equals, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 3U }, results3.MatchingAddressCount());
         Assert::IsTrue(results3.ContainsAddress(0U));
@@ -628,7 +647,7 @@ public:
         // no change - tests the "entire block matches" optimization
         SearchResults results4;
         memory.at(3) = 0x99;
-        results4.Initialize(results3, ComparisonType::Equals, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results4.Initialize(results3, ComparisonType::Equals, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 2U }, results4.MatchingAddressCount());
         Assert::IsTrue(results4.ContainsAddress(0U));
@@ -656,7 +675,7 @@ public:
         memory.at(1) = 0x14;
         memory.at(2) = 0x55;
         SearchResults results2;
-        results2.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results2.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 2U }, results2.MatchingAddressCount());
         Assert::IsFalse(results2.ContainsAddress(0U));
@@ -678,7 +697,7 @@ public:
 
         // no change - tests the "entire block matches" optimization
         SearchResults results3;
-        results3.Initialize(results2, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results3.Initialize(results2, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
         Assert::AreEqual({ 0U }, results3.MatchingAddressCount());
     }
 
@@ -700,7 +719,7 @@ public:
         memory.at(99) = 0x99;
         memory.at(700) = 0x00;
         SearchResults results2;
-        results2.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results2.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
 
         // expectation: results2 should allocate several smaller blocks instead of one large one
         Assert::AreEqual({ 5U }, results2.MatchingAddressCount());
@@ -750,7 +769,7 @@ public:
         memory.at(1) = 0x14;
         memory.at(2) = 0x36;
         SearchResults results;
-        results.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::LastKnownValuePlus, 2U);
+        results.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::LastKnownValuePlus, L"2");
 
         Assert::AreEqual({ 2U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -784,7 +803,7 @@ public:
         memory.at(1) = 0x10;
         memory.at(2) = 0x32;
         SearchResults results;
-        results.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::LastKnownValueMinus, 2U);
+        results.Initialize(results1, ComparisonType::Equals, ra::services::SearchFilterType::LastKnownValueMinus, L"2");
 
         Assert::AreEqual({ 2U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -818,7 +837,7 @@ public:
         memory.at(1) = 0x14;
         memory.at(2) = 0x37;
         SearchResults results;
-        results.Initialize(results1, ComparisonType::GreaterThan, ra::services::SearchFilterType::LastKnownValuePlus, 2U);
+        results.Initialize(results1, ComparisonType::GreaterThan, ra::services::SearchFilterType::LastKnownValuePlus, L"2");
 
         Assert::AreEqual({ 1U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -846,7 +865,7 @@ public:
 
         memory.at(2) = 0x55;
         SearchResults results;
-        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 2U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -878,7 +897,7 @@ public:
 
         memory.at(4) = 0x55;
         SearchResults results;
-        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 1U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -904,7 +923,7 @@ public:
         memory.at(1) = 0x55;
         memory.at(3) = 0x66;
         SearchResults results;
-        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 2U }, results.MatchingAddressCount());
         Assert::IsTrue(results.ContainsAddress(0U));
@@ -923,7 +942,7 @@ public:
 
         memory.at(2) = 0x99;
         SearchResults results2;
-        results2.Initialize(results, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results2.Initialize(results, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 1U }, results2.MatchingAddressCount());
         Assert::IsFalse(results2.ContainsAddress(0U));
@@ -948,7 +967,7 @@ public:
         memory.at(2) = 0x55;
         memory.at(4) = 0x66;
         SearchResults results;
-        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 2U }, results.MatchingAddressCount());
         Assert::IsTrue(results.ContainsAddress(0U));
@@ -967,7 +986,7 @@ public:
 
         memory.at(6) = 0x99;
         SearchResults results2;
-        results2.Initialize(results, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results2.Initialize(results, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 1U }, results2.MatchingAddressCount());
         Assert::IsFalse(results2.ContainsAddress(0U));
@@ -991,7 +1010,7 @@ public:
 
         memory.at(2) = 0x55;
         SearchResults results;
-        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 2U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -1023,7 +1042,32 @@ public:
 
         memory.at(4) = 0x55;
         SearchResults results;
-        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
+
+        Assert::AreEqual({ 1U }, results.MatchingAddressCount());
+        Assert::IsFalse(results.ContainsAddress(0U));
+        Assert::IsTrue(results.ContainsAddress(1U));
+
+        SearchResults::Result result;
+        Assert::IsTrue(results.GetMatchingAddress(0U, result));
+        Assert::AreEqual(1U, result.nAddress);
+        Assert::AreEqual(MemSize::ThirtyTwoBitBigEndian, result.nSize);
+        Assert::AreEqual(0x1234AB55U, result.nValue);
+    }
+
+    TEST_METHOD(TestInitializeFromResultsThirtyTwoBitBigEndianLessThanPrevious)
+    {
+        std::array<unsigned char, 5> memory{ 0x00, 0x12, 0x34, 0xAB, 0x56 };
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results1;
+        results1.Initialize(0U, 5U, ra::services::SearchType::ThirtyTwoBitBigEndian);
+        Assert::AreEqual({ 2U }, results1.MatchingAddressCount());
+
+        memory.at(4) = 0x55;
+        SearchResults results;
+        results.Initialize(results1, ComparisonType::LessThan, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 1U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -1049,7 +1093,7 @@ public:
         memory.at(1) = 0x14;
         memory.at(2) = 0x55;
         SearchResults results;
-        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 3U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -1086,7 +1130,7 @@ public:
         Assert::AreEqual({ 6U }, results.MatchingAddressCount());
 
         SearchResults filtered1;
-        filtered1.Initialize(results, ComparisonType::Equals, ra::services::SearchFilterType::Constant, 1U);
+        filtered1.Initialize(results, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L"1");
         Assert::AreEqual({ 1U }, filtered1.MatchingAddressCount());
 
         SearchResults::Result result;
@@ -1098,13 +1142,13 @@ public:
         // Nibble_Upper no longer matches, but Nibble_Lower does. Neither should not be returned.
         memory.at(1) = 0x21;
         SearchResults filtered2;
-        filtered2.Initialize(filtered1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, 1U);
+        filtered2.Initialize(filtered1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L"1");
         Assert::AreEqual({ 0U }, filtered2.MatchingAddressCount());
 
         // Both nibbles match, only previously matched one should be returned
         memory.at(1) = 0x11;
         SearchResults filtered3;
-        filtered3.Initialize(filtered1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, 1U);
+        filtered3.Initialize(filtered1, ComparisonType::Equals, ra::services::SearchFilterType::Constant, L"1");
         Assert::AreEqual({ 1U }, filtered3.MatchingAddressCount());
 
         Assert::IsTrue(filtered3.GetMatchingAddress(0U, result));
@@ -1124,7 +1168,7 @@ public:
         Assert::AreEqual({ 5U }, results1.MatchingAddressCount());
 
         SearchResults results2;
-        results2.Initialize(results1, ComparisonType::GreaterThan, ra::services::SearchFilterType::Constant, 0x10U);
+        results2.Initialize(results1, ComparisonType::GreaterThan, ra::services::SearchFilterType::Constant, L"0x10");
         Assert::AreEqual({ 2U }, results2.MatchingAddressCount());
         Assert::IsFalse(results2.ContainsAddress(0U));
         Assert::IsTrue(results2.ContainsAddress(1U));
@@ -1137,7 +1181,7 @@ public:
         // Last Known Value would match indices 0, 3, and 4 as indices 1 and 2 were just changed
         // results2 limits search to indices 1 and 3, so only 3 should be matched
         SearchResults results3;
-        results3.Initialize(results1, results2, ComparisonType::Equals, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results3.Initialize(results1, results2, ComparisonType::Equals, ra::services::SearchFilterType::LastKnownValue, L"");
         Assert::AreEqual({ 1U }, results3.MatchingAddressCount());
         Assert::IsFalse(results3.ContainsAddress(0U));
         Assert::IsFalse(results3.ContainsAddress(1U));
@@ -1169,7 +1213,7 @@ public:
         memory.at(1) = 0x14;
         memory.at(2) = 0x55;
         SearchResults results;
-        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
 
         Assert::AreEqual({ 2U }, results.MatchingAddressCount());
         Assert::IsFalse(results.ContainsAddress(0U));
@@ -1300,6 +1344,7 @@ public:
         Assert::IsTrue(results.ContainsAddress(35U));
         Assert::IsFalse(results.ContainsAddress(36U));
 
+        // nValue for ASCII text is the first byte of the string. use FormattedValue to get the whole string
         SearchResults::Result result;
         Assert::IsTrue(results.GetMatchingAddress(0U, result));
         Assert::AreEqual(0U, result.nAddress);
@@ -1312,6 +1357,70 @@ public:
         Assert::AreEqual({ '.' }, result.nValue);
     }
 
+    TEST_METHOD(TestInitializeFromMemoryFloat)
+    {
+        std::array<unsigned char, 8> memory{ 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x46, 0x41 }; // -2.0, 12.375
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results;
+        results.Initialize(0U, memory.size(), ra::services::SearchType::Float);
+        Assert::AreEqual({ 5U }, results.MatchingAddressCount());
+
+        Assert::IsTrue(results.ContainsAddress(0U));
+        Assert::IsTrue(results.ContainsAddress(1U));
+        Assert::IsTrue(results.ContainsAddress(2U));
+        Assert::IsTrue(results.ContainsAddress(3U));
+        Assert::IsTrue(results.ContainsAddress(4U));
+        Assert::IsFalse(results.ContainsAddress(5U));
+        Assert::IsFalse(results.ContainsAddress(6U));
+        Assert::IsFalse(results.ContainsAddress(7U));
+
+        // nValue is an IEE-754 encoded float
+        SearchResults::Result result;
+        Assert::IsTrue(results.GetMatchingAddress(0U, result));
+        Assert::AreEqual(0U, result.nAddress);
+        Assert::AreEqual(MemSize::Float, result.nSize);
+        Assert::AreEqual(0xC0000000U, result.nValue);
+
+        Assert::IsTrue(results.GetMatchingAddress(4U, result));
+        Assert::AreEqual(4U, result.nAddress);
+        Assert::AreEqual(MemSize::Float, result.nSize);
+        Assert::AreEqual(0x41460000U, result.nValue);
+    }
+
+    TEST_METHOD(TestInitializeFromMemoryMBF32)
+    {
+        std::array<unsigned char, 8> memory{ 0x87, 0x46, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00 }; // 99.0, -0.5
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results;
+        results.Initialize(0U, memory.size(), ra::services::SearchType::MBF32);
+        Assert::AreEqual({ 5U }, results.MatchingAddressCount());
+
+        Assert::IsTrue(results.ContainsAddress(0U));
+        Assert::IsTrue(results.ContainsAddress(1U));
+        Assert::IsTrue(results.ContainsAddress(2U));
+        Assert::IsTrue(results.ContainsAddress(3U));
+        Assert::IsTrue(results.ContainsAddress(4U));
+        Assert::IsFalse(results.ContainsAddress(5U));
+        Assert::IsFalse(results.ContainsAddress(6U));
+        Assert::IsFalse(results.ContainsAddress(7U));
+
+        // nValue is an MBF32 encoded float
+        SearchResults::Result result;
+        Assert::IsTrue(results.GetMatchingAddress(0U, result));
+        Assert::AreEqual(0U, result.nAddress);
+        Assert::AreEqual(MemSize::MBF32, result.nSize);
+        Assert::AreEqual(0x00004687U, result.nValue);
+
+        Assert::IsTrue(results.GetMatchingAddress(4U, result));
+        Assert::AreEqual(4U, result.nAddress);
+        Assert::AreEqual(MemSize::MBF32, result.nSize);
+        Assert::AreEqual(0x00008080U, result.nValue);
+    }
+
     TEST_METHOD(TestInitializeFromResultsAsciiText)
     {
         std::array<unsigned char, 36> memory{
@@ -1459,6 +1568,99 @@ public:
         Assert::AreEqual(30U, result.nAddress); // shore
     }
 
+    TEST_METHOD(TestInitializeFromResultsFloatEqualsConstant)
+    {
+        std::array<unsigned char, 8> memory{ 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x46, 0x41 }; // -2.0, 12.375
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results;
+        results.Initialize(0U, memory.size(), ra::services::SearchType::Float);
+        Assert::AreEqual({ 5U }, results.MatchingAddressCount());
+
+        SearchResults results2;
+        results2.Initialize(results, ComparisonType::Equals, SearchFilterType::Constant, L"-2");
+        Assert::AreEqual({ 1U }, results2.MatchingAddressCount());
+
+        // nValue is an IEE-754 encoded float
+        SearchResults::Result result;
+        Assert::IsTrue(results2.GetMatchingAddress(0U, result));
+        Assert::AreEqual(0U, result.nAddress);
+        Assert::AreEqual(MemSize::Float, result.nSize);
+        Assert::AreEqual(0xC0000000U, result.nValue);
+    }
+
+    TEST_METHOD(TestInitializeFromResultsFloatGreaterThanLast)
+    {
+        std::array<unsigned char, 8> memory{ 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x46, 0x41 }; // -2.0, 12.375
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results;
+        results.Initialize(0U, memory.size(), ra::services::SearchType::Float);
+        Assert::AreEqual({ 5U }, results.MatchingAddressCount());
+
+        memory.at(3) = 0xBF; // -2.0 -> -1.0
+        memory.at(2) = 0x80;
+
+        SearchResults results2;
+        results2.Initialize(results, ComparisonType::GreaterThan, SearchFilterType::LastKnownValue, L"");
+        Assert::AreEqual({ 1U }, results2.MatchingAddressCount());
+
+        // nValue is an IEE-754 encoded float
+        SearchResults::Result result;
+        Assert::IsTrue(results2.GetMatchingAddress(0U, result));
+        Assert::AreEqual(0U, result.nAddress);
+        Assert::AreEqual(MemSize::Float, result.nSize);
+        Assert::AreEqual(0xBF800000U, result.nValue);
+    }
+
+    TEST_METHOD(TestInitializeFromResultsMBF32EqualsConstant)
+    {
+        std::array<unsigned char, 8> memory{ 0x87, 0x46, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00 }; // 99.0, -0.5
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results;
+        results.Initialize(0U, memory.size(), ra::services::SearchType::MBF32);
+        Assert::AreEqual({ 5U }, results.MatchingAddressCount());
+
+        SearchResults results2;
+        results2.Initialize(results, ComparisonType::Equals, SearchFilterType::Constant, L"-0.5");
+        Assert::AreEqual({ 1U }, results2.MatchingAddressCount());
+
+        // nValue is an MBF32 encoded float
+        SearchResults::Result result;
+        Assert::IsTrue(results2.GetMatchingAddress(0U, result));
+        Assert::AreEqual(4U, result.nAddress);
+        Assert::AreEqual(MemSize::MBF32, result.nSize);
+        Assert::AreEqual(0x00008080U, result.nValue);
+    }
+
+    TEST_METHOD(TestInitializeFromResultsMBF32GreaterThanLast)
+    {
+        std::array<unsigned char, 8> memory{ 0x87, 0x46, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00 }; // 99.0, -0.5
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results;
+        results.Initialize(0U, memory.size(), ra::services::SearchType::MBF32);
+        Assert::AreEqual({ 5U }, results.MatchingAddressCount());
+
+        memory.at(4) = 0x00; // -0.5 -> -0.0
+
+        SearchResults results2;
+        results2.Initialize(results, ComparisonType::GreaterThan, SearchFilterType::LastKnownValue, L"");
+        Assert::AreEqual({ 2U }, results2.MatchingAddressCount());
+
+        // nValue is an MBF32 encoded float
+        SearchResults::Result result;
+        Assert::IsTrue(results2.GetMatchingAddress(1U, result));
+        Assert::AreEqual(4U, result.nAddress);
+        Assert::AreEqual(MemSize::MBF32, result.nSize);
+        Assert::AreEqual(0x00008000U, result.nValue);
+    }
+
     TEST_METHOD(TestCopyConstructor)
     {
         std::array<unsigned char, 5> memory{0x00, 0x12, 0x34, 0xAB, 0x56};
@@ -1476,7 +1678,7 @@ public:
         memory.at(1) = 0x14;
         memory.at(2) = 0x55;
         SearchResults results2;
-        results2.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, 0U);
+        results2.Initialize(results1, ComparisonType::NotEqualTo, ra::services::SearchFilterType::LastKnownValue, L"");
         Assert::AreEqual({ 2U }, results2.MatchingAddressCount());
 
         SearchResults results3(results2);
@@ -1553,6 +1755,146 @@ public:
         Assert::AreEqual(std::wstring(L""), results1.GetFormattedValue(4U, MemSize::Nibble_Lower));
         Assert::AreEqual(std::wstring(L""), results1.GetFormattedValue(4U, MemSize::Nibble_Upper));
     }
+
+    TEST_METHOD(TestGetFormattedValueFloat)
+    {
+        std::array<unsigned char, 8> memory{ 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x46, 0x41 }; // -2.0, 12.375
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results1;
+        results1.Initialize(0U, 8U, ra::services::SearchType::Float);
+        Assert::AreEqual({ 5U }, results1.MatchingAddressCount());
+
+        Assert::AreEqual(std::wstring(L"-2.0"), results1.GetFormattedValue(0U, MemSize::Float));
+        Assert::AreEqual(std::wstring(L"12.375"), results1.GetFormattedValue(4U, MemSize::Float));
+        Assert::AreEqual(std::wstring(L"5.91191e-39"), results1.GetFormattedValue(2U, MemSize::Float));
+        Assert::AreEqual(std::wstring(L"8192.1875"), results1.GetFormattedValue(3U, MemSize::Float));
+    }
+
+    TEST_METHOD(TestGetFormattedValueMBF32)
+    {
+        std::array<unsigned char, 8> memory{ 0x87, 0x46, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00 }; // 99.0, -0.5
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results1;
+        results1.Initialize(0U, 8U, ra::services::SearchType::MBF32);
+        Assert::AreEqual({ 5U }, results1.MatchingAddressCount());
+
+        Assert::AreEqual(std::wstring(L"99.0"), results1.GetFormattedValue(0U, MemSize::MBF32));
+        Assert::AreEqual(std::wstring(L"-0.5"), results1.GetFormattedValue(4U, MemSize::MBF32));
+        Assert::AreEqual(std::wstring(L"1.47513e-39"), results1.GetFormattedValue(2U, MemSize::MBF32));
+        Assert::AreEqual(std::wstring(L"1.73475e-18"), results1.GetFormattedValue(1U, MemSize::MBF32));
+    }
+
+    TEST_METHOD(TestMatchesFilterEightBitGreaterThanLast)
+    {
+        std::array<unsigned char, 5> memory{ 6, 12, 18, 24, 30 };
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results1, results2;
+        results1.Initialize(1U, 3U, ra::services::SearchType::EightBit);
+
+        SearchResults::Result result1, result2, result3;
+        results1.GetMatchingAddress(0, result1);
+        results1.GetMatchingAddress(1, result2);
+        results1.GetMatchingAddress(2, result3);
+
+        results2.Initialize(results1, ComparisonType::GreaterThanOrEqual, SearchFilterType::LastKnownValue, L"");
+
+        Assert::IsTrue(results2.MatchesFilter(results1, result1));  // 12 >= 12 ?
+        Assert::IsTrue(results2.MatchesFilter(results1, result2));  // 18 >= 18 ?
+        Assert::IsTrue(results2.MatchesFilter(results1, result3));  // 24 >= 24 ?
+
+        result1.nValue = 15;
+        result2.nValue = 15;
+        result3.nValue = 30;
+
+        Assert::IsTrue(results2.MatchesFilter(results1, result1));  // 15 >= 12 ?
+        Assert::IsFalse(results2.MatchesFilter(results1, result2)); // 15 >= 18 ?
+        Assert::IsTrue(results2.MatchesFilter(results1, result3));  // 30 >= 24 ?
+    }
+
+    TEST_METHOD(TestMatchesFilterEightBitGreaterThanLastPlus)
+    {
+        std::array<unsigned char, 5> memory{ 6, 12, 18, 24, 30 };
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results1, results2;
+        results1.Initialize(1U, 3U, ra::services::SearchType::EightBit);
+
+        SearchResults::Result result1, result2, result3;
+        results1.GetMatchingAddress(0, result1);
+        results1.GetMatchingAddress(1, result2);
+        results1.GetMatchingAddress(2, result3);
+
+        results2.Initialize(results1, ComparisonType::GreaterThanOrEqual, SearchFilterType::LastKnownValuePlus, L"4");
+
+        Assert::IsFalse(results2.MatchesFilter(results1, result1)); // 12 >= 12 + 4 ?
+        Assert::IsFalse(results2.MatchesFilter(results1, result2)); // 18 >= 18 + 4 ?
+        Assert::IsFalse(results2.MatchesFilter(results1, result3)); // 24 >= 24 + 4 ?
+
+        result1.nValue = 15;
+        result2.nValue = 22;
+        result3.nValue = 30;
+
+        Assert::IsFalse(results2.MatchesFilter(results1, result1)); // 15 >= 12 + 4 ?
+        Assert::IsTrue(results2.MatchesFilter(results1, result2));  // 22 >= 18 + 4 ?
+        Assert::IsTrue(results2.MatchesFilter(results1, result3));  // 30 >= 24 + 4 ?
+    }
+
+    TEST_METHOD(TestMatchesFilterEightBitGreaterThanLastMinus)
+    {
+        std::array<unsigned char, 5> memory{ 6, 12, 18, 24, 30 };
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results1, results2;
+        results1.Initialize(1U, 3U, ra::services::SearchType::EightBit);
+
+        SearchResults::Result result1, result2, result3;
+        results1.GetMatchingAddress(0, result1);
+        results1.GetMatchingAddress(1, result2);
+        results1.GetMatchingAddress(2, result3);
+
+        results2.Initialize(results1, ComparisonType::GreaterThanOrEqual, SearchFilterType::LastKnownValueMinus, L"4");
+
+        Assert::IsTrue(results2.MatchesFilter(results1, result1));  // 12 >= 12 - 4 ?
+        Assert::IsTrue(results2.MatchesFilter(results1, result2));  // 18 >= 18 - 4 ?
+        Assert::IsTrue(results2.MatchesFilter(results1, result3));  // 24 >= 24 - 4 ?
+
+        result1.nValue = 10;
+        result2.nValue = 14;
+        result3.nValue = 18;
+
+        Assert::IsTrue(results2.MatchesFilter(results1, result1));  // 10 >= 12 - 4 ?
+        Assert::IsTrue(results2.MatchesFilter(results1, result2));  // 14 >= 18 - 4 ?
+        Assert::IsFalse(results2.MatchesFilter(results1, result3)); // 18 >= 24 - 4 ?
+    }
+
+    TEST_METHOD(TestMatchesFilterEightBitGreaterThanConstant)
+    {
+        std::array<unsigned char, 5> memory{ 6, 12, 18, 24, 30 };
+        ra::data::context::mocks::MockEmulatorContext mockEmulatorContext;
+        mockEmulatorContext.MockMemory(memory);
+
+        SearchResults results1, results2;
+        results1.Initialize(1U, 3U, ra::services::SearchType::EightBit);
+
+        SearchResults::Result result1, result2, result3;
+        results1.GetMatchingAddress(0, result1);
+        results1.GetMatchingAddress(1, result2);
+        results1.GetMatchingAddress(2, result3);
+
+        results2.Initialize(results1, ComparisonType::GreaterThanOrEqual, SearchFilterType::Constant, L"18");
+
+        Assert::IsFalse(results2.MatchesFilter(results1, result1)); // 12 >= 18 (12) ?
+        Assert::IsTrue(results2.MatchesFilter(results1, result2));  // 18 >= 18 (18) ?
+        Assert::IsTrue(results2.MatchesFilter(results1, result3));  // 24 >= 18 (24) ?
+    }
 };
 
 } // namespace tests
diff --git a/tests/ui/viewmodels/MemorySearchViewModel_Tests.cpp b/tests/ui/viewmodels/MemorySearchViewModel_Tests.cpp
index 4a2ec6d8..67f5a845 100644
--- a/tests/ui/viewmodels/MemorySearchViewModel_Tests.cpp
+++ b/tests/ui/viewmodels/MemorySearchViewModel_Tests.cpp
@@ -151,7 +151,7 @@ public:
         Assert::AreEqual(std::wstring(L""), search.GetFilterRange());
         Assert::AreEqual(std::wstring(L""), search.GetFilterValue());
 
-        Assert::AreEqual({ 9U }, search.SearchTypes().Count());
+        Assert::AreEqual({ 11U }, search.SearchTypes().Count());
         Assert::AreEqual((int)ra::services::SearchType::FourBit, search.SearchTypes().GetItemAt(0)->GetId());
         Assert::AreEqual(std::wstring(L"4-bit"), search.SearchTypes().GetItemAt(0)->GetLabel());
         Assert::AreEqual((int)ra::services::SearchType::EightBit, search.SearchTypes().GetItemAt(1)->GetId());
@@ -168,8 +168,12 @@ public:
         Assert::AreEqual(std::wstring(L"16-bit BE"), search.SearchTypes().GetItemAt(6)->GetLabel());
         Assert::AreEqual((int)ra::services::SearchType::ThirtyTwoBitBigEndian, search.SearchTypes().GetItemAt(7)->GetId());
         Assert::AreEqual(std::wstring(L"32-bit BE"), search.SearchTypes().GetItemAt(7)->GetLabel());
-        Assert::AreEqual((int)ra::services::SearchType::AsciiText, search.SearchTypes().GetItemAt(8)->GetId());
-        Assert::AreEqual(std::wstring(L"ASCII Text"), search.SearchTypes().GetItemAt(8)->GetLabel());
+        Assert::AreEqual((int)ra::services::SearchType::Float, search.SearchTypes().GetItemAt(8)->GetId());
+        Assert::AreEqual(std::wstring(L"Float"), search.SearchTypes().GetItemAt(8)->GetLabel());
+        Assert::AreEqual((int)ra::services::SearchType::MBF32, search.SearchTypes().GetItemAt(9)->GetId());
+        Assert::AreEqual(std::wstring(L"MBF32"), search.SearchTypes().GetItemAt(9)->GetLabel());
+        Assert::AreEqual((int)ra::services::SearchType::AsciiText, search.SearchTypes().GetItemAt(10)->GetId());
+        Assert::AreEqual(std::wstring(L"ASCII Text"), search.SearchTypes().GetItemAt(10)->GetLabel());
         Assert::AreEqual(ra::services::SearchType::EightBit, search.GetSearchType());
 
         Assert::AreEqual({ 6U }, search.ComparisonTypes().Count());