Fix MatchNumberWithTolerance to better compare floating-point values - #1145

Merged
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta
Oct 5, 2018
Merged

Fix MatchNumberWithTolerance to better compare floating-point values#1145
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta

Conversation

@tannergooding

Copy link
Copy Markdown
Member

This updates MatchNumberWithTolerance to better compare floating-point values and enables it on Windows.

The previous algorithm was not properly accounting for the distribution of binary floating-point values and would not allow a match for numbers that could have been reasonably considered as equivalent.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

A good example of a place where the previous algorithm would have been less than ideal is:

staticvoidMain(string[]args){intxi=16777217;intyi=16777219;intzi=yi-xi;floatxf=xi;floatyf=yi;floatzf=yf-xf;// 16777219 - 16777217 = 2Console.WriteLine($"{yi} - {xi} = {zi}");// 16777220 - 16777216 = 4Console.WriteLine($"{yf:G9} - {xf:G9} = {zf:G9}");// 0x4B800002 - 0x4B800000 = 0x40800000Console.WriteLine($"0x{BitConverter.SingleToInt32Bits(yf):X8} - 0x{BitConverter.SingleToInt32Bits(xf):X8} = 0x{BitConverter.SingleToInt32Bits(zf):X8}");}

As you can see, the inputs are 16777217 and 16777219. However, the nearest representable floats are 16777216 and 16777220, respectively. And looking at the bit representation of the values, these numbers only differ by 2 bits (there is only one other representable value in between the two of them: 16777218). The previous algorithm would have only allowed a variance of 1.677722, which is not large enough to even make it to the next representable value.

The new algorithm rounds each input (both expected and actual) to a given number of significant digits (currently defaulting to 7), gets the delta of the rounded numbers, and then ensures that is within the tolerance (which is 10^-digits). This should properly account for the varying delta between representable values (for both large and small inputs).

public abstract partial class BaseTestBaseline : BaseTestClass
{
public const decimal Tolerance = 10_000_000;
public const int DigitsOfPrecision = 7;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public [](start = 8, length = 6)

nit: can it be internal?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but the existing code already had the previous constant as public.

@eerhardteerhardtOct 4, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it is used by inherited classes. And this is "just tests" so internal vs. public may not make that much of a difference.


In reply to: 222529405 [](ancestors = 222529405)

@sfilipi

Copy link
Copy Markdown
Member

Thanks for the change, Tanner. Did you want to try enabling any tests with the PR, to see if it helps?


In reply to: 426870598 [](ancestors = 426870598)

@danmoseley

Copy link
Copy Markdown

Will this allow you to remove the tolerance related disables on #1008 before you merge that?

@danmoseley
danmoseley requested a review from AnipikOctober 4, 2018 17:28
Comment threadtest/Microsoft.ML.TestFramework/BaseTestBaseline.cs

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@Anipik

Anipik commented Oct 4, 2018

Copy link
Copy Markdown
Contributor

Couple of cases where this is failing // digitsOfPrecision = 1 , variance = 0.1
f1 = 12, f2 = 13 (MulticlassLRNonNegativeTest)
f1 = -2.49565363 , f2 = -2.50574446 // f1 should have got rounded to 3 but it gets rounded to 2 , we need to correct this (DefaultCalibratorPerceptronTest)
f1 = 0.743881166, f2 = 0.7518015 // delta comes out to be -0.10000000000000009 where as it should be -0.1 (RandomCalibratorPerceptronTest)

@Anipik

Copy link
Copy Markdown
Contributor

similarly for f1 = 0.7099695, f2 = 0.6931915 delta should be one but its value 0.02 but its value is 0.020000000000000018

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@Anipik, you have to decide which rounding behavior is the most desirable as each has its pros/cons. I've defaulted to the IEEE default rounding mode as that tends to have the best overall behavior for the binary floating-point format.
The delta differences are due to the IEEE floating-point format: 0.1 + 0.2 != 0.3; it equals 0.30000000000000004.

The algorithm should be generally sufficient for ML.NET, where I would expect that, when dealing with System.Single inputs. we will be getting results to within at least 4 significant digits of accuracy (but should ideally aim for 6-9). The exact error for any given scalar algorithm depends on the number of inputs and how they are ordered. Vectorized algorithms can have additional error based on the alignment of the inputs and how many elements are processed at a time.

  • I still need to do some checking on some disabled tests, on the x86 tests, and when the CPU only supports 128-bit vectors to see if there needs to be any small tweaks to the default

@sfilipisfilipi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@sfilipi
sfilipi merged commit 02e85cc into dotnet:masterOct 5, 2018
@ghostghost locked as resolved and limited conversation to collaborators Mar 28, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@sfilipi@danmoseley@Anipik@eerhardt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix MatchNumberWithTolerance to better compare floating-point values - #1145

Merged
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta
Oct 5, 2018
Merged

Fix MatchNumberWithTolerance to better compare floating-point values#1145
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta

Conversation

@tannergooding

Copy link
Copy Markdown
Member

This updates MatchNumberWithTolerance to better compare floating-point values and enables it on Windows.

The previous algorithm was not properly accounting for the distribution of binary floating-point values and would not allow a match for numbers that could have been reasonably considered as equivalent.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

A good example of a place where the previous algorithm would have been less than ideal is:

staticvoidMain(string[]args){intxi=16777217;intyi=16777219;intzi=yi-xi;floatxf=xi;floatyf=yi;floatzf=yf-xf;// 16777219 - 16777217 = 2Console.WriteLine($"{yi} - {xi} = {zi}");// 16777220 - 16777216 = 4Console.WriteLine($"{yf:G9} - {xf:G9} = {zf:G9}");// 0x4B800002 - 0x4B800000 = 0x40800000Console.WriteLine($"0x{BitConverter.SingleToInt32Bits(yf):X8} - 0x{BitConverter.SingleToInt32Bits(xf):X8} = 0x{BitConverter.SingleToInt32Bits(zf):X8}");}

As you can see, the inputs are 16777217 and 16777219. However, the nearest representable floats are 16777216 and 16777220, respectively. And looking at the bit representation of the values, these numbers only differ by 2 bits (there is only one other representable value in between the two of them: 16777218). The previous algorithm would have only allowed a variance of 1.677722, which is not large enough to even make it to the next representable value.

The new algorithm rounds each input (both expected and actual) to a given number of significant digits (currently defaulting to 7), gets the delta of the rounded numbers, and then ensures that is within the tolerance (which is 10^-digits). This should properly account for the varying delta between representable values (for both large and small inputs).

public abstract partial class BaseTestBaseline : BaseTestClass
{
public const decimal Tolerance = 10_000_000;
public const int DigitsOfPrecision = 7;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public [](start = 8, length = 6)

nit: can it be internal?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but the existing code already had the previous constant as public.

@eerhardteerhardtOct 4, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it is used by inherited classes. And this is "just tests" so internal vs. public may not make that much of a difference.


In reply to: 222529405 [](ancestors = 222529405)

@sfilipi

Copy link
Copy Markdown
Member

Thanks for the change, Tanner. Did you want to try enabling any tests with the PR, to see if it helps?


In reply to: 426870598 [](ancestors = 426870598)

@danmoseley

Copy link
Copy Markdown

Will this allow you to remove the tolerance related disables on #1008 before you merge that?

@danmoseley
danmoseley requested a review from AnipikOctober 4, 2018 17:28
Comment threadtest/Microsoft.ML.TestFramework/BaseTestBaseline.cs

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@Anipik

Anipik commented Oct 4, 2018

Copy link
Copy Markdown
Contributor

Couple of cases where this is failing // digitsOfPrecision = 1 , variance = 0.1
f1 = 12, f2 = 13 (MulticlassLRNonNegativeTest)
f1 = -2.49565363 , f2 = -2.50574446 // f1 should have got rounded to 3 but it gets rounded to 2 , we need to correct this (DefaultCalibratorPerceptronTest)
f1 = 0.743881166, f2 = 0.7518015 // delta comes out to be -0.10000000000000009 where as it should be -0.1 (RandomCalibratorPerceptronTest)

@Anipik

Copy link
Copy Markdown
Contributor

similarly for f1 = 0.7099695, f2 = 0.6931915 delta should be one but its value 0.02 but its value is 0.020000000000000018

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@Anipik, you have to decide which rounding behavior is the most desirable as each has its pros/cons. I've defaulted to the IEEE default rounding mode as that tends to have the best overall behavior for the binary floating-point format.
The delta differences are due to the IEEE floating-point format: 0.1 + 0.2 != 0.3; it equals 0.30000000000000004.

The algorithm should be generally sufficient for ML.NET, where I would expect that, when dealing with System.Single inputs. we will be getting results to within at least 4 significant digits of accuracy (but should ideally aim for 6-9). The exact error for any given scalar algorithm depends on the number of inputs and how they are ordered. Vectorized algorithms can have additional error based on the alignment of the inputs and how many elements are processed at a time.

  • I still need to do some checking on some disabled tests, on the x86 tests, and when the CPU only supports 128-bit vectors to see if there needs to be any small tweaks to the default

@sfilipisfilipi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@sfilipi
sfilipi merged commit 02e85cc into dotnet:masterOct 5, 2018
@ghostghost locked as resolved and limited conversation to collaborators Mar 28, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@sfilipi@danmoseley@Anipik@eerhardt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix MatchNumberWithTolerance to better compare floating-point values - #1145

Merged
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta
Oct 5, 2018
Merged

Fix MatchNumberWithTolerance to better compare floating-point values#1145
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta

Conversation

@tannergooding

Copy link
Copy Markdown
Member

This updates MatchNumberWithTolerance to better compare floating-point values and enables it on Windows.

The previous algorithm was not properly accounting for the distribution of binary floating-point values and would not allow a match for numbers that could have been reasonably considered as equivalent.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

A good example of a place where the previous algorithm would have been less than ideal is:

staticvoidMain(string[]args){intxi=16777217;intyi=16777219;intzi=yi-xi;floatxf=xi;floatyf=yi;floatzf=yf-xf;// 16777219 - 16777217 = 2Console.WriteLine($"{yi} - {xi} = {zi}");// 16777220 - 16777216 = 4Console.WriteLine($"{yf:G9} - {xf:G9} = {zf:G9}");// 0x4B800002 - 0x4B800000 = 0x40800000Console.WriteLine($"0x{BitConverter.SingleToInt32Bits(yf):X8} - 0x{BitConverter.SingleToInt32Bits(xf):X8} = 0x{BitConverter.SingleToInt32Bits(zf):X8}");}

As you can see, the inputs are 16777217 and 16777219. However, the nearest representable floats are 16777216 and 16777220, respectively. And looking at the bit representation of the values, these numbers only differ by 2 bits (there is only one other representable value in between the two of them: 16777218). The previous algorithm would have only allowed a variance of 1.677722, which is not large enough to even make it to the next representable value.

The new algorithm rounds each input (both expected and actual) to a given number of significant digits (currently defaulting to 7), gets the delta of the rounded numbers, and then ensures that is within the tolerance (which is 10^-digits). This should properly account for the varying delta between representable values (for both large and small inputs).

public abstract partial class BaseTestBaseline : BaseTestClass
{
public const decimal Tolerance = 10_000_000;
public const int DigitsOfPrecision = 7;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public [](start = 8, length = 6)

nit: can it be internal?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but the existing code already had the previous constant as public.

@eerhardteerhardtOct 4, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it is used by inherited classes. And this is "just tests" so internal vs. public may not make that much of a difference.


In reply to: 222529405 [](ancestors = 222529405)

@sfilipi

Copy link
Copy Markdown
Member

Thanks for the change, Tanner. Did you want to try enabling any tests with the PR, to see if it helps?


In reply to: 426870598 [](ancestors = 426870598)

@danmoseley

Copy link
Copy Markdown

Will this allow you to remove the tolerance related disables on #1008 before you merge that?

@danmoseley
danmoseley requested a review from AnipikOctober 4, 2018 17:28
Comment threadtest/Microsoft.ML.TestFramework/BaseTestBaseline.cs

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@Anipik

Anipik commented Oct 4, 2018

Copy link
Copy Markdown
Contributor

Couple of cases where this is failing // digitsOfPrecision = 1 , variance = 0.1
f1 = 12, f2 = 13 (MulticlassLRNonNegativeTest)
f1 = -2.49565363 , f2 = -2.50574446 // f1 should have got rounded to 3 but it gets rounded to 2 , we need to correct this (DefaultCalibratorPerceptronTest)
f1 = 0.743881166, f2 = 0.7518015 // delta comes out to be -0.10000000000000009 where as it should be -0.1 (RandomCalibratorPerceptronTest)

@Anipik

Copy link
Copy Markdown
Contributor

similarly for f1 = 0.7099695, f2 = 0.6931915 delta should be one but its value 0.02 but its value is 0.020000000000000018

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@Anipik, you have to decide which rounding behavior is the most desirable as each has its pros/cons. I've defaulted to the IEEE default rounding mode as that tends to have the best overall behavior for the binary floating-point format.
The delta differences are due to the IEEE floating-point format: 0.1 + 0.2 != 0.3; it equals 0.30000000000000004.

The algorithm should be generally sufficient for ML.NET, where I would expect that, when dealing with System.Single inputs. we will be getting results to within at least 4 significant digits of accuracy (but should ideally aim for 6-9). The exact error for any given scalar algorithm depends on the number of inputs and how they are ordered. Vectorized algorithms can have additional error based on the alignment of the inputs and how many elements are processed at a time.

  • I still need to do some checking on some disabled tests, on the x86 tests, and when the CPU only supports 128-bit vectors to see if there needs to be any small tweaks to the default

@sfilipisfilipi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@sfilipi
sfilipi merged commit 02e85cc into dotnet:masterOct 5, 2018
@ghostghost locked as resolved and limited conversation to collaborators Mar 28, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@sfilipi@danmoseley@Anipik@eerhardt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix MatchNumberWithTolerance to better compare floating-point values - #1145

Merged
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta
Oct 5, 2018
Merged

Fix MatchNumberWithTolerance to better compare floating-point values#1145
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta

Conversation

@tannergooding

Copy link
Copy Markdown
Member

This updates MatchNumberWithTolerance to better compare floating-point values and enables it on Windows.

The previous algorithm was not properly accounting for the distribution of binary floating-point values and would not allow a match for numbers that could have been reasonably considered as equivalent.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

A good example of a place where the previous algorithm would have been less than ideal is:

staticvoidMain(string[]args){intxi=16777217;intyi=16777219;intzi=yi-xi;floatxf=xi;floatyf=yi;floatzf=yf-xf;// 16777219 - 16777217 = 2Console.WriteLine($"{yi} - {xi} = {zi}");// 16777220 - 16777216 = 4Console.WriteLine($"{yf:G9} - {xf:G9} = {zf:G9}");// 0x4B800002 - 0x4B800000 = 0x40800000Console.WriteLine($"0x{BitConverter.SingleToInt32Bits(yf):X8} - 0x{BitConverter.SingleToInt32Bits(xf):X8} = 0x{BitConverter.SingleToInt32Bits(zf):X8}");}

As you can see, the inputs are 16777217 and 16777219. However, the nearest representable floats are 16777216 and 16777220, respectively. And looking at the bit representation of the values, these numbers only differ by 2 bits (there is only one other representable value in between the two of them: 16777218). The previous algorithm would have only allowed a variance of 1.677722, which is not large enough to even make it to the next representable value.

The new algorithm rounds each input (both expected and actual) to a given number of significant digits (currently defaulting to 7), gets the delta of the rounded numbers, and then ensures that is within the tolerance (which is 10^-digits). This should properly account for the varying delta between representable values (for both large and small inputs).

public abstract partial class BaseTestBaseline : BaseTestClass
{
public const decimal Tolerance = 10_000_000;
public const int DigitsOfPrecision = 7;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public [](start = 8, length = 6)

nit: can it be internal?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but the existing code already had the previous constant as public.

@eerhardteerhardtOct 4, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it is used by inherited classes. And this is "just tests" so internal vs. public may not make that much of a difference.


In reply to: 222529405 [](ancestors = 222529405)

@sfilipi

Copy link
Copy Markdown
Member

Thanks for the change, Tanner. Did you want to try enabling any tests with the PR, to see if it helps?


In reply to: 426870598 [](ancestors = 426870598)

@danmoseley

Copy link
Copy Markdown

Will this allow you to remove the tolerance related disables on #1008 before you merge that?

@danmoseley
danmoseley requested a review from AnipikOctober 4, 2018 17:28
Comment threadtest/Microsoft.ML.TestFramework/BaseTestBaseline.cs

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@Anipik

Anipik commented Oct 4, 2018

Copy link
Copy Markdown
Contributor

Couple of cases where this is failing // digitsOfPrecision = 1 , variance = 0.1
f1 = 12, f2 = 13 (MulticlassLRNonNegativeTest)
f1 = -2.49565363 , f2 = -2.50574446 // f1 should have got rounded to 3 but it gets rounded to 2 , we need to correct this (DefaultCalibratorPerceptronTest)
f1 = 0.743881166, f2 = 0.7518015 // delta comes out to be -0.10000000000000009 where as it should be -0.1 (RandomCalibratorPerceptronTest)

@Anipik

Copy link
Copy Markdown
Contributor

similarly for f1 = 0.7099695, f2 = 0.6931915 delta should be one but its value 0.02 but its value is 0.020000000000000018

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@Anipik, you have to decide which rounding behavior is the most desirable as each has its pros/cons. I've defaulted to the IEEE default rounding mode as that tends to have the best overall behavior for the binary floating-point format.
The delta differences are due to the IEEE floating-point format: 0.1 + 0.2 != 0.3; it equals 0.30000000000000004.

The algorithm should be generally sufficient for ML.NET, where I would expect that, when dealing with System.Single inputs. we will be getting results to within at least 4 significant digits of accuracy (but should ideally aim for 6-9). The exact error for any given scalar algorithm depends on the number of inputs and how they are ordered. Vectorized algorithms can have additional error based on the alignment of the inputs and how many elements are processed at a time.

  • I still need to do some checking on some disabled tests, on the x86 tests, and when the CPU only supports 128-bit vectors to see if there needs to be any small tweaks to the default

@sfilipisfilipi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@sfilipi
sfilipi merged commit 02e85cc into dotnet:masterOct 5, 2018
@ghostghost locked as resolved and limited conversation to collaborators Mar 28, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@sfilipi@danmoseley@Anipik@eerhardt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix MatchNumberWithTolerance to better compare floating-point values - #1145

Merged
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta
Oct 5, 2018
Merged

Fix MatchNumberWithTolerance to better compare floating-point values#1145
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta

Conversation

@tannergooding

Copy link
Copy Markdown
Member

This updates MatchNumberWithTolerance to better compare floating-point values and enables it on Windows.

The previous algorithm was not properly accounting for the distribution of binary floating-point values and would not allow a match for numbers that could have been reasonably considered as equivalent.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

A good example of a place where the previous algorithm would have been less than ideal is:

staticvoidMain(string[]args){intxi=16777217;intyi=16777219;intzi=yi-xi;floatxf=xi;floatyf=yi;floatzf=yf-xf;// 16777219 - 16777217 = 2Console.WriteLine($"{yi} - {xi} = {zi}");// 16777220 - 16777216 = 4Console.WriteLine($"{yf:G9} - {xf:G9} = {zf:G9}");// 0x4B800002 - 0x4B800000 = 0x40800000Console.WriteLine($"0x{BitConverter.SingleToInt32Bits(yf):X8} - 0x{BitConverter.SingleToInt32Bits(xf):X8} = 0x{BitConverter.SingleToInt32Bits(zf):X8}");}

As you can see, the inputs are 16777217 and 16777219. However, the nearest representable floats are 16777216 and 16777220, respectively. And looking at the bit representation of the values, these numbers only differ by 2 bits (there is only one other representable value in between the two of them: 16777218). The previous algorithm would have only allowed a variance of 1.677722, which is not large enough to even make it to the next representable value.

The new algorithm rounds each input (both expected and actual) to a given number of significant digits (currently defaulting to 7), gets the delta of the rounded numbers, and then ensures that is within the tolerance (which is 10^-digits). This should properly account for the varying delta between representable values (for both large and small inputs).

public abstract partial class BaseTestBaseline : BaseTestClass
{
public const decimal Tolerance = 10_000_000;
public const int DigitsOfPrecision = 7;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public [](start = 8, length = 6)

nit: can it be internal?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but the existing code already had the previous constant as public.

@eerhardteerhardtOct 4, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it is used by inherited classes. And this is "just tests" so internal vs. public may not make that much of a difference.


In reply to: 222529405 [](ancestors = 222529405)

@sfilipi

Copy link
Copy Markdown
Member

Thanks for the change, Tanner. Did you want to try enabling any tests with the PR, to see if it helps?


In reply to: 426870598 [](ancestors = 426870598)

@danmoseley

Copy link
Copy Markdown

Will this allow you to remove the tolerance related disables on #1008 before you merge that?

@danmoseley
danmoseley requested a review from AnipikOctober 4, 2018 17:28
Comment threadtest/Microsoft.ML.TestFramework/BaseTestBaseline.cs

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@Anipik

Anipik commented Oct 4, 2018

Copy link
Copy Markdown
Contributor

Couple of cases where this is failing // digitsOfPrecision = 1 , variance = 0.1
f1 = 12, f2 = 13 (MulticlassLRNonNegativeTest)
f1 = -2.49565363 , f2 = -2.50574446 // f1 should have got rounded to 3 but it gets rounded to 2 , we need to correct this (DefaultCalibratorPerceptronTest)
f1 = 0.743881166, f2 = 0.7518015 // delta comes out to be -0.10000000000000009 where as it should be -0.1 (RandomCalibratorPerceptronTest)

@Anipik

Copy link
Copy Markdown
Contributor

similarly for f1 = 0.7099695, f2 = 0.6931915 delta should be one but its value 0.02 but its value is 0.020000000000000018

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@Anipik, you have to decide which rounding behavior is the most desirable as each has its pros/cons. I've defaulted to the IEEE default rounding mode as that tends to have the best overall behavior for the binary floating-point format.
The delta differences are due to the IEEE floating-point format: 0.1 + 0.2 != 0.3; it equals 0.30000000000000004.

The algorithm should be generally sufficient for ML.NET, where I would expect that, when dealing with System.Single inputs. we will be getting results to within at least 4 significant digits of accuracy (but should ideally aim for 6-9). The exact error for any given scalar algorithm depends on the number of inputs and how they are ordered. Vectorized algorithms can have additional error based on the alignment of the inputs and how many elements are processed at a time.

  • I still need to do some checking on some disabled tests, on the x86 tests, and when the CPU only supports 128-bit vectors to see if there needs to be any small tweaks to the default

@sfilipisfilipi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@sfilipi
sfilipi merged commit 02e85cc into dotnet:masterOct 5, 2018
@ghostghost locked as resolved and limited conversation to collaborators Mar 28, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@sfilipi@danmoseley@Anipik@eerhardt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix MatchNumberWithTolerance to better compare floating-point values - #1145

Merged
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta
Oct 5, 2018
Merged

Fix MatchNumberWithTolerance to better compare floating-point values#1145
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta

Conversation

@tannergooding

Copy link
Copy Markdown
Member

This updates MatchNumberWithTolerance to better compare floating-point values and enables it on Windows.

The previous algorithm was not properly accounting for the distribution of binary floating-point values and would not allow a match for numbers that could have been reasonably considered as equivalent.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

A good example of a place where the previous algorithm would have been less than ideal is:

staticvoidMain(string[]args){intxi=16777217;intyi=16777219;intzi=yi-xi;floatxf=xi;floatyf=yi;floatzf=yf-xf;// 16777219 - 16777217 = 2Console.WriteLine($"{yi} - {xi} = {zi}");// 16777220 - 16777216 = 4Console.WriteLine($"{yf:G9} - {xf:G9} = {zf:G9}");// 0x4B800002 - 0x4B800000 = 0x40800000Console.WriteLine($"0x{BitConverter.SingleToInt32Bits(yf):X8} - 0x{BitConverter.SingleToInt32Bits(xf):X8} = 0x{BitConverter.SingleToInt32Bits(zf):X8}");}

As you can see, the inputs are 16777217 and 16777219. However, the nearest representable floats are 16777216 and 16777220, respectively. And looking at the bit representation of the values, these numbers only differ by 2 bits (there is only one other representable value in between the two of them: 16777218). The previous algorithm would have only allowed a variance of 1.677722, which is not large enough to even make it to the next representable value.

The new algorithm rounds each input (both expected and actual) to a given number of significant digits (currently defaulting to 7), gets the delta of the rounded numbers, and then ensures that is within the tolerance (which is 10^-digits). This should properly account for the varying delta between representable values (for both large and small inputs).

public abstract partial class BaseTestBaseline : BaseTestClass
{
public const decimal Tolerance = 10_000_000;
public const int DigitsOfPrecision = 7;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public [](start = 8, length = 6)

nit: can it be internal?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but the existing code already had the previous constant as public.

@eerhardteerhardtOct 4, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it is used by inherited classes. And this is "just tests" so internal vs. public may not make that much of a difference.


In reply to: 222529405 [](ancestors = 222529405)

@sfilipi

Copy link
Copy Markdown
Member

Thanks for the change, Tanner. Did you want to try enabling any tests with the PR, to see if it helps?


In reply to: 426870598 [](ancestors = 426870598)

@danmoseley

Copy link
Copy Markdown

Will this allow you to remove the tolerance related disables on #1008 before you merge that?

@danmoseley
danmoseley requested a review from AnipikOctober 4, 2018 17:28
Comment threadtest/Microsoft.ML.TestFramework/BaseTestBaseline.cs

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@Anipik

Anipik commented Oct 4, 2018

Copy link
Copy Markdown
Contributor

Couple of cases where this is failing // digitsOfPrecision = 1 , variance = 0.1
f1 = 12, f2 = 13 (MulticlassLRNonNegativeTest)
f1 = -2.49565363 , f2 = -2.50574446 // f1 should have got rounded to 3 but it gets rounded to 2 , we need to correct this (DefaultCalibratorPerceptronTest)
f1 = 0.743881166, f2 = 0.7518015 // delta comes out to be -0.10000000000000009 where as it should be -0.1 (RandomCalibratorPerceptronTest)

@Anipik

Copy link
Copy Markdown
Contributor

similarly for f1 = 0.7099695, f2 = 0.6931915 delta should be one but its value 0.02 but its value is 0.020000000000000018

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@Anipik, you have to decide which rounding behavior is the most desirable as each has its pros/cons. I've defaulted to the IEEE default rounding mode as that tends to have the best overall behavior for the binary floating-point format.
The delta differences are due to the IEEE floating-point format: 0.1 + 0.2 != 0.3; it equals 0.30000000000000004.

The algorithm should be generally sufficient for ML.NET, where I would expect that, when dealing with System.Single inputs. we will be getting results to within at least 4 significant digits of accuracy (but should ideally aim for 6-9). The exact error for any given scalar algorithm depends on the number of inputs and how they are ordered. Vectorized algorithms can have additional error based on the alignment of the inputs and how many elements are processed at a time.

  • I still need to do some checking on some disabled tests, on the x86 tests, and when the CPU only supports 128-bit vectors to see if there needs to be any small tweaks to the default

@sfilipisfilipi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@sfilipi
sfilipi merged commit 02e85cc into dotnet:masterOct 5, 2018
@ghostghost locked as resolved and limited conversation to collaborators Mar 28, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@sfilipi@danmoseley@Anipik@eerhardt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix MatchNumberWithTolerance to better compare floating-point values - #1145

Merged
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta
Oct 5, 2018
Merged

Fix MatchNumberWithTolerance to better compare floating-point values#1145
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta

Conversation

@tannergooding

Copy link
Copy Markdown
Member

This updates MatchNumberWithTolerance to better compare floating-point values and enables it on Windows.

The previous algorithm was not properly accounting for the distribution of binary floating-point values and would not allow a match for numbers that could have been reasonably considered as equivalent.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

A good example of a place where the previous algorithm would have been less than ideal is:

staticvoidMain(string[]args){intxi=16777217;intyi=16777219;intzi=yi-xi;floatxf=xi;floatyf=yi;floatzf=yf-xf;// 16777219 - 16777217 = 2Console.WriteLine($"{yi} - {xi} = {zi}");// 16777220 - 16777216 = 4Console.WriteLine($"{yf:G9} - {xf:G9} = {zf:G9}");// 0x4B800002 - 0x4B800000 = 0x40800000Console.WriteLine($"0x{BitConverter.SingleToInt32Bits(yf):X8} - 0x{BitConverter.SingleToInt32Bits(xf):X8} = 0x{BitConverter.SingleToInt32Bits(zf):X8}");}

As you can see, the inputs are 16777217 and 16777219. However, the nearest representable floats are 16777216 and 16777220, respectively. And looking at the bit representation of the values, these numbers only differ by 2 bits (there is only one other representable value in between the two of them: 16777218). The previous algorithm would have only allowed a variance of 1.677722, which is not large enough to even make it to the next representable value.

The new algorithm rounds each input (both expected and actual) to a given number of significant digits (currently defaulting to 7), gets the delta of the rounded numbers, and then ensures that is within the tolerance (which is 10^-digits). This should properly account for the varying delta between representable values (for both large and small inputs).

public abstract partial class BaseTestBaseline : BaseTestClass
{
public const decimal Tolerance = 10_000_000;
public const int DigitsOfPrecision = 7;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public [](start = 8, length = 6)

nit: can it be internal?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but the existing code already had the previous constant as public.

@eerhardteerhardtOct 4, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it is used by inherited classes. And this is "just tests" so internal vs. public may not make that much of a difference.


In reply to: 222529405 [](ancestors = 222529405)

@sfilipi

Copy link
Copy Markdown
Member

Thanks for the change, Tanner. Did you want to try enabling any tests with the PR, to see if it helps?


In reply to: 426870598 [](ancestors = 426870598)

@danmoseley

Copy link
Copy Markdown

Will this allow you to remove the tolerance related disables on #1008 before you merge that?

@danmoseley
danmoseley requested a review from AnipikOctober 4, 2018 17:28
Comment threadtest/Microsoft.ML.TestFramework/BaseTestBaseline.cs

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@Anipik

Anipik commented Oct 4, 2018

Copy link
Copy Markdown
Contributor

Couple of cases where this is failing // digitsOfPrecision = 1 , variance = 0.1
f1 = 12, f2 = 13 (MulticlassLRNonNegativeTest)
f1 = -2.49565363 , f2 = -2.50574446 // f1 should have got rounded to 3 but it gets rounded to 2 , we need to correct this (DefaultCalibratorPerceptronTest)
f1 = 0.743881166, f2 = 0.7518015 // delta comes out to be -0.10000000000000009 where as it should be -0.1 (RandomCalibratorPerceptronTest)

@Anipik

Copy link
Copy Markdown
Contributor

similarly for f1 = 0.7099695, f2 = 0.6931915 delta should be one but its value 0.02 but its value is 0.020000000000000018

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@Anipik, you have to decide which rounding behavior is the most desirable as each has its pros/cons. I've defaulted to the IEEE default rounding mode as that tends to have the best overall behavior for the binary floating-point format.
The delta differences are due to the IEEE floating-point format: 0.1 + 0.2 != 0.3; it equals 0.30000000000000004.

The algorithm should be generally sufficient for ML.NET, where I would expect that, when dealing with System.Single inputs. we will be getting results to within at least 4 significant digits of accuracy (but should ideally aim for 6-9). The exact error for any given scalar algorithm depends on the number of inputs and how they are ordered. Vectorized algorithms can have additional error based on the alignment of the inputs and how many elements are processed at a time.

  • I still need to do some checking on some disabled tests, on the x86 tests, and when the CPU only supports 128-bit vectors to see if there needs to be any small tweaks to the default

@sfilipisfilipi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@sfilipi
sfilipi merged commit 02e85cc into dotnet:masterOct 5, 2018
@ghostghost locked as resolved and limited conversation to collaborators Mar 28, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@sfilipi@danmoseley@Anipik@eerhardt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix MatchNumberWithTolerance to better compare floating-point values - #1145

Merged
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta
Oct 5, 2018
Merged

Fix MatchNumberWithTolerance to better compare floating-point values#1145
sfilipi merged 2 commits into
dotnet:masterfrom
tannergooding:float-delta

Conversation

@tannergooding

Copy link
Copy Markdown
Member

This updates MatchNumberWithTolerance to better compare floating-point values and enables it on Windows.

The previous algorithm was not properly accounting for the distribution of binary floating-point values and would not allow a match for numbers that could have been reasonably considered as equivalent.

@tannergooding

Copy link
Copy Markdown
MemberAuthor

A good example of a place where the previous algorithm would have been less than ideal is:

staticvoidMain(string[]args){intxi=16777217;intyi=16777219;intzi=yi-xi;floatxf=xi;floatyf=yi;floatzf=yf-xf;// 16777219 - 16777217 = 2Console.WriteLine($"{yi} - {xi} = {zi}");// 16777220 - 16777216 = 4Console.WriteLine($"{yf:G9} - {xf:G9} = {zf:G9}");// 0x4B800002 - 0x4B800000 = 0x40800000Console.WriteLine($"0x{BitConverter.SingleToInt32Bits(yf):X8} - 0x{BitConverter.SingleToInt32Bits(xf):X8} = 0x{BitConverter.SingleToInt32Bits(zf):X8}");}

As you can see, the inputs are 16777217 and 16777219. However, the nearest representable floats are 16777216 and 16777220, respectively. And looking at the bit representation of the values, these numbers only differ by 2 bits (there is only one other representable value in between the two of them: 16777218). The previous algorithm would have only allowed a variance of 1.677722, which is not large enough to even make it to the next representable value.

The new algorithm rounds each input (both expected and actual) to a given number of significant digits (currently defaulting to 7), gets the delta of the rounded numbers, and then ensures that is within the tolerance (which is 10^-digits). This should properly account for the varying delta between representable values (for both large and small inputs).

public abstract partial class BaseTestBaseline : BaseTestClass
{
public const decimal Tolerance = 10_000_000;
public const int DigitsOfPrecision = 7;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public [](start = 8, length = 6)

nit: can it be internal?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe, but the existing code already had the previous constant as public.

@eerhardteerhardtOct 4, 2018

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it is used by inherited classes. And this is "just tests" so internal vs. public may not make that much of a difference.


In reply to: 222529405 [](ancestors = 222529405)

@sfilipi

Copy link
Copy Markdown
Member

Thanks for the change, Tanner. Did you want to try enabling any tests with the PR, to see if it helps?


In reply to: 426870598 [](ancestors = 426870598)

@danmoseley

Copy link
Copy Markdown

Will this allow you to remove the tolerance related disables on #1008 before you merge that?

@danmoseley
danmoseley requested a review from AnipikOctober 4, 2018 17:28
Comment threadtest/Microsoft.ML.TestFramework/BaseTestBaseline.cs

@eerhardteerhardt left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@Anipik

Anipik commented Oct 4, 2018

Copy link
Copy Markdown
Contributor

Couple of cases where this is failing // digitsOfPrecision = 1 , variance = 0.1
f1 = 12, f2 = 13 (MulticlassLRNonNegativeTest)
f1 = -2.49565363 , f2 = -2.50574446 // f1 should have got rounded to 3 but it gets rounded to 2 , we need to correct this (DefaultCalibratorPerceptronTest)
f1 = 0.743881166, f2 = 0.7518015 // delta comes out to be -0.10000000000000009 where as it should be -0.1 (RandomCalibratorPerceptronTest)

@Anipik

Copy link
Copy Markdown
Contributor

similarly for f1 = 0.7099695, f2 = 0.6931915 delta should be one but its value 0.02 but its value is 0.020000000000000018

@tannergooding

Copy link
Copy Markdown
MemberAuthor

@Anipik, you have to decide which rounding behavior is the most desirable as each has its pros/cons. I've defaulted to the IEEE default rounding mode as that tends to have the best overall behavior for the binary floating-point format.
The delta differences are due to the IEEE floating-point format: 0.1 + 0.2 != 0.3; it equals 0.30000000000000004.

The algorithm should be generally sufficient for ML.NET, where I would expect that, when dealing with System.Single inputs. we will be getting results to within at least 4 significant digits of accuracy (but should ideally aim for 6-9). The exact error for any given scalar algorithm depends on the number of inputs and how they are ordered. Vectorized algorithms can have additional error based on the alignment of the inputs and how many elements are processed at a time.

  • I still need to do some checking on some disabled tests, on the x86 tests, and when the CPU only supports 128-bit vectors to see if there needs to be any small tweaks to the default

@sfilipisfilipi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@sfilipi
sfilipi merged commit 02e85cc into dotnet:masterOct 5, 2018
@ghostghost locked as resolved and limited conversation to collaborators Mar 28, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@tannergooding@sfilipi@danmoseley@Anipik@eerhardt