From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on dcvr.yhbt.net X-Spam-Level: X-Spam-ASN: AS4713 221.184.0.0/13 X-Spam-Status: No, score=-4.0 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED, SPF_PASS shortcircuit=no autolearn=ham autolearn_force=no version=3.4.2 Received: from neon.ruby-lang.org (neon.ruby-lang.org [221.186.184.75]) by dcvr.yhbt.net (Postfix) with ESMTP id 9EE3C1F45F for ; Thu, 9 May 2019 10:22:13 +0000 (UTC) Received: from neon.ruby-lang.org (localhost [IPv6:::1]) by neon.ruby-lang.org (Postfix) with ESMTP id D6552120B2B; Thu, 9 May 2019 19:22:06 +0900 (JST) Received: from o1678916x28.outbound-mail.sendgrid.net (o1678916x28.outbound-mail.sendgrid.net [167.89.16.28]) by neon.ruby-lang.org (Postfix) with ESMTPS id 71400120B1B for ; Thu, 9 May 2019 19:22:03 +0900 (JST) Received: by filter0061p3iad2.sendgrid.net with SMTP id filter0061p3iad2-17334-5CD3FF4B-3F 2019-05-09 10:22:03.951364924 +0000 UTC m=+130285.519558276 Received: from herokuapp.com (unknown [54.227.1.136]) by ismtpd0035p1iad2.sendgrid.net (SG) with ESMTP id gLriZmdDSnmFxXPxWNw5TA for ; Thu, 09 May 2019 10:22:03.931 +0000 (UTC) Date: Thu, 09 May 2019 10:22:04 +0000 (UTC) From: hai-yen.nguyen@creditshelf.com Message-ID: References: Mime-Version: 1.0 X-Redmine-MailingListIntegration-Message-Ids: 68096 X-Redmine-Project: ruby-trunk X-Redmine-Issue-Id: 15811 X-Redmine-Issue-Author: yennguyenh X-Redmine-Sender: yennguyenh X-Mailer: Redmine X-Redmine-Host: bugs.ruby-lang.org X-Redmine-Site: Ruby Issue Tracking System X-Auto-Response-Suppress: All Auto-Submitted: auto-generated X-SG-EID: =?us-ascii?Q?FMHLl+3RYbLeK5ndbix9T4+jJkZ3h45d1qgFiUmNpsXFf+EBobgYVW+7knbCA4?= =?us-ascii?Q?2yd1uLSXiksvq23XgkN1GYrhNQRHA2lT7jFtaz4?= =?us-ascii?Q?wcAWF+VQkKgiXnWri3eppBdD6xNvHPKUIFF6yQP?= =?us-ascii?Q?ltkClvCqHbO+yfQvRxyte4CbU3bwyuzh+3bjmTk?= =?us-ascii?Q?XT21ovtv4Q+j4fZ7l0Jr9dntJIlrvAl2F6Q=3D=3D?= To: ruby-core@ruby-lang.org X-ML-Name: ruby-core X-Mail-Count: 92613 Subject: [ruby-core:92613] [Ruby trunk Feature#15811] Propsing new method for comparing equality of 2 (float) numbers relatively X-BeenThere: ruby-core@ruby-lang.org X-Mailman-Version: 2.1.15 Precedence: list Reply-To: Ruby developers List-Id: Ruby developers List-Unsubscribe: , List-Post: List-Help: List-Subscribe: , Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ruby-core-bounces@ruby-lang.org Sender: "ruby-core" Issue #15811 has been updated by yennguyenh (yen nguyen). wishdev (John Higgins) wrote: > The tests for this are incorrect and show why this does not work. > > From the test gist > > ```ruby > context 'Numbers between 1 and 0' do > let (:absolute_tolerance) { 1.0E-14 } > it 'returns true for same positive numbers' do > expect(Math.close?(1000001.0E-15, 1000002.0E-15, abs_tol: absolute_tolerance)).to be_truthy > expect(Math.close?(1000002.0E-15, 1000001.0E-15, abs_tol: absolute_tolerance)).to be_truthy > end > it 'returns true for same negative numbers' do > expect(Math.close?(-1000001.0E-15, -1000002.0E-15, abs_tol: absolute_tolerance)).to be_truthy > expect(Math.close?(-1000002.0E-15, -1000001.0E-15, abs_tol: absolute_tolerance)).to be_truthy > end > it 'returns false for different positive numbers' do > expect(Math.close?(1000010.0E-15, 1000020.0E-15, abs_tol: absolute_tolerance)).to be_falsey > expect(Math.close?(1000020.0E-15, 1000010.0E-15, abs_tol: absolute_tolerance)).to be_falsey > end > it 'returns false for different negative numbers' do > expect(Math.close?(-1000010.0E-15, -1000020.0E-15, abs_tol: absolute_tolerance)).to be_falsey > expect(Math.close?(-1000020.0E-15, -1000010.0E-15, abs_tol: absolute_tolerance)).to be_falsey > end > end > ``` > > 10E-15 == 1E-14 therefore since the absolute tolerance is equal to the difference of the bottom two "return false" specs - they must be true - they are not true because subtracting those floats ends up with garbage. > > For example > > 1000020.0E-15 - 1000010.0E-15 > > equals > > 1.0000000000085785e-14 > > Which places it outside of 1E14 but common sense (and looking at the numbers in front of us) obviously the correct answer is 1E14. > > Floating numbers cannot be acted upon and then the result used to prove something. > > This does not provide what it claims to provide - it is not possible to provide what you wish to provide here when dealing with floats. > > Sorry > > John wishdev (John Higgins) wrote: > The tests for this are incorrect and show why this does not work. > > From the test gist > > ```ruby > context 'Numbers between 1 and 0' do > let (:absolute_tolerance) { 1.0E-14 } > it 'returns true for same positive numbers' do > expect(Math.close?(1000001.0E-15, 1000002.0E-15, abs_tol: absolute_tolerance)).to be_truthy > expect(Math.close?(1000002.0E-15, 1000001.0E-15, abs_tol: absolute_tolerance)).to be_truthy > end > it 'returns true for same negative numbers' do > expect(Math.close?(-1000001.0E-15, -1000002.0E-15, abs_tol: absolute_tolerance)).to be_truthy > expect(Math.close?(-1000002.0E-15, -1000001.0E-15, abs_tol: absolute_tolerance)).to be_truthy > end > it 'returns false for different positive numbers' do > expect(Math.close?(1000010.0E-15, 1000020.0E-15, abs_tol: absolute_tolerance)).to be_falsey > expect(Math.close?(1000020.0E-15, 1000010.0E-15, abs_tol: absolute_tolerance)).to be_falsey > end > it 'returns false for different negative numbers' do > expect(Math.close?(-1000010.0E-15, -1000020.0E-15, abs_tol: absolute_tolerance)).to be_falsey > expect(Math.close?(-1000020.0E-15, -1000010.0E-15, abs_tol: absolute_tolerance)).to be_falsey > end > end > ``` > > 10E-15 == 1E-14 therefore since the absolute tolerance is equal to the difference of the bottom two "return false" specs - they must be true - they are not true because subtracting those floats ends up with garbage. > > For example > > 1000020.0E-15 - 1000010.0E-15 > > equals > > 1.0000000000085785e-14 > > Which places it outside of 1E14 but common sense (and looking at the numbers in front of us) obviously the correct answer is 1E14. > > Floating numbers cannot be acted upon and then the result used to prove something. > > This does not provide what it claims to provide - it is not possible to provide what you wish to provide here when dealing with floats. > > Sorry > > John Sorry but I do not really understand what you meant. What I get so far is that you mean the difference of that pair of number (1000020.0E-15 - 1000010.0E-15) results not as expected, 1.0000000000085785e-14 instead of 1.0e-14. I have taken a look on that and realize one mistake on the algorithm. The absolute tolerance is set to check the accuracy to a certain decimal place and so at that place the difference should be less than 1 which is 0. Therefore the equal case should not be considered as the case for equal numbers. It should be fixed like below ( I have also updated the code!) current method: ```ruby abs_diff = (a - b).abs ((abs_diff <= (rel_tol * b).abs) || (abs_diff <= (rel_tol * a).abs) || (abs_diff <= abs_tol)) ``` fixed method: ``` ruby abs_diff = (a - b).abs ((abs_diff <= (rel_tol * b).abs) || (abs_diff <= (rel_tol * a).abs) || (abs_diff < abs_tol)) ``` For ex: absolute tolerance: 1e-2 a: 0.01 b: 0.02 (a-b).abs: 0.001 == 1e-2 At the second decimal place, there is the difference of '1' which should return false for the equal comparison, so it return false in case absolute tolerance == (a-b).abs If it is not what you meant, please explain me more! Anyway thank you for the feedback, that I could find out that mistake! ---------------------------------------- Feature #15811: Propsing new method for comparing equality of 2 (float) numbers relatively https://bugs.ruby-lang.org/issues/15811#change-77973 * Author: yennguyenh (yen nguyen) * Status: Open * Priority: Normal * Assignee: * Target version: ---------------------------------------- # Background Equal comparison method between 2 float numbers returns unexpected results sometimes. Therefore, a relative comparison method is needed! # Proposal A relative equal comparison method has been written based on a Python project! This method gives the approximation for the equal comparison based on two values: realative tolerance and absolute tolerance. Near zero value will also be considered carefully! # Implementation The function for that would be called close? `close?(a, b, rel_tol, abs_tol)` `a` and `b`: are the two values to be tested to relative closeness `rel_tol`: is the relative tolerance -- it is the amount of error allowed, relative to the larger absolute value of a or b. For example, to set a tolerance of 5%, pass tol=0.05. The default tolerance is 1E-9, which assures that the two values are the same within about 9 decimal digits. rel_tol must be greater than 0.0 `abs_tol`: is a minimum absolute tolerance level -- useful for comparisons near zero. # Evaluation of your implementation By default, relative tolerance is 1E-9 which is relatively precise enough to compare two float numbers. However it can also be adjusted in case higher accuracy is requested. The absolute tolerance is by default 0.0 and need to be set in case of near-zero numbers. # Discussion There are some test cases available for the method which has approved the accuracy of the method. BigNumbers and integers are also tested. However, more test cases are still needed to assure even better the accuracy of the method. # Gist Relative equal comparison https://gist.github.com/yennguyenh/63d5e7a11f354f796b43ada037c4b2c5 Test cases https://gist.github.com/yennguyenh/2e81dc72b310cb9d886a82faf3d536ef -- https://bugs.ruby-lang.org/