diff --git a/sjsonnet/src/sjsonnet/DecimalFormat.scala b/sjsonnet/src/sjsonnet/DecimalFormat.scala index b95cfe61..b0c8ccd9 100644 --- a/sjsonnet/src/sjsonnet/DecimalFormat.scala +++ b/sjsonnet/src/sjsonnet/DecimalFormat.scala @@ -19,7 +19,16 @@ object DecimalFormat { hashes: Int, alternate: Boolean, expLengthOpt: Option[Int], - number: Double): String = { + number: Double): String = + format(zeroes, hashes, alternate, expLengthOpt, number, useExactDecimal = false) + + private[sjsonnet] def format( + zeroes: Int, + hashes: Int, + alternate: Boolean, + expLengthOpt: Option[Int], + number: Double, + useExactDecimal: Boolean): String = { expLengthOpt match { case Some(expLength) => var expNum = @@ -75,9 +84,15 @@ object DecimalFormat { if (alternate) prefix + "." else prefix } else { val denominator = BigDecimal(10).pow(precision) - val bd = BigDecimal(number).abs + val exactDecimal = useExactDecimal || precision > 15 + val bd = + if (exactDecimal) BigDecimal.exact(number).abs + else BigDecimal(number).abs val scaled = - (bd * denominator + BigDecimal("0.5")).setScale(0, BigDecimal.RoundingMode.FLOOR) + if (exactDecimal) + (bd * denominator).setScale(0, BigDecimal.RoundingMode.HALF_EVEN) + else + (bd * denominator + BigDecimal("0.5")).setScale(0, BigDecimal.RoundingMode.FLOOR) val wholeBD = (scaled / denominator).setScale(0, BigDecimal.RoundingMode.FLOOR) val fracBD = (scaled - wholeBD * denominator).abs diff --git a/sjsonnet/src/sjsonnet/Format.scala b/sjsonnet/src/sjsonnet/Format.scala index d878fd2f..1219a195 100644 --- a/sjsonnet/src/sjsonnet/Format.scala +++ b/sjsonnet/src/sjsonnet/Format.scala @@ -1222,7 +1222,8 @@ object Format { if (formatted.alternate) 0 else fractionalPrecision, formatted.alternate, None, - math.abs(s) + math.abs(s), + useExactDecimal = precision > 15 ), numeric = true, signedConversion = !isNegative(s) diff --git a/sjsonnet/test/resources/new_test_suite/format_exact_binary64_precision.jsonnet b/sjsonnet/test/resources/new_test_suite/format_exact_binary64_precision.jsonnet new file mode 100644 index 00000000..4046cfe8 --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/format_exact_binary64_precision.jsonnet @@ -0,0 +1,16 @@ +std.assertEqual(std.format("%.16f", 3.14159265358979323), "3.1415926535897931") && +std.assertEqual(std.format("%.17f", 1.0 / 3.0), "0.33333333333333331") && +std.assertEqual(std.format("%.17f", 3.14159265358979323), "3.14159265358979312") && +std.assertEqual(std.format("%.17f", 0.1), "0.10000000000000001") && +std.assertEqual(std.format("%.17g", 3.14159265358979323), "3.1415926535897931") && +std.assertEqual(std.format("%.17g", 31.4159265358979323), "31.415926535897931") && +std.assertEqual(std.format("%.16f", 1.00000762939453125), "1.0000076293945312") && +std.assertEqual(std.format("%.16f", 1.00002288818359375), "1.0000228881835938") && +std.assertEqual(std.format("%.16f", -1.00000762939453125), "-1.0000076293945312") && +std.assertEqual(std.format("%.17g", 1.00000762939453125), "1.0000076293945312") && +std.assertEqual(std.format("%.15f", 3.14159265358979323), "3.141592653589793") && +std.assertEqual(std.format("%.20f", 0.1), "0.10000000000000000555") && +std.assertEqual(std.format("%.20f", 1e-10), "0.00000000010000000000") && +std.assertEqual(std.format("%.16f", 5e-324), "0.0000000000000000") && +std.assertEqual(std.format("%.20g", 0.1), "0.10000000000000000555") && +true diff --git a/sjsonnet/test/resources/new_test_suite/format_exact_binary64_precision.jsonnet.golden b/sjsonnet/test/resources/new_test_suite/format_exact_binary64_precision.jsonnet.golden new file mode 100644 index 00000000..27ba77dd --- /dev/null +++ b/sjsonnet/test/resources/new_test_suite/format_exact_binary64_precision.jsonnet.golden @@ -0,0 +1 @@ +true