Note: This website is archived. For up-to-date information about D projects and development, please visit wiki.dlang.org.

Ticket #55 (reopened defect)

Opened 11 years ago

Last modified 10 years ago

Real literal doesn't get correct values for semantic analysis

Reported by: asterite Assigned to:
Priority: major Component: descent.core
Version: 0.5 Keywords:
Cc:

Change History

01/30/09 10:51:38 changed by Don Clugston

I don't know what the original bug was about, but certainly it can't do hex format floating point literals. AFAICT it's OK for decimal literals.

double x = 0x1.34F6p+23;

In the compile-time view, this shows up as '0' -- it stops converting it once it hits the 'x'.

01/30/09 22:24:04 changed by asterite

This is fixed in 0.5.4.20090131

Can you verify it?

02/11/09 08:26:46 changed by Don Clugston

  • status changed from new to closed.
  • resolution set to fixed.

Confirmed fixed. Thanks!

02/11/09 15:19:23 changed by Don Clugston

  • status changed from closed to reopened.
  • resolution deleted.

Actually, I spoke too soon. It seems that only the 'double' (64-bit) portion of the hex literal is being used. In this example from std.math / tango.math.Math:

const real LOG2T = 0x1.a934f0979a3715fcp+1L;

the values after the "71" don't make any difference. The next larger value which is different is by changing the 5 to an 8.

const real LOG2T = 0x1.a934f0979a3715fcp+1L; const real LOG2X = 0x1.a934f0979a3718fcp+1L;

const real LOG2T = 3.321928094887362181708567732130177319049835205078125; const real LOG2X = 3.32192809488736262579777758219279348850250244140625;

Changing it to a 7 doesn't change anything.

This is actually pretty interesting. There far way more digits printed than are actually meaningful. In that case, why does Descent stop? So I investigated.

It's different in the decimal case.

const real E = 2.7182818284590452354L; const real E2 = E*E; const real E4 = E2*E2;

const real E = 2.7182818284590452354; const real E2 = 7.389056098930650227446327150543441; const real E4 = 54.59815003314423908130085104469785;

Notice how it saturates. At 34 digits, which is quadruple precision, and which is pretty bloody impressive. And if I change E2 to be E*E+1e-36, the displayed value of E2 doesn't change, but E4 does, so it's even better than that. So the only remaining issue is, that the later hex digits are being discarded. And when displaying the values in compile view, it definitely shouldn't be displaying more than 34 decimal digits.

(follow-up: ↓ 6 ) 02/12/09 15:21:06 changed by asterite

Nice analysis!

Descent behaves different from DMD because DMD uses the strtod function from C++, which is not present in Java. So... since I didn't want to parse that string by myself, I search for 'strtod Java' in Google but found nothing. Later I found out that Double.parseDouble understands that same syntax, but it has less precision than strtod...

So my solution was to parse it with that method and then create a BigDecimal? from that value (because it has more precision, but I guess it has waaay more precision than the datatype from C++).

Heres the code that parses the string. There are some checks because you can create a BigDecimal? directly from a string, but this constructor dosen't understand hexadecimal, neither hexadecimal exponents:

public static BigDecimal strtold(String string) {
	String base = string;
	
	boolean isNegative = base.length() >= 1 && base.charAt(0) == '-';
	if (isNegative) {
		base = base.substring(1);
	}
	
	boolean isHex = base.length() >= 2 &&
		base.charAt(0) == '0' && (base.charAt(1) == 'x' || base.charAt(1) == 'X');
	
	if (isHex) {
		int exponentIndex = base.indexOf('p');
		if (exponentIndex == -1) {
			exponentIndex = base.indexOf('P');
		}
		try {
			if (exponentIndex != -1) {
				return new BigDecimal(Double.parseDouble(string));
			} else {
				return new BigDecimal(Double.parseDouble(string + "p0"));
			}
		} catch (NumberFormatException e) {
			// XXX fix this, for really big exponents like:
			// 0x1.6a09e667f3bcc908p+16383
			// it is not working :-(
			return BigDecimal.ZERO;
		}
	} else {
		return new BigDecimal(string);
	}
}

