diff --git a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
index 4d6a392ab2..101611c253 100644
--- a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
+++ b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt
@@ -1018,6 +1018,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_core/maths/juce_Expression.cpp"
"../../../../../modules/juce_core/maths/juce_Expression.h"
"../../../../../modules/juce_core/maths/juce_MathsFunctions.h"
+ "../../../../../modules/juce_core/maths/juce_MathsFunctions_test.cpp"
"../../../../../modules/juce_core/maths/juce_NormalisableRange.h"
"../../../../../modules/juce_core/maths/juce_Random.cpp"
"../../../../../modules/juce_core/maths/juce_Random.h"
@@ -3075,6 +3076,7 @@ set_source_files_properties(
"../../../../../modules/juce_core/maths/juce_Expression.cpp"
"../../../../../modules/juce_core/maths/juce_Expression.h"
"../../../../../modules/juce_core/maths/juce_MathsFunctions.h"
+ "../../../../../modules/juce_core/maths/juce_MathsFunctions_test.cpp"
"../../../../../modules/juce_core/maths/juce_NormalisableRange.h"
"../../../../../modules/juce_core/maths/juce_Random.cpp"
"../../../../../modules/juce_core/maths/juce_Random.h"
diff --git a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj
index 8ee6b9d4f9..490890f14c 100644
--- a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj
@@ -1316,6 +1316,9 @@
true
+
+ true
+
true
diff --git a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters
index 4a99297aac..3e516cf817 100644
--- a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters
@@ -1930,6 +1930,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj
index 4543b0a551..17060885e4 100644
--- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj
@@ -1316,6 +1316,9 @@
true
+
+ true
+
true
diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters
index 2f118afbfa..57d886986f 100644
--- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters
@@ -1930,6 +1930,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj
index 8b2054b1b9..cdfe1ccb5c 100644
--- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj
+++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj
@@ -1316,6 +1316,9 @@
true
+
+ true
+
true
diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters
index 0090613495..e93c1f373a 100644
--- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters
+++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters
@@ -1930,6 +1930,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt
index 91ebd84ab0..786016a054 100644
--- a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt
+++ b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt
@@ -894,6 +894,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_core/maths/juce_Expression.cpp"
"../../../../../modules/juce_core/maths/juce_Expression.h"
"../../../../../modules/juce_core/maths/juce_MathsFunctions.h"
+ "../../../../../modules/juce_core/maths/juce_MathsFunctions_test.cpp"
"../../../../../modules/juce_core/maths/juce_NormalisableRange.h"
"../../../../../modules/juce_core/maths/juce_Random.cpp"
"../../../../../modules/juce_core/maths/juce_Random.h"
@@ -2649,6 +2650,7 @@ set_source_files_properties(
"../../../../../modules/juce_core/maths/juce_Expression.cpp"
"../../../../../modules/juce_core/maths/juce_Expression.h"
"../../../../../modules/juce_core/maths/juce_MathsFunctions.h"
+ "../../../../../modules/juce_core/maths/juce_MathsFunctions_test.cpp"
"../../../../../modules/juce_core/maths/juce_NormalisableRange.h"
"../../../../../modules/juce_core/maths/juce_Random.cpp"
"../../../../../modules/juce_core/maths/juce_Random.h"
diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj
index 16a3054c71..dcf2ccbd15 100644
--- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj
+++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj
@@ -1156,6 +1156,9 @@
true
+
+ true
+
true
diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters
index 0c8a0e1ed8..3e89495e2b 100644
--- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters
+++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters
@@ -1639,6 +1639,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt
index dcc9366d03..e2f90a4ed1 100644
--- a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt
+++ b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt
@@ -927,6 +927,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_core/maths/juce_Expression.cpp"
"../../../../../modules/juce_core/maths/juce_Expression.h"
"../../../../../modules/juce_core/maths/juce_MathsFunctions.h"
+ "../../../../../modules/juce_core/maths/juce_MathsFunctions_test.cpp"
"../../../../../modules/juce_core/maths/juce_NormalisableRange.h"
"../../../../../modules/juce_core/maths/juce_Random.cpp"
"../../../../../modules/juce_core/maths/juce_Random.h"
@@ -2837,6 +2838,7 @@ set_source_files_properties(
"../../../../../modules/juce_core/maths/juce_Expression.cpp"
"../../../../../modules/juce_core/maths/juce_Expression.h"
"../../../../../modules/juce_core/maths/juce_MathsFunctions.h"
+ "../../../../../modules/juce_core/maths/juce_MathsFunctions_test.cpp"
"../../../../../modules/juce_core/maths/juce_NormalisableRange.h"
"../../../../../modules/juce_core/maths/juce_Random.cpp"
"../../../../../modules/juce_core/maths/juce_Random.h"
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj
index 6b9b8868db..d5fc25189b 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj
+++ b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj
@@ -1164,6 +1164,9 @@
true
+
+ true
+
true
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters
index 4fcfcdf62c..21606bf9ae 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters
+++ b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters
@@ -1714,6 +1714,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj
index 1e39fc5d10..fc660c3969 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj
+++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj
@@ -1164,6 +1164,9 @@
true
+
+ true
+
true
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters
index 2c0ff11d93..b377ad122b 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters
+++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters
@@ -1714,6 +1714,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj
index c92f9b3be4..1ace548d2b 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj
+++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj
@@ -1164,6 +1164,9 @@
true
+
+ true
+
true
diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters
index d6a55b0f33..1291e712dc 100644
--- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters
+++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters
@@ -1714,6 +1714,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj
index 99120550ae..8365f8a7ba 100644
--- a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj
+++ b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj
@@ -226,6 +226,9 @@
true
+
+ true
+
true
diff --git a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters
index 77902d1382..6967555eab 100644
--- a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters
+++ b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters
@@ -163,6 +163,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt
index 14a8efabfe..827da1f1f1 100644
--- a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt
+++ b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt
@@ -898,6 +898,7 @@ add_library( ${BINARY_NAME}
"../../../../../modules/juce_core/maths/juce_Expression.cpp"
"../../../../../modules/juce_core/maths/juce_Expression.h"
"../../../../../modules/juce_core/maths/juce_MathsFunctions.h"
+ "../../../../../modules/juce_core/maths/juce_MathsFunctions_test.cpp"
"../../../../../modules/juce_core/maths/juce_NormalisableRange.h"
"../../../../../modules/juce_core/maths/juce_Random.cpp"
"../../../../../modules/juce_core/maths/juce_Random.h"
@@ -2733,6 +2734,7 @@ set_source_files_properties(
"../../../../../modules/juce_core/maths/juce_Expression.cpp"
"../../../../../modules/juce_core/maths/juce_Expression.h"
"../../../../../modules/juce_core/maths/juce_MathsFunctions.h"
+ "../../../../../modules/juce_core/maths/juce_MathsFunctions_test.cpp"
"../../../../../modules/juce_core/maths/juce_NormalisableRange.h"
"../../../../../modules/juce_core/maths/juce_Random.cpp"
"../../../../../modules/juce_core/maths/juce_Random.h"
diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj
index 3a31c059f6..eb75d0e7e8 100644
--- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj
+++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj
@@ -1156,6 +1156,9 @@
true
+
+ true
+
true
diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters
index 910f7564c6..8f75cfd8a0 100644
--- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters
+++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters
@@ -1669,6 +1669,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj
index 56236a5e16..703e635c97 100644
--- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj
+++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj
@@ -362,6 +362,9 @@
true
+
+ true
+
true
diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters
index b0fc221c32..259ebdcaca 100644
--- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters
+++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters
@@ -640,6 +640,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj
index f36375c919..0444ca02e4 100644
--- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj
+++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj
@@ -362,6 +362,9 @@
true
+
+ true
+
true
diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters
index bf6b52ad66..4a976f8376 100644
--- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters
+++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters
@@ -640,6 +640,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj
index 151e70b76a..907655c3c1 100644
--- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj
+++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj
@@ -362,6 +362,9 @@
true
+
+ true
+
true
diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters
index 15c39f8c7e..663fe31f35 100644
--- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters
+++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters
@@ -640,6 +640,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj
index 97955ae31e..e51eba141b 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj
+++ b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj
@@ -1172,6 +1172,9 @@
true
+
+ true
+
true
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters
index b7e10e429d..16bc7c745b 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters
+++ b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters
@@ -1738,6 +1738,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj
index 74e020bb64..3e4e42ce35 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj
+++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj
@@ -1172,6 +1172,9 @@
true
+
+ true
+
true
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters
index 52c631f8f8..939d8d5fe2 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters
+++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters
@@ -1738,6 +1738,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj
index 18f5f96ec4..cc21666e94 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj
+++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj
@@ -1172,6 +1172,9 @@
true
+
+ true
+
true
diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters
index 635d74841f..bb7217c9ff 100644
--- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters
+++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters
@@ -1738,6 +1738,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj
index 39f10c4446..7a4db94484 100644
--- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj
+++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj
@@ -1155,6 +1155,9 @@
true
+
+ true
+
true
diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters
index 43806d2f2f..4a13cce29d 100644
--- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters
+++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters
@@ -1666,6 +1666,9 @@
JUCE Modules\juce_core\maths
+
+ JUCE Modules\juce_core\maths
+
JUCE Modules\juce_core\maths
diff --git a/modules/juce_core/juce_core.cpp b/modules/juce_core/juce_core.cpp
index 6ed8bd9c0e..788c74b027 100644
--- a/modules/juce_core/juce_core.cpp
+++ b/modules/juce_core/juce_core.cpp
@@ -276,8 +276,8 @@
//==============================================================================
#if JUCE_UNIT_TESTS
#include "containers/juce_HashMap_test.cpp"
-
#include "containers/juce_Optional_test.cpp"
+ #include "maths/juce_MathsFunctions_test.cpp"
#include "misc/juce_EnumHelpers_test.cpp"
#endif
diff --git a/modules/juce_core/maths/juce_MathsFunctions.h b/modules/juce_core/maths/juce_MathsFunctions.h
index f47ea1154c..186693af01 100644
--- a/modules/juce_core/maths/juce_MathsFunctions.h
+++ b/modules/juce_core/maths/juce_MathsFunctions.h
@@ -86,16 +86,112 @@ using uint32 = unsigned int;
using ssize_t = pointer_sized_int;
#endif
-/** Returns true if the two numbers are approximately equal. This is useful for floating-point
- and double comparisons.
+//==============================================================================
+/** Handy function for avoiding unused variables warning. */
+template
+void ignoreUnused (Types&&...) noexcept {}
+
+/** Handy function for getting the number of elements in a simple const C array.
+ E.g.
+ @code
+ static int myArray[] = { 1, 2, 3 };
+
+ int numElements = numElementsInArray (myArray) // returns 3
+ @endcode
*/
+template
+constexpr int numElementsInArray (Type (&)[N]) noexcept { return N; }
+
+//==============================================================================
+// Some useful maths functions that aren't always present with all compilers and build settings.
+
+/** Using juce_hypot is easier than dealing with the different types of hypot function
+ that are provided by the various platforms and compilers. */
template
-bool approximatelyEqual (Type a, Type b) noexcept
+Type juce_hypot (Type a, Type b) noexcept
{
- return std::abs (a - b) <= (std::numeric_limits::epsilon() * std::max (a, b))
- || std::abs (a - b) < std::numeric_limits::min();
+ #if JUCE_MSVC
+ return static_cast (_hypot (a, b));
+ #else
+ return static_cast (hypot (a, b));
+ #endif
}
+#ifndef DOXYGEN
+template <>
+inline float juce_hypot (float a, float b) noexcept
+{
+ #if JUCE_MSVC
+ return _hypotf (a, b);
+ #else
+ return hypotf (a, b);
+ #endif
+}
+#endif
+
+//==============================================================================
+/** Commonly used mathematical constants
+
+ @tags{Core}
+*/
+template
+struct MathConstants
+{
+ /** A predefined value for Pi */
+ static constexpr FloatType pi = static_cast (3.141592653589793238L);
+
+ /** A predefined value for 2 * Pi */
+ static constexpr FloatType twoPi = static_cast (2 * 3.141592653589793238L);
+
+ /** A predefined value for Pi / 2 */
+ static constexpr FloatType halfPi = static_cast (3.141592653589793238L / 2);
+
+ /** A predefined value for Euler's number */
+ static constexpr FloatType euler = static_cast (2.71828182845904523536L);
+
+ /** A predefined value for sqrt(2) */
+ static constexpr FloatType sqrt2 = static_cast (1.4142135623730950488L);
+};
+
+#ifndef DOXYGEN
+/** A double-precision constant for pi. */
+[[deprecated ("This is deprecated in favour of MathConstants::pi.")]]
+const constexpr double double_Pi = MathConstants::pi;
+
+/** A single-precision constant for pi. */
+[[deprecated ("This is deprecated in favour of MathConstants::pi.")]]
+const constexpr float float_Pi = MathConstants::pi;
+#endif
+
+/** Converts an angle in degrees to radians. */
+template
+constexpr FloatType degreesToRadians (FloatType degrees) noexcept { return degrees * (MathConstants::pi / FloatType (180)); }
+
+/** Converts an angle in radians to degrees. */
+template
+constexpr FloatType radiansToDegrees (FloatType radians) noexcept { return radians * (FloatType (180) / MathConstants::pi); }
+
+//==============================================================================
+/** The isfinite() method seems to vary between platforms, so this is a
+ platform-independent function for it.
+*/
+template
+bool juce_isfinite (NumericType value) noexcept
+{
+ if constexpr (std::numeric_limits::has_infinity
+ || std::numeric_limits::has_quiet_NaN
+ || std::numeric_limits::has_signaling_NaN)
+ {
+ return std::isfinite (value);
+ }
+ else
+ {
+ ignoreUnused (value);
+ return true;
+ }
+}
+
+//==============================================================================
/** Equivalent to operator==, but suppresses float-equality warnings.
This allows code to be explicit about float-equality checks that are known to have the correct
@@ -109,6 +205,128 @@ constexpr bool exactlyEqual (Type a, Type b)
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
}
+/** A class encapsulating both relative and absolute tolerances for use in floating-point comparisons.
+
+ @see approximatelyEqual, absoluteTolerance, relativeTolerance
+*/
+template
+class Tolerance
+{
+public:
+ Tolerance() = default;
+
+ /** Returns a copy of this Tolerance object with a new absolute tolerance.
+
+ If you just need a Tolerance object with an absolute tolerance, it might be worth using the
+ absoluteTolerance() function.
+
+ @see getAbsolute, absoluteTolerance
+ */
+ [[nodiscard]] Tolerance withAbsolute (Type newAbsolute)
+ {
+ return withMember (*this, &Tolerance::absolute, std::abs (newAbsolute));
+ }
+
+ /** Returns a copy of this Tolerance object with a new relative tolerance.
+
+ If you just need a Tolerance object with a relative tolerance, it might be worth using the
+ relativeTolerance() function.
+
+ @see getRelative, relativeTolerance
+ */
+ [[nodiscard]] Tolerance withRelative (Type newRelative)
+ {
+ return withMember (*this, &Tolerance::relative, std::abs (newRelative));
+ }
+
+ [[nodiscard]] Type getAbsolute() const { return absolute; }
+ [[nodiscard]] Type getRelative() const { return relative; }
+
+private:
+ Type absolute{};
+ Type relative{};
+};
+
+/** Returns a type deduced Tolerance object containing only an absolute tolerance.
+
+ @see Tolerance::withAbsolute, approximatelyEqual
+ */
+template
+static Tolerance absoluteTolerance (Type tolerance)
+{
+ return Tolerance{}.withAbsolute (tolerance);
+}
+
+/** Returns a type deduced Tolerance object containing only a relative tolerance.
+
+ @see Tolerance::withRelative, approximatelyEqual
+ */
+template
+static Tolerance relativeTolerance (Type tolerance)
+{
+ return Tolerance{}.withRelative (tolerance);
+}
+
+
+/** Returns true if the two numbers are approximately equal. This is useful for floating-point
+ comparisons.
+
+ If a and b are not floating-point types, returns a == b.
+
+ If either a or b are not finite, returns exactlyEqual (a, b).
+
+ The default absolute tolerance is equal to the minimum normal value. This ensures
+ differences that are subnormal are always considered equal. It is highly recommend this
+ value is reviewed depending on the calculation being carried out. In general specifying an
+ absolute value is useful when considering values close to zero. For example you might
+ expect sin(pi) to return 0, but what it actually returns is close to the error of the value pi.
+ Therefore, in this example it might be better to set the absolute tolerance to sin(pi).
+
+ The default relative tolerance is equal to the machine epsilon which is the difference between
+ 1.0 and the next floating-point value that can be represented by Type. In most cases this value
+ is probably reasonable. This value is multiplied by the largest absolute value of a and b so as
+ to scale relatively according to the input parameters. For example, specifying a relative value
+ of 0.05 will ensure values return equal if the difference between them is less than or equal to
+ 5% of the larger of the two absolute values.
+
+ @param tolerance An object that represents both absolute and relative tolerances
+ when evaluating if a and b are equal.
+
+ @see exactlyEqual
+*/
+template
+constexpr bool approximatelyEqual (Type a, Type b,
+ Tolerance tolerance = Tolerance{}
+ .withAbsolute (std::numeric_limits::min())
+ .withRelative (std::numeric_limits::epsilon()))
+{
+ if constexpr (! std::is_floating_point_v)
+ return a == b;
+
+ if (! (juce_isfinite (a) && juce_isfinite (b)))
+ return exactlyEqual (a, b);
+
+ const auto diff = std::abs (a - b);
+
+ return diff <= tolerance.getAbsolute()
+ || diff <= tolerance.getRelative() * std::max (std::abs (a), std::abs (b));
+}
+
+//==============================================================================
+/** Returns the next representable value by FloatType in the direction of the largest representable value. */
+template
+FloatType nextFloatUp (FloatType value) noexcept
+{
+ return std::nextafter (value, std::numeric_limits::max());
+}
+
+/** Returns the next representable value by FloatType in the direction of the lowest representable value. */
+template
+FloatType nextFloatDown (FloatType value) noexcept
+{
+ return std::nextafter (value, std::numeric_limits::lowest());
+}
+
//==============================================================================
// Some indispensable min/max functions
@@ -340,122 +558,6 @@ bool isWithin (Type a, Type b, Type tolerance) noexcept
return std::abs (a - b) <= tolerance;
}
-//==============================================================================
-/** Handy function for avoiding unused variables warning. */
-template
-void ignoreUnused (Types&&...) noexcept {}
-
-/** Handy function for getting the number of elements in a simple const C array.
- E.g.
- @code
- static int myArray[] = { 1, 2, 3 };
-
- int numElements = numElementsInArray (myArray) // returns 3
- @endcode
-*/
-template
-constexpr int numElementsInArray (Type (&)[N]) noexcept { return N; }
-
-//==============================================================================
-// Some useful maths functions that aren't always present with all compilers and build settings.
-
-/** Using juce_hypot is easier than dealing with the different types of hypot function
- that are provided by the various platforms and compilers. */
-template
-Type juce_hypot (Type a, Type b) noexcept
-{
- #if JUCE_MSVC
- return static_cast (_hypot (a, b));
- #else
- return static_cast (hypot (a, b));
- #endif
-}
-
-#ifndef DOXYGEN
-template <>
-inline float juce_hypot (float a, float b) noexcept
-{
- #if JUCE_MSVC
- return _hypotf (a, b);
- #else
- return hypotf (a, b);
- #endif
-}
-#endif
-
-//==============================================================================
-/** Commonly used mathematical constants
-
- @tags{Core}
-*/
-template
-struct MathConstants
-{
- /** A predefined value for Pi */
- static constexpr FloatType pi = static_cast (3.141592653589793238L);
-
- /** A predefined value for 2 * Pi */
- static constexpr FloatType twoPi = static_cast (2 * 3.141592653589793238L);
-
- /** A predefined value for Pi / 2 */
- static constexpr FloatType halfPi = static_cast (3.141592653589793238L / 2);
-
- /** A predefined value for Euler's number */
- static constexpr FloatType euler = static_cast (2.71828182845904523536L);
-
- /** A predefined value for sqrt(2) */
- static constexpr FloatType sqrt2 = static_cast (1.4142135623730950488L);
-};
-
-#ifndef DOXYGEN
-/** A double-precision constant for pi. */
-[[deprecated ("This is deprecated in favour of MathConstants::pi.")]]
-const constexpr double double_Pi = MathConstants::pi;
-
-/** A single-precision constant for pi. */
-[[deprecated ("This is deprecated in favour of MathConstants::pi.")]]
-const constexpr float float_Pi = MathConstants::pi;
-#endif
-
-/** Converts an angle in degrees to radians. */
-template
-constexpr FloatType degreesToRadians (FloatType degrees) noexcept { return degrees * (MathConstants::pi / FloatType (180)); }
-
-/** Converts an angle in radians to degrees. */
-template
-constexpr FloatType radiansToDegrees (FloatType radians) noexcept { return radians * (FloatType (180) / MathConstants::pi); }
-
-
-//==============================================================================
-/** The isfinite() method seems to vary between platforms, so this is a
- platform-independent function for it.
-*/
-template
-bool juce_isfinite (NumericType) noexcept
-{
- return true; // Integer types are always finite
-}
-
-template <>
-inline bool juce_isfinite (float value) noexcept
-{
- #if JUCE_WINDOWS && ! JUCE_MINGW
- return _finite (value) != 0;
- #else
- return std::isfinite (value);
- #endif
-}
-
-template <>
-inline bool juce_isfinite (double value) noexcept
-{
- #if JUCE_WINDOWS && ! JUCE_MINGW
- return _finite (value) != 0;
- #else
- return std::isfinite (value);
- #endif
-}
-
//==============================================================================
#if JUCE_MSVC
#pragma optimize ("t", off)
diff --git a/modules/juce_core/maths/juce_MathsFunctions_test.cpp b/modules/juce_core/maths/juce_MathsFunctions_test.cpp
new file mode 100644
index 0000000000..1dbed3d63d
--- /dev/null
+++ b/modules/juce_core/maths/juce_MathsFunctions_test.cpp
@@ -0,0 +1,483 @@
+/*
+ ==============================================================================
+
+ This file is part of the JUCE library.
+ Copyright (c) 2022 - Raw Material Software Limited
+
+ JUCE is an open source library subject to commercial or open-source
+ licensing.
+
+ The code included in this file is provided under the terms of the ISC license
+ http://www.isc.org/downloads/software-support-policy/isc-license. Permission
+ To use, copy, modify, and/or distribute this software for any purpose with or
+ without fee is hereby granted provided that the above copyright notice and
+ this permission notice appear in all copies.
+
+ JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
+ EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
+ DISCLAIMED.
+
+ ==============================================================================
+*/
+
+namespace juce
+{
+
+//==============================================================================
+template
+String getTemplatedMathsFunctionUnitTestName (const String& functionName)
+{
+ const auto getTypeName = []() -> String
+ {
+ if constexpr (std::is_same_v)
+ return "float";
+
+ if constexpr (std::is_same_v)
+ return "double";
+
+ if constexpr (std::is_same_v)
+ return "long double";
+ };
+
+ return functionName + "<" + getTypeName() + ">";
+}
+
+template
+class ApproximatelyEqualTests final : public UnitTest
+{
+public:
+ ApproximatelyEqualTests()
+ : UnitTest { getTemplatedMathsFunctionUnitTestName ("approximatelyEqual"), UnitTestCategories::maths }
+ {}
+
+ void runTest() final
+ {
+ using limits = std::numeric_limits;
+
+ constexpr auto zero = T{};
+ constexpr auto one = T (1);
+ constexpr auto min = limits::min();
+ constexpr auto max = limits::max();
+ constexpr auto epsilon = limits::epsilon();
+ constexpr auto oneThird = one / (T) 3;
+
+ beginTest ("Equal values are always equal");
+ {
+ expect (approximatelyEqual (zero, zero));
+ expect (approximatelyEqual (zero, -zero));
+ expect (approximatelyEqual (-zero, -zero));
+
+ expect (approximatelyEqual (min, min));
+ expect (approximatelyEqual (-min, -min));
+
+ expect (approximatelyEqual (one, one));
+ expect (approximatelyEqual (-one, -one));
+
+ expect (approximatelyEqual (max, max));
+ expect (approximatelyEqual (-max, -max));
+
+ const Tolerance zeroTolerance{};
+
+ expect (approximatelyEqual (zero, zero, zeroTolerance));
+ expect (approximatelyEqual (zero, -zero, zeroTolerance));
+ expect (approximatelyEqual (-zero, -zero, zeroTolerance));
+
+ expect (approximatelyEqual (min, min, zeroTolerance));
+ expect (approximatelyEqual (-min, -min, zeroTolerance));
+
+ expect (approximatelyEqual (one, one, zeroTolerance));
+ expect (approximatelyEqual (-one, -one, zeroTolerance));
+
+ expect (approximatelyEqual (max, max, zeroTolerance));
+ expect (approximatelyEqual (-max, -max, zeroTolerance));
+ }
+
+ beginTest ("Comparing subnormal values to zero, returns true");
+ {
+ expect (! exactlyEqual (zero, nextFloatUp (zero)));
+ expect (approximatelyEqual (zero, nextFloatUp (zero)));
+
+ expect (! exactlyEqual (zero, nextFloatDown (zero)));
+ expect (approximatelyEqual (zero, nextFloatDown (zero)));
+
+ expect (! exactlyEqual (zero, nextFloatDown (min)));
+ expect (approximatelyEqual (zero, nextFloatDown (min)));
+
+ expect (! exactlyEqual (zero, nextFloatUp (-min)));
+ expect (approximatelyEqual (zero, nextFloatUp (-min)));
+ }
+
+ beginTest ("Comparing the minimum normal value to zero, returns true");
+ {
+ expect (approximatelyEqual (zero, min));
+ expect (approximatelyEqual (zero, -min));
+ }
+
+ beginTest ("Comparing normal values greater than the minimum to zero, returns true");
+ {
+ expect (! approximatelyEqual (zero, one));
+ expect (! approximatelyEqual (zero, epsilon));
+ expect (! approximatelyEqual (zero, nextFloatUp (min)));
+ expect (! approximatelyEqual (zero, nextFloatDown (-min)));
+ }
+
+ beginTest ("Values with large ranges can be compared");
+ {
+ expect (! approximatelyEqual (zero, max));
+ expect ( approximatelyEqual (zero, max, absoluteTolerance (max)));
+ expect ( approximatelyEqual (zero, max, relativeTolerance (one)));
+ expect (! approximatelyEqual (-one, max));
+ expect (! approximatelyEqual (-max, max));
+ }
+
+ beginTest ("Larger values have a boundary that is a factor of the epsilon");
+ {
+ for (auto exponent = 0; exponent < 127; ++exponent)
+ {
+ const auto value = std::pow ((T) 2, (T) exponent);
+ const auto boundaryValue = value * (one + epsilon);
+
+ expect (juce_isfinite (value));
+ expect (juce_isfinite (boundaryValue));
+
+ expect ( approximatelyEqual (value, boundaryValue));
+ expect (! approximatelyEqual (value, nextFloatUp (boundaryValue)));
+
+ expect ( approximatelyEqual (-value, -boundaryValue));
+ expect (! approximatelyEqual (-value, nextFloatDown (-boundaryValue)));
+ }
+ }
+
+ beginTest ("Tolerances scale with the values being compared");
+ {
+
+ expect (approximatelyEqual ((T) 100'000'000'000'000.01,
+ (T) 100'000'000'000'000.011));
+
+ expect (! approximatelyEqual ((T) 100.01,
+ (T) 100.011));
+
+ expect (! approximatelyEqual ((T) 123'000, (T) 121'000, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 123'000, (T) 122'000, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 123'000, (T) 123'000, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 123'000, (T) 124'000, relativeTolerance ((T) 1e-2)));
+ expect (! approximatelyEqual ((T) 123'000, (T) 125'000, relativeTolerance ((T) 1e-2)));
+
+ expect (! approximatelyEqual ((T) 123, (T) 121, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 123, (T) 122, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 123, (T) 123, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 123, (T) 124, relativeTolerance ((T) 1e-2)));
+ expect (! approximatelyEqual ((T) 123, (T) 125, relativeTolerance ((T) 1e-2)));
+
+ expect (! approximatelyEqual ((T) 12.3, (T) 12.1, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 12.3, (T) 12.2, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 12.3, (T) 12.3, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 12.3, (T) 12.4, relativeTolerance ((T) 1e-2)));
+ expect (! approximatelyEqual ((T) 12.3, (T) 12.5, relativeTolerance ((T) 1e-2)));
+
+ expect (! approximatelyEqual ((T) 1.23, (T) 1.21, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 1.23, (T) 1.22, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 1.23, (T) 1.23, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 1.23, (T) 1.24, relativeTolerance ((T) 1e-2)));
+ expect (! approximatelyEqual ((T) 1.23, (T) 1.25, relativeTolerance ((T) 1e-2)));
+
+ expect (! approximatelyEqual ((T) 0.123, (T) 0.121, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 0.123, (T) 0.122, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 0.123, (T) 0.123, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 0.123, (T) 0.124, relativeTolerance ((T) 1e-2)));
+ expect (! approximatelyEqual ((T) 0.123, (T) 0.125, relativeTolerance ((T) 1e-2)));
+
+ expect (! approximatelyEqual ((T) 0.000123, (T) 0.000121, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 0.000123, (T) 0.000122, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 0.000123, (T) 0.000123, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual ((T) 0.000123, (T) 0.000124, relativeTolerance ((T) 1e-2)));
+ expect (! approximatelyEqual ((T) 0.000123, (T) 0.000125, relativeTolerance ((T) 1e-2)));
+ }
+
+ beginTest ("The square of the square root of 2 is approximately 2");
+ {
+ constexpr auto two = (T) 2;
+ const auto sqrtOfTwo = std::sqrt (two);
+
+ expect (approximatelyEqual (sqrtOfTwo * sqrtOfTwo, two));
+ expect (approximatelyEqual (-sqrtOfTwo * sqrtOfTwo, -two));
+ expect (approximatelyEqual (two / sqrtOfTwo, sqrtOfTwo));
+ }
+
+ if constexpr (limits::has_quiet_NaN)
+ {
+ beginTest ("Values are never equal to NaN");
+ {
+ const auto nan = limits::quiet_NaN();
+
+ expect (! approximatelyEqual (nan, nan));
+
+ const auto expectNotEqualTo = [&](auto value)
+ {
+ expect (! approximatelyEqual (value, nan));
+ expect (! approximatelyEqual (nan, value));
+ };
+
+ expectNotEqualTo (zero);
+ expectNotEqualTo (-zero);
+ expectNotEqualTo (min);
+ expectNotEqualTo (-min);
+ expectNotEqualTo (one);
+ expectNotEqualTo (-one);
+ expectNotEqualTo (max);
+ expectNotEqualTo (-max);
+ }
+ }
+
+ if constexpr (limits::has_infinity)
+ {
+ beginTest ("Only infinity is equal to infinity");
+ {
+ const auto inf = limits::infinity();
+ expect (approximatelyEqual (inf, inf));
+ expect (approximatelyEqual (-inf, -inf));
+ expect (! approximatelyEqual (inf, -inf));
+ expect (! approximatelyEqual (-inf, inf));
+
+ const auto expectNotEqualTo = [&](auto value)
+ {
+ expect (! approximatelyEqual (value, inf));
+ expect (! approximatelyEqual (value, -inf));
+ expect (! approximatelyEqual (inf, value));
+ expect (! approximatelyEqual (-inf, value));
+ };
+
+ expectNotEqualTo (zero);
+ expectNotEqualTo (-zero);
+ expectNotEqualTo (min);
+ expectNotEqualTo (-min);
+ expectNotEqualTo (one);
+ expectNotEqualTo (-one);
+ expectNotEqualTo (max);
+ expectNotEqualTo (-max);
+ }
+ }
+
+ beginTest ("Can set an absolute tolerance");
+ {
+ constexpr std::array negativePowersOfTwo
+ {
+ (T) 0.5 /* 2^-1 */,
+ (T) 0.25 /* 2^-2 */,
+ (T) 0.125 /* 2^-3 */,
+ (T) 0.0625 /* 2^-4 */,
+ (T) 0.03125 /* 2^-5 */,
+ (T) 0.015625 /* 2^-6 */,
+ (T) 0.0078125 /* 2^-7 */
+ };
+
+ const auto testTolerance = [&](auto tolerance)
+ {
+ const auto t = Tolerance{}.withAbsolute ((T) tolerance);
+
+ const auto testValue= [&](auto value)
+ {
+ const auto boundary = value + tolerance;
+
+ expect (approximatelyEqual (value, boundary, t));
+ expect (! approximatelyEqual (value, nextFloatUp (boundary), t));
+
+ expect (approximatelyEqual (-value, -boundary, t));
+ expect (! approximatelyEqual (-value, nextFloatDown (-boundary), t));
+ };
+
+ testValue (zero);
+ testValue (min);
+ testValue (epsilon);
+ testValue (one);
+
+ for (const auto value : negativePowersOfTwo)
+ testValue (value);
+ };
+
+ for (const auto toleranceValue : negativePowersOfTwo)
+ testTolerance (toleranceValue);
+ }
+
+ beginTest ("Can set a relative tolerance");
+ {
+ expect (! approximatelyEqual (oneThird, (T) 0.34, relativeTolerance ((T) 1e-2)));
+ expect ( approximatelyEqual (oneThird, (T) 0.334, relativeTolerance ((T) 1e-2)));
+
+ expect (! approximatelyEqual (oneThird, (T) 0.334, relativeTolerance ((T) 1e-3)));
+ expect ( approximatelyEqual (oneThird, (T) 0.3334, relativeTolerance ((T) 1e-3)));
+
+ expect (! approximatelyEqual (oneThird, (T) 0.3334, relativeTolerance ((T) 1e-4)));
+ expect ( approximatelyEqual (oneThird, (T) 0.33334, relativeTolerance ((T) 1e-4)));
+
+ expect (! approximatelyEqual (oneThird, (T) 0.33334, relativeTolerance ((T) 1e-5)));
+ expect ( approximatelyEqual (oneThird, (T) 0.333334, relativeTolerance ((T) 1e-5)));
+
+ expect (! approximatelyEqual (oneThird, (T) 0.333334, relativeTolerance ((T) 1e-6)));
+ expect ( approximatelyEqual (oneThird, (T) 0.3333334, relativeTolerance ((T) 1e-6)));
+
+ expect (! approximatelyEqual (oneThird, (T) 0.3333334, relativeTolerance ((T) 1e-7)));
+ expect ( approximatelyEqual (oneThird, (T) 0.33333334, relativeTolerance ((T) 1e-7)));
+
+ expect ( approximatelyEqual ((T) 1e6, (T) 1e6 + (T) 1, relativeTolerance ((T) 1e-6)));
+ expect (! approximatelyEqual ((T) 1e6, (T) 1e6 + (T) 1, relativeTolerance ((T) 1e-7)));
+
+ expect ( approximatelyEqual ((T) -1e-6, (T) -1.0000009e-6, relativeTolerance ((T) 1e-6)));
+ expect (! approximatelyEqual ((T) -1e-6, (T) -1.0000009e-6, relativeTolerance ((T) 1e-7)));
+
+ const auto a = (T) 1.234567;
+ const auto b = (T) 1.234568;
+
+ for (auto exponent = 0; exponent < 39; ++exponent)
+ {
+ const auto m = std::pow ((T) 10, (T) exponent);
+ expect ( approximatelyEqual (a * m, b * m, relativeTolerance ((T) 1e-6)));
+ expect (! approximatelyEqual (a * m, b * m, relativeTolerance ((T) 1e-7)));
+ }
+ }
+
+ beginTest ("A relative tolerance is always scaled by the maximum value");
+ {
+ expect ( approximatelyEqual ((T) 9, (T) 10, absoluteTolerance ((T) 10.0 * (T) 0.1)));
+ expect (! approximatelyEqual ((T) 9, (T) 10, absoluteTolerance ((T) 9.0 * (T) 0.1)));
+
+ expect (approximatelyEqual ((T) 9, (T) 10, relativeTolerance ((T) 0.1)));
+ expect (approximatelyEqual ((T) 10, (T) 9, relativeTolerance ((T) 0.1)));
+ }
+
+ beginTest ("Documentation examples");
+ {
+ constexpr auto pi = MathConstants::pi;
+
+ expect (! approximatelyEqual (zero, std::sin (pi)));
+ expect ( approximatelyEqual (zero, std::sin (pi), absoluteTolerance (std::sin (pi))));
+
+ expect ( approximatelyEqual ((T) 100, (T) 95, relativeTolerance ((T) 0.05)));
+ expect (! approximatelyEqual ((T) 100, (T) 94, relativeTolerance ((T) 0.05)));
+ }
+ }
+};
+
+template
+class IsFiniteTests final : public UnitTest
+{
+public:
+ IsFiniteTests()
+ : UnitTest { getTemplatedMathsFunctionUnitTestName ("juce_isfinite"), UnitTestCategories::maths }
+ {}
+
+ void runTest() final
+ {
+ using limits = std::numeric_limits;
+
+ constexpr auto zero = T{};
+ constexpr auto one = (T) 1;
+ constexpr auto max = limits::max();
+ constexpr auto inf = limits::infinity();
+ constexpr auto nan = limits::quiet_NaN();
+
+ beginTest ("Zero is finite");
+ {
+ expect (juce_isfinite (zero));
+ expect (juce_isfinite (-zero));
+ }
+
+ beginTest ("Subnormals are finite");
+ {
+ expect (juce_isfinite (nextFloatUp (zero)));
+ expect (juce_isfinite (nextFloatDown (zero)));
+ }
+
+ beginTest ("One is finite");
+ {
+ expect (juce_isfinite (one));
+ expect (juce_isfinite (-one));
+ }
+
+ beginTest ("Max is finite");
+ {
+ expect (juce_isfinite (max));
+ expect (juce_isfinite (-max));
+ }
+
+ beginTest ("Infinity is not finite");
+ {
+ expect (! juce_isfinite (inf));
+ expect (! juce_isfinite (-inf));
+ }
+
+ beginTest ("NaN is not finite");
+ {
+ expect (! juce_isfinite (nan));
+ expect (! juce_isfinite (-nan));
+ expect (! juce_isfinite (std::sqrt ((T) -1)));
+ expect (! juce_isfinite (inf * zero));
+ }
+ }
+};
+
+template
+class NextFloatTests final : public UnitTest
+{
+public:
+ NextFloatTests()
+ : UnitTest { getTemplatedMathsFunctionUnitTestName ("nextFloat"), UnitTestCategories::maths }
+ {}
+
+ void runTest() final
+ {
+ using limits = std::numeric_limits;
+
+ constexpr auto zero = T{};
+ constexpr auto one = T (1);
+ constexpr auto min = limits::min();
+ constexpr auto epsilon = limits::epsilon();
+
+ beginTest ("nextFloat from zero is subnormal");
+ {
+ expect (juce_isfinite (nextFloatUp (zero)));
+ expect (! exactlyEqual (zero, nextFloatUp (zero)));
+ expect (! std::isnormal (nextFloatUp (zero)));
+
+ expect (juce_isfinite (nextFloatDown (zero)));
+ expect (! exactlyEqual (zero, nextFloatDown (zero)));
+ expect (! std::isnormal (nextFloatDown (zero)));
+ }
+
+ beginTest ("nextFloat from min, towards zero, is subnormal");
+ {
+ expect (std::isnormal (min));
+ expect (std::isnormal (-min));
+ expect (! std::isnormal (nextFloatDown (min)));
+ expect (! std::isnormal (nextFloatUp (-min)));
+ }
+
+ beginTest ("nextFloat from one matches epsilon");
+ {
+ expect (! exactlyEqual (one, nextFloatUp (one)));
+ expect ( exactlyEqual (one + epsilon, nextFloatUp (one)));
+
+ expect (! exactlyEqual (-one, nextFloatDown (-one)));
+ expect ( exactlyEqual (-one - epsilon, nextFloatDown (-one)));
+ }
+ }
+};
+
+template
+struct MathsFloatingPointFunctionsTests
+{
+ IsFiniteTests isFiniteTests;
+ NextFloatTests nextFloatTests;
+ ApproximatelyEqualTests approximatelyEqualTests;
+};
+
+struct MathsFunctionsTests
+{
+ MathsFloatingPointFunctionsTests floatFunctionTests;
+ MathsFloatingPointFunctionsTests doubleFunctionTests;
+ MathsFloatingPointFunctionsTests longDoubleFunctionTests;
+};
+
+static MathsFunctionsTests mathsFunctionsTests;
+
+} // namespace juce
diff --git a/modules/juce_graphics/colour/juce_Colour.cpp b/modules/juce_graphics/colour/juce_Colour.cpp
index 572084c3dc..47c343ec3c 100644
--- a/modules/juce_graphics/colour/juce_Colour.cpp
+++ b/modules/juce_graphics/colour/juce_Colour.cpp
@@ -44,7 +44,7 @@ namespace ColourHelpers
float hue = 0.0f;
- if (hi > 0 && ! approximatelyEqual (hi, lo))
+ if (hi > 0 && ! exactlyEqual (hi, lo))
{
auto invDiff = 1.0f / (float) (hi - lo);
diff --git a/modules/juce_graphics/geometry/juce_Point.h b/modules/juce_graphics/geometry/juce_Point.h
index 6fc88b4a9e..9e6117f066 100644
--- a/modules/juce_graphics/geometry/juce_Point.h
+++ b/modules/juce_graphics/geometry/juce_Point.h
@@ -66,7 +66,7 @@ public:
constexpr bool isOrigin() const noexcept { return operator== (Point()); }
/** Returns true if the coordinates are finite values. */
- constexpr inline bool isFinite() const noexcept { return juce_isfinite(x) && juce_isfinite(y); }
+ constexpr inline bool isFinite() const noexcept { return juce_isfinite (x) && juce_isfinite (y); }
/** Returns the point's x coordinate. */
constexpr inline ValueType getX() const noexcept { return x; }