Skip to content

Commit fe52282

Browse files
authored
Merge pull request #8056 from enebo/float_fun
Add hex exponential notation for Kernel::Float
2 parents 7f8741b + e25bf82 commit fe52282

File tree

2 files changed

+102
-27
lines changed

2 files changed

+102
-27
lines changed

core/src/main/java/org/jruby/RubyKernel.java

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
package org.jruby;
4545

4646
import java.io.ByteArrayOutputStream;
47+
import java.math.BigInteger;
4748
import java.util.ArrayList;
4849
import java.util.Arrays;
4950
import java.util.Map;
@@ -111,6 +112,7 @@
111112
import static org.jruby.runtime.Visibility.PRIVATE;
112113
import static org.jruby.runtime.Visibility.PROTECTED;
113114
import static org.jruby.runtime.Visibility.PUBLIC;
115+
import static org.jruby.util.RubyStringBuilder.str;
114116

115117
/**
116118
* Note: For CVS history, see KernelModule.java.
@@ -412,6 +414,103 @@ public static RubyFloat new_float(final Ruby runtime, IRubyObject object) {
412414
return (RubyFloat) new_float(runtime.getCurrentContext(), object, true);
413415
}
414416

417+
private static BigInteger SIXTEEN = BigInteger.valueOf(16L);
418+
private static BigInteger MINUS_ONE = BigInteger.valueOf(-1L);
419+
420+
private static RaiseException floatError(Ruby runtime, ByteList string) {
421+
throw runtime.newArgumentError(str(runtime, "invalid value for Float(): ", runtime.newString(string)));
422+
}
423+
424+
/**
425+
* Parse hexidecimal exponential notation:
426+
* <a href="https://en.wikipedia.org/wiki/Hexadecimal#Hexadecimal_exponential_notation">...</a>
427+
* <p/>
428+
* This method assumes the string is a valid-formatted string.
429+
*
430+
* @param str the bytelist to be parsed
431+
* @return the result.
432+
*/
433+
public static double parseHexidecimalExponentString2(Ruby runtime, ByteList str) {
434+
byte[] bytes = str.unsafeBytes();
435+
int length = str.length();
436+
if (length <= 2) throw floatError(runtime, str);
437+
int sign = 1;
438+
int letter;
439+
int i = str.begin();
440+
441+
letter = bytes[i];
442+
if (letter == '+') {
443+
i++;
444+
} else if (letter == '-') {
445+
sign = -1;
446+
i++;
447+
}
448+
449+
// Skip '0x'
450+
letter = bytes[i++];
451+
if (letter != '0') throw floatError(runtime, str);
452+
letter = bytes[i++];
453+
if (letter != 'x') throw floatError(runtime, str);
454+
455+
int exponent = 0;
456+
int explicitExponent = 0;
457+
int explicitExponentSign = 1;
458+
boolean periodFound = false;
459+
boolean explicitExponentFound = false;
460+
BigInteger value = BigInteger.valueOf(0L);
461+
462+
if (i == length || bytes[i] == '_' || bytes[i] == 'p' || bytes[i] == 'P') throw floatError(runtime, str);
463+
464+
for(; i < length && !explicitExponentFound; i++) {
465+
letter = bytes[i];
466+
switch (letter) {
467+
case '.': // Fractional part of floating point value "1(.)23"
468+
periodFound = true;
469+
continue;
470+
case 'p': // Explicit exponent "1.23(p)1a"
471+
case 'P':
472+
if (bytes[i-1] == '_' || i == length - 1) throw floatError(runtime, str);
473+
explicitExponentFound = true;
474+
continue;
475+
case '_':
476+
continue;
477+
}
478+
479+
// base 16 value representing main pieces of number
480+
int digit = Character.digit(letter, 16);
481+
if (Character.forDigit(digit, 16) == 0) throw floatError(runtime, str);
482+
value = value.multiply(SIXTEEN).add(BigInteger.valueOf(digit));
483+
484+
if (periodFound) exponent++;
485+
}
486+
487+
if (explicitExponentFound) {
488+
if (bytes[i] == '-') {
489+
explicitExponentSign = -1;
490+
i++;
491+
} else if (bytes[i] == '+') {
492+
i++;
493+
} else if (bytes[i] == '_') {
494+
throw floatError(runtime, str);
495+
}
496+
497+
for (; i < length; i++) { // base 10 value representing base 2 exponent
498+
letter = bytes[i];
499+
if (letter == '_') {
500+
if (i == length - 1) throw floatError(runtime, str);
501+
continue;
502+
}
503+
int digit = Character.digit(letter, 10);
504+
if (Character.forDigit(digit, 10) == 0) throw floatError(runtime, str);
505+
explicitExponent = explicitExponent * 10 + digit;
506+
}
507+
}
508+
509+
// each exponent in main number is 4 bits and the value after 'p' represents a power of 2. Wacky.
510+
int scaleFactor = 4 * exponent - explicitExponent * explicitExponentSign;
511+
return sign * Math.scalb(value.doubleValue(), -scaleFactor);
512+
}
513+
415514
public static IRubyObject new_float(ThreadContext context, IRubyObject object, boolean exception) {
416515
Ruby runtime = context.runtime;
417516

@@ -430,6 +529,9 @@ public static IRubyObject new_float(ThreadContext context, IRubyObject object, b
430529
}
431530

432531
if (bytes.startsWith(ZEROx)) { // startsWith("0x")
532+
if (bytes.indexOf('p') != -1 || bytes.indexOf('P') != -1) {
533+
return runtime.newFloat(parseHexidecimalExponentString2(runtime, bytes));
534+
}
433535
IRubyObject inum = ConvertBytes.byteListToInum(runtime, bytes, 16, true, exception);
434536
if (!exception && inum.isNil()) return inum;
435537
return ((RubyInteger) inum).toFloat();

spec/tags/ruby/core/kernel/Float_tags.txt

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)