Issue #7037 has been updated by headius (Charles Nutter).


Ok, I can buy the precision argument, and it fits if I expand my example to all values of 1.x5:

irb(main):002:0> "%1.1f" % 1.05
=> "1.1"
irb(main):003:0> "%1.1f" % 1.15
=> "1.1"
irb(main):004:0> "%1.1f" % 1.25
=> "1.2"
irb(main):005:0> "%1.1f" % 1.35
=> "1.4"
irb(main):006:0> "%1.1f" % 1.45
=> "1.4"
irb(main):007:0> "%1.1f" % 1.55
=> "1.6"
irb(main):008:0> "%1.1f" % 1.65
=> "1.6"
irb(main):009:0> "%1.1f" % 1.75
=> "1.8"
irb(main):010:0> "%1.1f" % 1.85
=> "1.9"
irb(main):011:0> "%1.1f" % 1.95
=> "1.9"

My next question is if this is actually desirable or not. In JRuby and Java/JDK, it appears float formatting rounds half up, and treats imprecise halves as precise.

JRuby:


irb(main):010:0> "%1.1f" % 1.05
=> "1.1"
irb(main):011:0> "%1.1f" % 1.15
=> "1.2"
irb(main):012:0> "%1.1f" % 1.25
=> "1.3"
irb(main):013:0> "%1.1f" % 1.35
=> "1.4"
irb(main):014:0> "%1.1f" % 1.45
=> "1.5"
irb(main):015:0> "%1.1f" % 1.55
=> "1.6"
irb(main):016:0> "%1.1f" % 1.65
=> "1.7"
irb(main):017:0> "%1.1f" % 1.75
=> "1.8"
irb(main):018:0> "%1.1f" % 1.85
=> "1.9"
irb(main):019:0> "%1.1f" % 1.95
=> "2.0"

Even given arguments that we should round half to even, we'd likely be consistent with human expectations here.

Here's Java's String.format in action:

public class FormatFloat {
  public static void main(String[] args) {
    System.out.println(String.format("%1.1f",1.05));
    System.out.println(String.format("%1.1f",1.15));
    System.out.println(String.format("%1.1f",1.25));
    System.out.println(String.format("%1.1f",1.35));
    System.out.println(String.format("%1.1f",1.45));
    System.out.println(String.format("%1.1f",1.55));
    System.out.println(String.format("%1.1f",1.65));
    System.out.println(String.format("%1.1f",1.75));
    System.out.println(String.format("%1.1f",1.85));
    System.out.println(String.format("%1.1f",1.95));
  }
}

Output:

system ~/projects/jruby $ java FormatFloat
1.1
1.2
1.3
1.4
1.5
1.6
1.7
1.8
1.9
2.0

These are Java doubles. JRuby uses the same representation internally, so ignoring the half-up-versus-half-even issue, this may be a platform-specific float representation question.

The JVM does not enforce strict IEEE 754 floating point math since Java 1.2 for performance reasons; instead, it may (not required) use the most efficient and accurate representation of floating point values on the given platform. For x86_64, that is not a 64-bit value but is instead 80-bit extended precision. This means (correct me if I'm wrong) that the JVM *can* accurately represent 1.05 and friends exactly as the equivalent of 105x10^-2 since it can fully and accurately represent a 64-bit signed integer and with an extra 16 bits for exponentiation.

In this light, ignoring the rounding strategy for the moment, the JRuby and JVM results make sense. Halves can all be represented accurately up to 64 bit signed integer values + exponent.

So this leaves me with two questions about how to handle this issue in JRuby:

* Is strict IEEE 754 floating-point precision a specified behavior of Ruby?

If yes, then all builds of MRI on all platforms must exhibit the same behavior regardless of platform representation or compiler optimizations. If no, then JRuby's use of JVM floating-point representation and behavior are ok. We *may* be able to force IEEE 754 floating point behavior in JRuby, but my quick tests seemed to show it's not as simple as just turning on strictfp. I'd prefer to not have to go that direction.

* Is rounding half even a specified behavior of Ruby?

If yes, I question whether it's a significant distinction to make, since only two "half" values can ever be represented accurately in IEEE 754 anyway: xx25 and xx75. If no, then JRuby's current behavior is acceptable as a platform-specific or implementation-specific detail. In JRuby's case, where we're relying on JVM floating-point representation, we are always using the "round half away from zero" strategy, which has bias, but we're consistent for all input values.
----------------------------------------
Bug #7037: float formatting inconsistently rounds half to even
https://bugs.ruby-lang.org/issues/7037#change-29541

Author: headius (Charles Nutter)
Status: Closed
Priority: Normal
Assignee: 
Category: core
Target version: 
ruby -v: 2.0.0dev


MRI does not appear to consistently round half to even. I'm not sure what rounding strategy this is, but it rounds xx05 and xx15 to odd for xxx, and other values to even:

irb(main):001:0> "%1.1f" % 1.05
=> "1.1"
irb(main):002:0> "%1.1f" % 1.15
=> "1.1"
irb(main):003:0> "%1.1f" % 1.25
=> "1.2"
irb(main):004:0> "%1.1f" % 1.35
=> "1.4"
irb(main):005:0> "%1.1f" % 1.45
=> "1.4"
irb(main):006:0> "%1.1f" % 1.55
=> "1.6"

None of the tie-breaking strategies I could find (http://en.wikipedia.org/wiki/Rounding#Tie-breaking) seem to support MRI's model.

If MRI is indeed using "half even", xx05 should round to xx0 and xx15 should round to xx2. An example with Java's BigDecimal appears to support this:

irb(main):029:0> java.math.BigDecimal.new('1.05').round(java.math.MathContext.new(2, java.math.RoundingMode::HALF_EVEN)).to_s
=> "1.0"
irb(main):030:0> java.math.BigDecimal.new('1.15').round(java.math.MathContext.new(2, java.math.RoundingMode::HALF_EVEN)).to_s
=> "1.2"
irb(main):031:0> java.math.BigDecimal.new('1.25').round(java.math.MathContext.new(2, java.math.RoundingMode::HALF_EVEN)).to_s
=> "1.2"
irb(main):032:0> java.math.BigDecimal.new('1.35').round(java.math.MathContext.new(2, java.math.RoundingMode::HALF_EVEN)).to_s
=> "1.4"

We would like clarification about the proper rounding tie-breaker strategy to use so we can fix this JRuby issue properly: http://jira.codehaus.org/browse/JRUBY-6889


-- 
http://bugs.ruby-lang.org/