001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014package org.gbif.utils.number; 015 016import java.math.BigDecimal; 017import java.text.DecimalFormat; 018import java.text.NumberFormat; 019 020import org.junit.jupiter.api.Test; 021 022import static org.junit.jupiter.api.Assertions.assertEquals; 023import static org.junit.jupiter.api.Assertions.assertFalse; 024import static org.junit.jupiter.api.Assertions.assertNotEquals; 025import static org.junit.jupiter.api.Assertions.assertTrue; 026 027/** 028 * Set of tests to illustrate the issue with double and how some problems can be solved with {@link BigDecimal}. 029 * {@link BigDecimal} is specially useful to work with base 10 type of data (money, metric measures). 030 * 031 * Be aware that {@link BigDecimal} requires more memory to work with (it's an Object) and it's also slower on 032 * computations (it's an immutable Object). 033 */ 034// @Disabled("Demonstration of Java API behavior") 035public class DoubleVsBigDecimal { 036 037 /** 038 * Demonstrates that the sum of 1/10 (10 times) is not exactly equals to 1d using double. 039 */ 040 @Test 041 public void testOneTenthAdditions() { 042 043 double oneInDouble = 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1; 044 assertFalse(oneInDouble == 1d); // oneInDouble value: 0.9999999999999999 045 046 // Try the same with BigDecimal 047 BigDecimal zeroPointOne = new BigDecimal("0.1"); 048 BigDecimal currentValue = zeroPointOne; 049 for (int i = 0; i < 9; i++) { 050 currentValue = currentValue.add(zeroPointOne); 051 } 052 assertEquals(currentValue, new BigDecimal("1.0")); 053 assertEquals(1d, currentValue.doubleValue()); 054 } 055 056 /** 057 * Demonstrates the effect of Double approximations on equality. 058 */ 059 @Test 060 public void testDoubleApproximation() { 061 assertEquals(999199.1231231236, 999199.1231231235); 062 } 063 064 /** 065 * Demonstrates the possible issue with the default rounding mode of NumberFormat (RoundingMode.HALF_EVEN). 066 * This is a modified version from the blog post: 067 * https://blogs.oracle.com/CoreJavaTechTips/entry/the_need_for_bigdecimal 068 * 069 * No idea why the blog says "why does 90.045 round down to 90.04 and not up to 90.05" because it is not the case. 070 */ 071 @Test 072 public void testRoundingUsingFormat() { 073 074 // Simple default DecimalFormat to keep only 2 decimals 075 // this simply demonstrates the default rounding behavior (RoundingMode.HALF_EVEN) 076 NumberFormat myFormatter = new DecimalFormat("##.##"); 077 assertEquals("90.05", myFormatter.format(90.045)); // 90.045 078 assertEquals("90.14", myFormatter.format(90.145)); 079 assertEquals("90.25", myFormatter.format(90.245)); 080 assertEquals("90.34", myFormatter.format(90.345)); 081 082 // same thing with NumberFormat for Currency 083 NumberFormat moneyFormatter = NumberFormat.getCurrencyInstance(); 084 assertTrue(moneyFormatter.format(90.045).contains("90.05")); 085 assertTrue(moneyFormatter.format(90.145).contains("90.14")); 086 } 087 088 /** 089 * Example from https://en.wikipedia.org/wiki/Floating_point#Accuracy_problems 090 * Simply demonstrates that (a + b) + c is not necessarily equal to a + (b + c) using double. 091 */ 092 @Test 093 public void testAdditionAssociativity() { 094 double a = 1234.567, b = 45.67834, c = 0.0004; 095 096 double t1 = (a + b) + c; // 1280.2457399999998 097 double t2 = a + (b + c); // 1280.24574 098 assertNotEquals(t2, t1, 0.0); 099 100 // Try the same with BigDecimal 101 BigDecimal a2 = new BigDecimal("1234.567"), 102 b2 = new BigDecimal("45.67834"), 103 c2 = new BigDecimal("0.0004"); 104 105 // BigDecimla is immutable 106 BigDecimal t3 = a2.add(b2); 107 t3 = t3.add(c2); 108 109 BigDecimal t4 = b2.add(c2); 110 t4 = a2.add(t4); 111 112 assertEquals(t3, t4); 113 assertEquals(new BigDecimal("1280.24574"), t3); 114 } 115}