一个JDK7的四舍五入的bug引发的思考

本文探讨了一个在JDK7中出现的四舍五入Bug,并展示了该Bug在升级到JDK8后的修复情况。通过对测试案例的详细分析,文章强调了在处理货币等精确数值时避免使用浮点数的重要性,并推荐使用BigDecimal类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个JDK7的四舍五入的bug引发的思考

1.背景:

今天我的 feilong-core 项目使用 jdk8 进行maven install 的时候,有一个测试类报错, 但是原先使用jdk7 进行maven install的时候却是正常通过,

issue 参见 venusdrogon/feilong-core#165

2.测试类代码如下:

    @Test
    public void testFormat32(){
        assertEquals("1.2", NumberFormatUtil.format(1.15, "#####.#", RoundingMode.HALF_EVEN));
        assertEquals("1.2", NumberFormatUtil.format(1.25, "#####.#", RoundingMode.HALF_EVEN));
        assertEquals("1.3", NumberFormatUtil.format(1.251, "#####.#", RoundingMode.HALF_EVEN));

        assertEquals("-1.2", NumberFormatUtil.format(-1.15, "#####.#", RoundingMode.HALF_EVEN));
        assertEquals("-1.2", NumberFormatUtil.format(-1.25, "#####.#", RoundingMode.HALF_EVEN));
        assertEquals("-1.3", NumberFormatUtil.format(-1.251, "#####.#", RoundingMode.HALF_EVEN));
    }


    @Test
    public void testFormat321(){
        assertEquals("1.2", NumberFormatUtil.format(1.15, "#####.#", null));
        assertEquals("1.3", NumberFormatUtil.format(1.25, "#####.#", null));
        assertEquals("1.3", NumberFormatUtil.format(1.251, "#####.#", null));

        assertEquals("-1.2", NumberFormatUtil.format(-1.15, "#####.#", null));
        assertEquals("-1.3", NumberFormatUtil.format(-1.25, "#####.#", null));
        assertEquals("-1.3", NumberFormatUtil.format(-1.251, "#####.#", null));
    }


    @Test
    public void testFormat111(){
        assertEquals("1.2", NumberFormatUtil.format(1.15, "#####.#"));
        assertEquals("1.3", NumberFormatUtil.format(1.25, "#####.#"));
        assertEquals("1.3", NumberFormatUtil.format(1.251, "#####.#"));

        assertEquals("-1.2", NumberFormatUtil.format(-1.15, "#####.#"));
        assertEquals("-1.3", NumberFormatUtil.format(-1.25, "#####.#"));
        assertEquals("-1.3", NumberFormatUtil.format(-1.251, "#####.#"));
    }

3.报错信息

    Tests run: 568, Failures: 3, Errors: 0, Skipped: 2, Time elapsed: 2.194 sec <<< FAILURE! - in com.feilong.core.FeiLongSuiteTests
    testFormat111(com.feilong.core.text.NumberFormatUtilTest)  Time elapsed: 0.009 sec  <<< FAILURE!
    org.junit.ComparisonFailure: expected:<1.[2]> but was:<1.[1]>
        at com.feilong.core.text.NumberFormatUtilTest.testFormat111(NumberFormatUtilTest.java:149)

    testFormat321(com.feilong.core.text.NumberFormatUtilTest)  Time elapsed: 0 sec  <<< FAILURE!
    org.junit.ComparisonFailure: expected:<1.[2]> but was:<1.[1]>
        at com.feilong.core.text.NumberFormatUtilTest.testFormat321(NumberFormatUtilTest.java:135)

    testFormat32(com.feilong.core.text.NumberFormatUtilTest)  Time elapsed: 0.001 sec  <<< FAILURE!
    org.junit.ComparisonFailure: expected:<1.[2]> but was:<1.[1]>
        at com.feilong.core.text.NumberFormatUtilTest.testFormat32(NumberFormatUtilTest.java:121)


    Results :

    Failed tests: 
      NumberFormatUtilTest.testFormat111:149 expected:<1.[2]> but was:<1.[1]>
      NumberFormatUtilTest.testFormat32:121 expected:<1.[2]> but was:<1.[1]>
      NumberFormatUtilTest.testFormat321:135 expected:<1.[2]> but was:<1.[1]>

    Tests run: 568, Failures: 3, Errors: 0, Skipped: 2

    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD FAILURE

4.原因

经过搜索,发现

https://bugs.openjdk.java.net/browse/JDK-7131459
https://bugs.openjdk.java.net/browse/JDK-8029896
http://stackoverflow.com/questions/22797964/is-inconsistency-in-rounding-between-java-7-and-java-8-a-bug

