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.