mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8370013: Refactor Double.toHexString to eliminate regex and StringBuilder
Reviewed-by: rgiulietti, darcy
This commit is contained in:
parent
d720a8491b
commit
5862358965
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 1994, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, Alibaba Group Holding Limited. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -33,6 +34,7 @@ import java.util.Optional;
|
||||
import jdk.internal.math.FloatingDecimal;
|
||||
import jdk.internal.math.DoubleConsts;
|
||||
import jdk.internal.math.DoubleToDecimal;
|
||||
import jdk.internal.util.DecimalDigits;
|
||||
import jdk.internal.vm.annotation.IntrinsicCandidate;
|
||||
|
||||
/**
|
||||
@ -699,56 +701,80 @@ public final class Double extends Number
|
||||
* 7.19.6.1; however, the output of this method is more
|
||||
* tightly specified.
|
||||
*/
|
||||
if (!isFinite(d) )
|
||||
if (!isFinite(d)) {
|
||||
// For infinity and NaN, use the decimal output.
|
||||
return Double.toString(d);
|
||||
else {
|
||||
// Initialized to maximum size of output.
|
||||
StringBuilder answer = new StringBuilder(24);
|
||||
|
||||
if (Math.copySign(1.0, d) == -1.0) // value is negative,
|
||||
answer.append("-"); // so append sign info
|
||||
|
||||
answer.append("0x");
|
||||
|
||||
d = Math.abs(d);
|
||||
|
||||
if(d == 0.0) {
|
||||
answer.append("0.0p0");
|
||||
} else {
|
||||
boolean subnormal = (d < Double.MIN_NORMAL);
|
||||
|
||||
// Isolate significand bits and OR in a high-order bit
|
||||
// so that the string representation has a known
|
||||
// length.
|
||||
long signifBits = (Double.doubleToLongBits(d)
|
||||
& DoubleConsts.SIGNIF_BIT_MASK) |
|
||||
0x1000000000000000L;
|
||||
|
||||
// Subnormal values have a 0 implicit bit; normal
|
||||
// values have a 1 implicit bit.
|
||||
answer.append(subnormal ? "0." : "1.");
|
||||
|
||||
// Isolate the low-order 13 digits of the hex
|
||||
// representation. If all the digits are zero,
|
||||
// replace with a single 0; otherwise, remove all
|
||||
// trailing zeros.
|
||||
String signif = Long.toHexString(signifBits).substring(3,16);
|
||||
answer.append(signif.equals("0000000000000") ? // 13 zeros
|
||||
"0":
|
||||
signif.replaceFirst("0{1,12}$", ""));
|
||||
|
||||
answer.append('p');
|
||||
// If the value is subnormal, use the E_min exponent
|
||||
// value for double; otherwise, extract and report d's
|
||||
// exponent (the representation of a subnormal uses
|
||||
// E_min -1).
|
||||
answer.append(subnormal ?
|
||||
Double.MIN_EXPONENT:
|
||||
Math.getExponent(d));
|
||||
}
|
||||
return answer.toString();
|
||||
}
|
||||
|
||||
long doubleToLongBits = Double.doubleToLongBits(d);
|
||||
boolean negative = doubleToLongBits < 0;
|
||||
|
||||
if (d == 0.0) {
|
||||
return negative ? "-0x0.0p0" : "0x0.0p0";
|
||||
}
|
||||
d = Math.abs(d);
|
||||
// Check if the value is subnormal (less than the smallest normal value)
|
||||
boolean subnormal = d < Double.MIN_NORMAL;
|
||||
|
||||
// Isolate significand bits and OR in a high-order bit
|
||||
// so that the string representation has a known length.
|
||||
// This ensures we always have 13 hex digits to work with (52 bits / 4 bits per hex digit)
|
||||
long signifBits = doubleToLongBits & DoubleConsts.SIGNIF_BIT_MASK;
|
||||
|
||||
// Calculate the number of trailing zeros in the significand (in groups of 4 bits)
|
||||
// This is used to remove trailing zeros from the hex representation
|
||||
// We limit to 12 because we want to keep at least 1 hex digit (13 total - 12 = 1)
|
||||
// assert 0 <= trailingZeros && trailingZeros <= 12
|
||||
int trailingZeros = Long.numberOfTrailingZeros(signifBits | 1L << 4 * 12) >> 2;
|
||||
|
||||
// Determine the exponent value based on whether the number is subnormal or normal
|
||||
// Subnormal numbers use the minimum exponent, normal numbers use the actual exponent
|
||||
int exp = subnormal ? Double.MIN_EXPONENT : Math.getExponent(d);
|
||||
|
||||
// Calculate the total length of the resulting string:
|
||||
// Sign (optional) + prefix "0x" + implicit bit + "." + hex digits + "p" + exponent
|
||||
int charlen = (negative ? 1 : 0) // sign character
|
||||
+ 4 // "0x1." or "0x0."
|
||||
+ 13 - trailingZeros // hex digits (13 max, minus trailing zeros)
|
||||
+ 1 // "p"
|
||||
+ DecimalDigits.stringSize(exp) // exponent
|
||||
;
|
||||
|
||||
// Create a byte array to hold the result characters
|
||||
byte[] chars = new byte[charlen];
|
||||
int index = 0;
|
||||
|
||||
// Add the sign character if the number is negative
|
||||
if (negative) { // value is negative
|
||||
chars[index++] = '-';
|
||||
}
|
||||
|
||||
// Add the prefix and the implicit bit ('1' for normal, '0' for subnormal)
|
||||
// Subnormal values have a 0 implicit bit; normal values have a 1 implicit bit.
|
||||
chars[index ] = '0'; // Hex prefix
|
||||
chars[index + 1] = 'x'; // Hex prefix
|
||||
chars[index + 2] = (byte) (subnormal ? '0' : '1'); // Implicit bit
|
||||
chars[index + 3] = '.'; // Decimal point
|
||||
index += 4;
|
||||
|
||||
// Convert significand to hex digits manually to avoid creating temporary strings
|
||||
// Extract the 13 hex digits (52 bits) from signifBits
|
||||
// We need to extract bits 48-51, 44-47, ..., 0-3 (13 groups of 4 bits)
|
||||
for (int sh = 4 * 12, end = 4 * trailingZeros; sh >= end; sh -= 4) {
|
||||
// Extract 4 bits at a time from left to right
|
||||
// Shift right by sh positions and mask with 0xF
|
||||
// Integer.digits maps values 0-15 to '0'-'f' characters
|
||||
chars[index++] = Integer.digits[((int)(signifBits >> sh)) & 0xF];
|
||||
}
|
||||
|
||||
// Add the exponent indicator
|
||||
chars[index] = 'p';
|
||||
|
||||
// Append the exponent value to the character array
|
||||
// This method writes the decimal representation of exp directly into the byte array
|
||||
DecimalDigits.uncheckedGetCharsLatin1(exp, charlen, chars);
|
||||
|
||||
return String.newStringWithLatin1Bytes(chars);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, Alibaba Group Holding Limited. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -174,6 +175,26 @@ public class ToHexString {
|
||||
{"+4.9e-324", "0000000000000001"},
|
||||
{"-4.9e-324", "8000000000000001"},
|
||||
|
||||
// Test cases for trailing zeros in significand
|
||||
// These test the removal of trailing zeros in the hexadecimal representation
|
||||
// The comments indicate the number of trailing zeros removed from the significand
|
||||
// For "0x1.0p1", there are 13 trailing zeros in the significand, but only 12 are removed
|
||||
// as we always keep at least one hex digit in the significand
|
||||
{"0x1.0p1", "4000000000000000"}, // 12 trailing zeros removed (13 total, but only 12 removed)
|
||||
{"0x1.1p1", "4001000000000000"}, // 12 trailing zeros removed (all zeros after '1')
|
||||
{"0x1.01p1", "4000100000000000"}, // 11 trailing zeros removed
|
||||
{"0x1.001p1", "4000010000000000"}, // 10 trailing zeros removed
|
||||
{"0x1.0001p1", "4000001000000000"}, // 9 trailing zeros removed
|
||||
{"0x1.00001p1", "4000000100000000"}, // 8 trailing zeros removed
|
||||
{"0x1.000001p1", "4000000010000000"}, // 7 trailing zeros removed
|
||||
{"0x1.0000001p1", "4000000001000000"}, // 6 trailing zeros removed
|
||||
{"0x1.00000001p1", "4000000000100000"}, // 5 trailing zeros removed
|
||||
{"0x1.000000001p1", "4000000000010000"}, // 4 trailing zeros removed
|
||||
{"0x1.0000000001p1", "4000000000001000"}, // 3 trailing zeros removed
|
||||
{"0x1.00000000001p1", "4000000000000100"}, // 2 trailing zeros removed
|
||||
{"0x1.000000000001p1", "4000000000000010"}, // 1 trailing zero removed (minimum)
|
||||
{"0x1.0000000000001p1", "4000000000000001"}, // 0 trailing zeros removed (no trailing zeros to remove)
|
||||
|
||||
// fdlibm k_sin.c
|
||||
{"+5.00000000000000000000e-01", "3FE0000000000000"},
|
||||
{"-1.66666666666666324348e-01", "BFC5555555555549"},
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2025, Alibaba Group Holding Limited. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -47,7 +48,7 @@ import java.util.concurrent.TimeUnit;
|
||||
@Warmup(iterations = 10, time = 1)
|
||||
@Measurement(iterations = 5, time = 2)
|
||||
@Fork(3)
|
||||
public class FloatingDecimal {
|
||||
public class Doubles {
|
||||
|
||||
private double[] randomArray, twoDecimalsArray, integerArray;
|
||||
private static final int TESTSIZE = 1000;
|
||||
@ -65,6 +66,14 @@ public class FloatingDecimal {
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(TESTSIZE)
|
||||
public void toHexString(Blackhole bh) {
|
||||
for (double d : randomArray) {
|
||||
bh.consume(Double.toHexString(d));
|
||||
}
|
||||
}
|
||||
|
||||
/** Tests Double.toString on double values generated from Random.nextDouble() */
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(TESTSIZE)
|
||||
Loading…
x
Reference in New Issue
Block a user