jdk7上是bug , jdk8 修复了

image

5.思考

  • 不要使用 float 或者 double 浮点数来表述货币这样的精确数量的值,会导致舍入误差。使用浮点数来进行元和分计算会得到灾难性的后果
  • 浮点数最好用来表示象测量值这类数值,这类值从一开始就不怎么精确。(PS:一般情况在你的工作中理论上是用不到的)
  • 尽量不要用 float 或者 double ,来做 +-*/ 运算,以及format,小数请使用 BigDecimal
  • BigDecimal 的构造函数有

    • java.math.BigDecimal.BigDecimal(double)
    • java.math.BigDecimal.BigDecimal(String)

    请使用 java.math.BigDecimal.BigDecimal(String))(PS:如果你使用 sonar 进行代码扫描的话,它会给你提示和建议)

  • 比较两个 BigDecimal 大小,请使用 java.math.BigDecimal.compareTo(BigDecimal) 方法,而不要使用 java.math.BigDecimal.equals(Object)方法,

    因为

    equals()方法认为,两个表示同一个数但换算值不同(例如, 100.00 和 100.000 )的 BigDecimal 值是不相等的。
    然而, compareTo() 方法会认为这两个数是相等的,所以在从数值上比较两个 BigDecimal 值时,应该使用 compareTo() 而不是 equals() 。

    参见源码:

6.参考