Note that in the case of a string that isn't expressed in hexadecimal notation, the BigDecimal?(String) constructor is used, and it doesn't change the precision/representation of the value. But in the case of hexadecimal, it changes it. That also explains the amazing precision. :-)

I can see BigDecimal? has a constructor that accepts a scale, and you can also set it later. Maybe I can always use the same scale for every BigDecimal? constructed and in that way it will behave "correctly" (what's the scale to use in this case?).

I'm also getting some parse exceptions in Double.parseDouble for really big/small values, and in that case I don't know what to do at all.

(in reply to: ↑ 5 ) 03/11/09 13:22:28 changed by Don Clugston

Replying to asterite:

if (isHex) { int exponentIndex = base.indexOf('p'); if (exponentIndex == -1) { exponentIndex = base.indexOf('P'); } try { if (exponentIndex != -1) { return new BigDecimal?(Double.parseDouble(string)); } else { return new BigDecimal?(Double.parseDouble(string + "p0")); } } catch (NumberFormatException? e) { // XXX fix this, for really big exponents like: // 0x1.6a09e667f3bcc908p+16383 // it is not working :-( return BigDecimal?.ZERO; }

} else { return new BigDecimal?(string); } } }}} Note that in the case of a string that isn't expressed in hexadecimal notation, the BigDecimal?(String) constructor is used, and it doesn't change the precision/representation of the value. But in the case of hexadecimal, it changes it. That also explains the amazing precision. :-) I can see BigDecimal? has a constructor that accepts a scale, and you can also set it later. Maybe I can always use the same scale for every BigDecimal? constructed and in that way it will behave "correctly" (what's the scale to use in this case?). I'm also getting some parse exceptions in Double.parseDouble for really big/small values, and in that case I don't know what to do at all.

Rather than using parseDouble, you're probably better parsing the hexadecimal value. Strip off the "0x1." and the "p" to get mantissa_str and exponent_str. You should also strip off any embedded "_" in the strings, I'm guessing Java won't like them! Then the result will be:

BigDecimal?(BigInteger?(mantissa_str, 16))

  • pow(2, atoi(exponent_str) - (mantissa_str.length)*4)

(I don't know any Java (though I looked up the BigDecimal? class), that's D code!)

The pow(2,...) is too big to fit into a double. For precision, you should use the DECIMAL128 MathContext?, because that's what D should be using ... eventually!

Do that, and you have the ultimate solution! ("Supports D4.0 floating-point literals" :-) )

03/11/09 13:26:39 changed by Don Clugston

Repeating my comment, with proper formatting this time:

Replying to asterite:

Note that in the case of a string that isn't expressed in hexadecimal notation, the BigDecimal?(String) constructor is used, and it doesn't change the precision/representation of the value. But in the case of hexadecimal, it changes it. That also explains the amazing precision. :-) I can see BigDecimal? has a constructor that accepts a scale, and you can also set it later. Maybe I can always use the same scale for every BigDecimal? constructed and in that way it will behave "correctly" (what's the scale to use in this case?). I'm also getting some parse exceptions in Double.parseDouble for really big/small values, and in that case I don't know what to do at all.

Rather than using parseDouble, you're probably better parsing the hexadecimal value. Strip off the "0x1." and the "p" to get mantissa_str and exponent_str. You should also strip off any embedded "_" in the strings, I'm guessing Java won't like them! Then the result will be:

BigDecimal(BigInteger(mantissa_str, 16)) * pow(2, atoi(exponent_str) - (mantissa_str.length)*4)

(I don't know any Java (though I looked up the BigDecimal? class), that's D code!)

The pow(2,...) is too big to fit into a double. For precision, you should use the DECIMAL128 MathContext?, because that's what D should be using ... eventually!

Do that, and you have the ultimate solution! ("Supports D4.0 floating-point literals" :-) )

05/16/09 21:48:12 changed by asterite

I still don't know how to do it. I've followed your steps but here's a value I don't know how to deal with:

0x1.6a09e667f3bcc908p+16383L

(it appears in std.math)

Then it's mantissa * pow(2, 16383), and I don't know how to solve that pow with Java. It gives infinity with BigInteger?, and it doesn't work with BigDecimal?. That's a huge number. :-(