-
-
Notifications
You must be signed in to change notification settings - Fork 925
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Truffle] Handle Bignum in sprintf Integer format
- 9.4.12.0
- 9.4.11.0
- 9.4.10.0
- 9.4.9.0
- 9.4.8.0
- 9.4.7.0
- 9.4.6.0
- 9.4.5.0
- 9.4.4.0
- 9.4.3.0
- 9.4.2.0
- 9.4.1.0
- 9.4.0.0
- 9.3.15.0
- 9.3.14.0
- 9.3.13.0
- 9.3.12.0
- 9.3.11.0
- 9.3.10.0
- 9.3.9.0
- 9.3.8.0
- 9.3.7.0
- 9.3.6.0
- 9.3.5.0
- 9.3.4.0
- 9.3.3.0
- 9.3.2.0
- 9.3.1.0
- 9.3.0.0
- 9.2.21.0
- 9.2.20.1
- 9.2.20.0
- 9.2.19.0
- 9.2.18.0
- 9.2.17.0
- 9.2.16.0
- 9.2.15.0
- 9.2.14.0
- 9.2.13.0
- 9.2.12.0
- 9.2.11.1
- 9.2.11.0
- 9.2.10.0
- 9.2.9.0
- 9.2.8.0
- 9.2.7.0
- 9.2.6.0
- 9.2.5.0
- 9.2.4.1
- 9.2.4.0
- 9.2.3.0
- 9.2.2.0
- 9.2.1.0
- 9.2.0.0
- 9.1.17.0
- 9.1.16.0
- 9.1.15.0
- 9.1.14.0
- 9.1.13.0
- 9.1.12.0
- 9.1.11.0
- 9.1.10.0
- 9.1.9.0
- 9.1.8.0
- 9.1.7.0
- 9.1.6.0
- 9.1.5.0
- 9.1.4.0
- 9.1.3.0
Brandon Fish
committed
Aug 11, 2016
1 parent
68cad24
commit a7ff062
Showing
2 changed files
with
135 additions
and
87 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,8 +29,6 @@ | |
import org.jruby.util.ConvertBytes; | ||
|
||
import java.math.BigInteger; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Locale; | ||
|
||
@NodeChildren({ | ||
@NodeChild(value = "width", type = FormatNode.class), | ||
|
@@ -47,6 +45,11 @@ public abstract class FormatIntegerNode extends FormatNode { | |
|
||
private static final byte[] PREFIX_NEGATIVE = {'.', '.'}; | ||
|
||
private static final BigInteger BIG_32 = BigInteger.valueOf(((long)Integer.MAX_VALUE + 1L) << 1); | ||
private static final BigInteger BIG_64 = BIG_32.shiftLeft(32); | ||
private static final BigInteger BIG_MINUS_32 = BigInteger.valueOf((long)Integer.MIN_VALUE << 1); | ||
private static final BigInteger BIG_MINUS_64 = BIG_MINUS_32.shiftLeft(32); | ||
|
||
private final char format; | ||
private final boolean hasSpaceFlag; | ||
private final boolean hasZeroFlag; | ||
|
@@ -74,10 +77,42 @@ public byte[] format(int width, int precision, int arg) { | |
@Specialization | ||
public byte[] format(int width, int precision, long arg) { | ||
|
||
ByteList buf = new ByteList(); | ||
final char fchar = this.getFormatCharacter(); | ||
final boolean sign = this.getSign(fchar); | ||
final int base = this.getBase(fchar); | ||
final boolean zero = arg == 0; | ||
final boolean negative = arg < 0; | ||
|
||
final byte[] bytes; | ||
if (negative && fchar == 'u') { | ||
bytes = getUnsignedNegativeBytes(arg); | ||
} else { | ||
bytes = getFixnumBytes(arg, base, sign, fchar == 'X'); | ||
} | ||
|
||
return formatBytes(width, precision, fchar, sign, base, zero, negative, bytes); | ||
} | ||
|
||
@TruffleBoundary | ||
@Specialization(guards = "isRubyBignum(value)") | ||
public byte[] format(int width, int precision, DynamicObject value) { | ||
final BigInteger bigInteger = Layouts.BIGNUM.getValue(value); | ||
final boolean negative = bigInteger.signum() < 0; | ||
final boolean zero = bigInteger.equals(BigInteger.ZERO); | ||
final char fchar = this.getFormatCharacter(); | ||
final boolean sign = this.getSign(fchar); | ||
final int base = this.getBase(fchar); | ||
|
||
boolean usePrefixForZero = false; | ||
final byte[] bytes; | ||
if (negative && fchar == 'u') { | ||
bytes = getUnsignedNegativeBytes(bigInteger); | ||
} else { | ||
bytes = getBignumBytes(bigInteger, base, sign, fchar == 'X'); | ||
} | ||
return formatBytes(width, precision, fchar, sign, base, zero, negative, bytes); | ||
} | ||
|
||
private byte[] formatBytes(int width, int precision, char fchar, boolean sign, int base, boolean zero, boolean negative, byte[] bytes) { | ||
boolean hasMinusFlag = this.hasMinusFlag; | ||
if (width == PrintfSimpleTreeBuilder.DEFAULT) { | ||
width = 0; | ||
|
@@ -90,57 +125,16 @@ public byte[] format(int width, int precision, long arg) { | |
precision = PrintfSimpleTreeBuilder.DEFAULT; | ||
} | ||
|
||
byte[] bytes = null; | ||
int first = 0; | ||
byte[] prefix = null; | ||
boolean sign; | ||
boolean negative; | ||
|
||
byte signChar = 0; | ||
byte leadChar = 0; | ||
int base; | ||
|
||
char fchar = this.format; | ||
|
||
// 'd' and 'i' are the same | ||
if (fchar == 'i') fchar = 'd'; | ||
|
||
// 'u' with space or plus flags is same as 'd' | ||
if (fchar == 'u' && (hasSpaceFlag || hasPlusFlag)) { | ||
fchar = 'd'; | ||
} | ||
sign = (fchar == 'd' || (hasSpaceFlag || hasPlusFlag)); | ||
|
||
switch (fchar) { | ||
case 'o': | ||
base = 8; | ||
break; | ||
case 'x': | ||
case 'X': | ||
base = 16; | ||
break; | ||
case 'b': | ||
case 'B': | ||
base = 2; | ||
break; | ||
case 'u': | ||
case 'd': | ||
default: | ||
base = 10; | ||
break; | ||
} | ||
|
||
boolean zero; | ||
|
||
negative = arg < 0; | ||
zero = arg == 0; | ||
if (negative && fchar == 'u') { | ||
bytes = getUnsignedNegativeBytes(arg); | ||
} else { | ||
bytes = getFixnumBytes(arg, base, sign, fchar == 'X'); | ||
} | ||
ByteList buf = new ByteList(); | ||
|
||
if (hasFSharp) { | ||
if (!zero || usePrefixForZero) { | ||
if (!zero) { | ||
switch (fchar) { | ||
case 'o': | ||
prefix = PREFIX_OCTAL; | ||
|
@@ -204,7 +198,6 @@ public byte[] format(int width, int precision, long arg) { | |
int numlen = bytes.length - first; | ||
len += numlen; | ||
|
||
|
||
boolean hasPrecisionFlag = precision != PrintfSimpleTreeBuilder.DEFAULT; | ||
|
||
// if ((flags & (FLAG_ZERO|FLAG_PRECISION)) == FLAG_ZERO) { | ||
|
@@ -225,37 +218,60 @@ public byte[] format(int width, int precision, long arg) { | |
|
||
if (len < precision) { | ||
if (leadChar == 0) { | ||
if (fchar != 'd' || usePrefixForZero || !negative || | ||
if (fchar != 'd' || !negative || | ||
hasPrecisionFlag || | ||
(hasZeroFlag && !hasMinusFlag)) { | ||
buf.fill('0', precision - len); | ||
} | ||
} else if (leadChar == '.') { | ||
buf.fill(leadChar, precision - len); | ||
buf.append(PREFIX_NEGATIVE); | ||
} else if (!usePrefixForZero) { | ||
} else { | ||
buf.append(PREFIX_NEGATIVE); | ||
buf.fill(leadChar, precision - len - 1); | ||
} else { | ||
buf.fill(leadChar, precision - len + 1); // the 1 is for the stripped sign char | ||
} | ||
} else if (leadChar != 0) { | ||
if (((!hasZeroFlag && precision == PrintfSimpleTreeBuilder.DEFAULT) && usePrefixForZero) || | ||
(!usePrefixForZero && "xXbBo".indexOf(fchar) != -1)) { | ||
if ( "xXbBo".indexOf(fchar) != -1) { | ||
buf.append(PREFIX_NEGATIVE); | ||
} | ||
if (leadChar != '.') buf.append(leadChar); | ||
} | ||
buf.append(bytes, first, numlen); | ||
|
||
if (width > 0) buf.fill(' ', width); | ||
if (len < precision && fchar == 'd' && negative && | ||
!usePrefixForZero && hasMinusFlag) { | ||
if (len < precision && fchar == 'd' && negative && hasMinusFlag) { | ||
buf.fill(' ', precision - len); | ||
} | ||
return buf.bytes(); | ||
} | ||
|
||
private boolean getSign(char fchar) { | ||
return (fchar == 'd' || (hasSpaceFlag || hasPlusFlag)); | ||
} | ||
|
||
private static int getBase(char fchar) { | ||
final int base; | ||
switch (fchar) { | ||
case 'o': | ||
base = 8; | ||
break; | ||
case 'x': | ||
case 'X': | ||
base = 16; | ||
break; | ||
case 'b': | ||
case 'B': | ||
base = 2; | ||
break; | ||
case 'u': | ||
case 'd': | ||
default: | ||
base = 10; | ||
break; | ||
} | ||
return base; | ||
} | ||
|
||
|
||
private static byte[] getFixnumBytes(long arg, int base, boolean sign, boolean upper) { | ||
long val = arg; | ||
|
@@ -324,45 +340,80 @@ private static byte[] getUnsignedNegativeBytes(long arg) { | |
return ConvertBytes.longToCharBytes(((Long.MAX_VALUE + 1L) << 1) + arg); | ||
} | ||
|
||
private char getFormatCharacter(){ | ||
char fchar = this.format; | ||
|
||
@TruffleBoundary | ||
@Specialization(guards = "isRubyBignum(value)") | ||
public byte[] format(int width, int precision, DynamicObject value) { | ||
final BigInteger bigInteger = Layouts.BIGNUM.getValue(value); | ||
// 'd' and 'i' are the same | ||
if (fchar == 'i') fchar = 'd'; | ||
|
||
String formatted; | ||
switch (format) { | ||
case 'd': | ||
case 'i': | ||
case 'u': | ||
formatted = bigInteger.toString(); | ||
break; | ||
// 'u' with space or plus flags is same as 'd' | ||
if (fchar == 'u' && (hasSpaceFlag || hasPlusFlag)) { | ||
fchar = 'd'; | ||
} | ||
return fchar; | ||
} | ||
|
||
case 'o': | ||
formatted = bigInteger.toString(8).toLowerCase(Locale.ENGLISH); | ||
break; | ||
|
||
case 'x': | ||
formatted = bigInteger.toString(16).toLowerCase(Locale.ENGLISH); | ||
break; | ||
|
||
case 'X': | ||
formatted = bigInteger.toString(16).toUpperCase(Locale.ENGLISH); | ||
break; | ||
|
||
default: | ||
throw new UnsupportedOperationException(); | ||
private byte[] getUnsignedNegativeBytes(BigInteger bigval) { | ||
// calculation for negatives when %u specified | ||
// for values >= Integer.MIN_VALUE * 2, MRI uses (the equivalent of) | ||
// long neg_u = (((long)Integer.MAX_VALUE + 1) << 1) + val | ||
// for smaller values, BigInteger math is required to conform to MRI's | ||
// result. | ||
|
||
// ok, now it gets expensive... | ||
int shift = 0; | ||
// go through negated powers of 32 until we find one small enough | ||
for (BigInteger minus = BIG_MINUS_64; | ||
bigval.compareTo(minus) < 0; | ||
minus = minus.shiftLeft(32), shift++) { | ||
} | ||
// add to the corresponding positive power of 32 for the result. | ||
// meaningful? no. conformant? yes. I just write the code... | ||
BigInteger nPower32 = shift > 0 ? BIG_64.shiftLeft(32 * shift) : BIG_64; | ||
return stringToBytes(nPower32.add(bigval).toString(), false); | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
bjfish
Contributor
|
||
} | ||
|
||
while (formatted.length() < precision) { | ||
formatted = "0" + formatted; | ||
private static byte[] getBignumBytes(BigInteger val, int base, boolean sign, boolean upper) { | ||
if (sign || base == 10 || val.signum() >= 0) { | ||
return stringToBytes(val.toString(base), upper); | ||
} | ||
|
||
while (formatted.length() < width) { | ||
formatted = " " + formatted; | ||
// negative values | ||
byte[] bytes = val.toByteArray(); | ||
switch (base) { | ||
case 2: | ||
return ConvertBytes.twosComplementToBinaryBytes(bytes); | ||
case 8: | ||
return ConvertBytes.twosComplementToOctalBytes(bytes); | ||
case 16: | ||
return ConvertBytes.twosComplementToHexBytes(bytes, upper); | ||
default: | ||
return stringToBytes(val.toString(base), upper); | ||
} | ||
} | ||
|
||
|
||
return formatted.getBytes(StandardCharsets.US_ASCII); | ||
private static byte[] stringToBytes(CharSequence s, boolean upper) { | ||
int len = s.length(); | ||
byte[] bytes = new byte[len]; | ||
if (upper) { | ||
for (int i = len; --i >= 0; ) { | ||
int b = (byte) ((int) s.charAt(i) & (int) 0xff); | ||
if (b >= 'a' && b <= 'z') { | ||
bytes[i] = (byte) (b & ~0x20); | ||
} else { | ||
bytes[i] = (byte) b; | ||
} | ||
} | ||
} else { | ||
for (int i = len; --i >= 0; ) { | ||
bytes[i] = (byte) ((int) s.charAt(i) & (int) 0xff); | ||
} | ||
} | ||
return bytes; | ||
} | ||
|
||
} |
This sounds like "fun", is it based on a test or spec or just trying MRI?