Files contained in junit4-4.8.2.jar: LICENSE.txt META-INF/MANIFEST.MF junit.extensions.ActiveTestSuite.class junit.extensions.RepeatedTest.class junit.extensions.TestDecorator.class junit.extensions.TestSetup.class junit.extensions.package-info.class junit.framework.Assert.class junit.framework.AssertionFailedError.class junit.framework.ComparisonCompactor.class junit.framework.ComparisonFailure.class junit.framework.JUnit4TestAdapter.class junit.framework.JUnit4TestAdapterCache.class junit.framework.JUnit4TestCaseFacade.class junit.framework.Protectable.class junit.framework.Test.class junit.framework.TestCase.class junit.framework.TestFailure.class junit.framework.TestListener.class junit.framework.TestResult.class junit.framework.TestSuite.class junit.framework.package-info.class junit.runner.BaseTestRunner.class junit.runner.TestRunListener.class junit.runner.Version.class junit.runner.package-info.class junit.textui.ResultPrinter.class junit.textui.TestRunner.class junit.textui.package-info.class org.hamcrest.BaseDescription.class org.hamcrest.BaseMatcher.class org.hamcrest.CoreMatchers.class org.hamcrest.Description.class org.hamcrest.Factory.class org.hamcrest.Matcher.class org.hamcrest.SelfDescribing.class org.hamcrest.StringDescription.class org.hamcrest.core.AllOf.class org.hamcrest.core.AnyOf.class org.hamcrest.core.DescribedAs.class org.hamcrest.core.Is.class org.hamcrest.core.IsAnything.class org.hamcrest.core.IsEqual.class org.hamcrest.core.IsInstanceOf.class org.hamcrest.core.IsNot.class org.hamcrest.core.IsNull.class org.hamcrest.core.IsSame.class org.hamcrest.internal.ArrayIterator.class org.hamcrest.internal.SelfDescribingValue.class org.hamcrest.internal.SelfDescribingValueIterator.class org.junit.After.class org.junit.AfterClass.class org.junit.Assert.class org.junit.Assume.class org.junit.Before.class org.junit.BeforeClass.class org.junit.ComparisonFailure.class org.junit.Ignore.class org.junit.Rule.class org.junit.Test.class org.junit.experimental.ParallelComputer.class org.junit.experimental.categories.Categories.class org.junit.experimental.categories.Category.class org.junit.experimental.max.CouldNotReadCoreException.class org.junit.experimental.max.MaxCore.class org.junit.experimental.max.MaxHistory.class org.junit.experimental.results.FailureList.class org.junit.experimental.results.PrintableResult.class org.junit.experimental.results.ResultMatchers.class org.junit.experimental.runners.Enclosed.class org.junit.experimental.theories.DataPoint.class org.junit.experimental.theories.DataPoints.class org.junit.experimental.theories.ParameterSignature.class org.junit.experimental.theories.ParameterSupplier.class org.junit.experimental.theories.ParametersSuppliedBy.class org.junit.experimental.theories.PotentialAssignment.class org.junit.experimental.theories.Theories.class org.junit.experimental.theories.Theory.class org.junit.experimental.theories.internal.AllMembersSupplier.class org.junit.experimental.theories.internal.Assignments.class org.junit.experimental.theories.internal.ParameterizedAssertionError.class org.junit.experimental.theories.suppliers.TestedOn.class org.junit.experimental.theories.suppliers.TestedOnSupplier.class org.junit.internal.ArrayComparisonFailure.class org.junit.internal.AssumptionViolatedException.class org.junit.internal.ComparisonCriteria.class org.junit.internal.ExactComparisonCriteria.class org.junit.internal.InexactComparisonCriteria.class org.junit.internal.JUnitSystem.class org.junit.internal.RealSystem.class org.junit.internal.TextListener.class org.junit.internal.builders.AllDefaultPossibilitiesBuilder.class org.junit.internal.builders.AnnotatedBuilder.class org.junit.internal.builders.IgnoredBuilder.class org.junit.internal.builders.IgnoredClassRunner.class org.junit.internal.builders.JUnit3Builder.class org.junit.internal.builders.JUnit4Builder.class org.junit.internal.builders.NullBuilder.class org.junit.internal.builders.SuiteMethodBuilder.class org.junit.internal.matchers.CombinableMatcher.class org.junit.internal.matchers.Each.class org.junit.internal.matchers.IsCollectionContaining.class org.junit.internal.matchers.StringContains.class org.junit.internal.matchers.SubstringMatcher.class org.junit.internal.matchers.TypeSafeMatcher.class org.junit.internal.requests.ClassRequest.class org.junit.internal.requests.FilterRequest.class org.junit.internal.requests.SortingRequest.class org.junit.internal.requests.package-info.class org.junit.internal.runners.ClassRoadie.class org.junit.internal.runners.ErrorReportingRunner.class org.junit.internal.runners.FailedBefore.class org.junit.internal.runners.InitializationError.class org.junit.internal.runners.JUnit38ClassRunner.class org.junit.internal.runners.JUnit4ClassRunner.class org.junit.internal.runners.MethodRoadie.class org.junit.internal.runners.MethodValidator.class org.junit.internal.runners.SuiteMethod.class org.junit.internal.runners.TestClass.class org.junit.internal.runners.TestMethod.class org.junit.internal.runners.model.EachTestNotifier.class org.junit.internal.runners.model.MultipleFailureException.class org.junit.internal.runners.model.ReflectiveCallable.class org.junit.internal.runners.package-info.class org.junit.internal.runners.statements.ExpectException.class org.junit.internal.runners.statements.Fail.class org.junit.internal.runners.statements.FailOnTimeout.class org.junit.internal.runners.statements.InvokeMethod.class org.junit.internal.runners.statements.RunAfters.class org.junit.internal.runners.statements.RunBefores.class org.junit.matchers.JUnitMatchers.class org.junit.matchers.package-info.class org.junit.package-info.class org.junit.rules.ErrorCollector.class org.junit.rules.ExpectedException.class org.junit.rules.ExternalResource.class org.junit.rules.MethodRule.class org.junit.rules.TemporaryFolder.class org.junit.rules.TestName.class org.junit.rules.TestWatchman.class org.junit.rules.Timeout.class org.junit.rules.Verifier.class org.junit.runner.Computer.class org.junit.runner.Describable.class org.junit.runner.Description.class org.junit.runner.JUnitCore.class org.junit.runner.Request.class org.junit.runner.Result.class org.junit.runner.RunWith.class org.junit.runner.Runner.class org.junit.runner.manipulation.Filter.class org.junit.runner.manipulation.Filterable.class org.junit.runner.manipulation.NoTestsRemainException.class org.junit.runner.manipulation.Sortable.class org.junit.runner.manipulation.Sorter.class org.junit.runner.manipulation.package-info.class org.junit.runner.notification.Failure.class org.junit.runner.notification.RunListener.class org.junit.runner.notification.RunNotifier.class org.junit.runner.notification.StoppedByUserException.class org.junit.runner.notification.package-info.class org.junit.runner.package-info.class org.junit.runners.AllTests.class org.junit.runners.BlockJUnit4ClassRunner.class org.junit.runners.JUnit4.class org.junit.runners.Parameterized.class org.junit.runners.ParentRunner.class org.junit.runners.Suite.class org.junit.runners.model.FrameworkField.class org.junit.runners.model.FrameworkMember.class org.junit.runners.model.FrameworkMethod.class org.junit.runners.model.InitializationError.class org.junit.runners.model.RunnerBuilder.class org.junit.runners.model.RunnerScheduler.class org.junit.runners.model.Statement.class org.junit.runners.model.TestClass.class org.junit.runners.package-info.class org/hamcrest/core/package.html org/hamcrest/package.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值