Skip to content

Commit 153ce3f

Browse files
authored
Merge pull request RustPython#1581 from Writtic/writtic/improve_fromhex
Improve float.fromhex module representation case
2 parents 52f1965 + 6a2c721 commit 153ce3f

File tree

2 files changed

+231
-32
lines changed

2 files changed

+231
-32
lines changed

tests/snippets/floats.py

Lines changed: 192 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
from testutils import assert_raises
44

5+
NAN = float('nan')
6+
INF = float('inf')
7+
NINF = float('-inf')
8+
59
1 + 1.1
610

711
a = 1.2
@@ -240,21 +244,194 @@
240244
with assert_raises(SyntaxError):
241245
exec(src)
242246

243-
assert float.fromhex('0.0') == 0.0
244-
assert float.fromhex('-0.0') == 0.0
245-
assert float.fromhex('0x0p0') == 0.0
246-
assert float.fromhex('0x010p0') == 16.0
247-
assert float.fromhex('0x0.10p3') == 0.5
248-
assert float.fromhex('-0x0p0') == 0.0
249-
assert float.fromhex('0x0.p0') == 0.0
250-
assert float.fromhex('-0x0.p0') == 0.0
251-
assert float.fromhex('0x0.0p+0') == 0.0
252-
assert float.fromhex('-0x0.0p+0') == -0.0
253-
assert float.fromhex('0x1.000000p+0') == 1.0
254-
assert float.fromhex('-0x1.800000p+0') == -1.5
255-
assert float.fromhex('inf') == float('inf')
256-
assert math.isnan(float.fromhex('nan'))
257-
assert_raises(ValueError, lambda: float.fromhex('error'))
247+
fromHex = float.fromhex
248+
def identical(x, y):
249+
if math.isnan(x) or math.isnan(y):
250+
if math.isnan(x) == math.isnan(y):
251+
return
252+
elif x == y and (x != 0.0 or math.copysign(1.0, x) == math.copysign(1.0, y)):
253+
return
254+
raise SyntaxError(f"{x} not identical to {y}")
255+
256+
invalid_inputs = [
257+
"infi", # misspelt infinities and nans
258+
"-Infinit",
259+
"++inf",
260+
"-+Inf",
261+
"--nan",
262+
"+-NaN",
263+
"snan",
264+
"NaNs",
265+
"nna",
266+
"an",
267+
"nf",
268+
"nfinity",
269+
"inity",
270+
"iinity",
271+
"0xnan",
272+
"",
273+
" ",
274+
"x1.0p0",
275+
"0xX1.0p0",
276+
"+ 0x1.0p0", # internal whitespace
277+
"- 0x1.0p0",
278+
"0 x1.0p0",
279+
"0x 1.0p0",
280+
"0x1 2.0p0",
281+
"+0x1 .0p0",
282+
"0x1. 0p0",
283+
"-0x1.0 1p0",
284+
"-0x1.0 p0",
285+
"+0x1.0p +0",
286+
"0x1.0p -0",
287+
"0x1.0p 0",
288+
"+0x1.0p+ 0",
289+
"-0x1.0p- 0",
290+
"++0x1.0p-0", # double signs
291+
"--0x1.0p0",
292+
"+-0x1.0p+0",
293+
"-+0x1.0p0",
294+
"0x1.0p++0",
295+
"+0x1.0p+-0",
296+
"-0x1.0p-+0",
297+
"0x1.0p--0",
298+
"0x1.0.p0",
299+
"0x.p0", # no hex digits before or after point
300+
"0x1,p0", # wrong decimal point character
301+
"0x1pa",
302+
"0x1p\uff10", # fullwidth Unicode digits
303+
"\uff10x1p0",
304+
"0x\uff11p0",
305+
"0x1.\uff10p0",
306+
"0x1p0 \n 0x2p0",
307+
"0x1p0\0 0x1p0", # embedded null byte is not end of string
308+
]
309+
310+
for x in invalid_inputs:
311+
assert_raises(ValueError, lambda: fromHex(x))
312+
313+
value_pairs = [
314+
("inf", INF),
315+
("-Infinity", -INF),
316+
("NaN", NAN),
317+
("1.0", 1.0),
318+
("-0x.2", -0.125),
319+
("-0.0", -0.0),
320+
]
321+
whitespace = [
322+
"",
323+
" ",
324+
"\t",
325+
"\n",
326+
"\n \t",
327+
"\f",
328+
"\v",
329+
"\r"
330+
]
331+
332+
for inp, expected in value_pairs:
333+
for lead in whitespace:
334+
for trail in whitespace:
335+
got = fromHex(lead + inp + trail)
336+
identical(got, expected)
337+
338+
MAX = fromHex('0x.fffffffffffff8p+1024') # max normal
339+
MIN = fromHex('0x1p-1022') # min normal
340+
TINY = fromHex('0x0.0000000000001p-1022') # min subnormal
341+
EPS = fromHex('0x0.0000000000001p0') # diff between 1.0 and next float up
342+
343+
# two spellings of infinity, with optional signs; case-insensitive
344+
identical(fromHex('inf'), INF)
345+
identical(fromHex('+Inf'), INF)
346+
identical(fromHex('-INF'), -INF)
347+
identical(fromHex('iNf'), INF)
348+
identical(fromHex('Infinity'), INF)
349+
identical(fromHex('+INFINITY'), INF)
350+
identical(fromHex('-infinity'), -INF)
351+
identical(fromHex('-iNFiNitY'), -INF)
352+
353+
# nans with optional sign; case insensitive
354+
identical(fromHex('nan'), NAN)
355+
identical(fromHex('+NaN'), NAN)
356+
identical(fromHex('-NaN'), NAN)
357+
identical(fromHex('-nAN'), NAN)
358+
359+
# variations in input format
360+
identical(fromHex('1'), 1.0)
361+
identical(fromHex('+1'), 1.0)
362+
identical(fromHex('1.'), 1.0)
363+
identical(fromHex('1.0'), 1.0)
364+
identical(fromHex('1.0p0'), 1.0)
365+
identical(fromHex('01'), 1.0)
366+
identical(fromHex('01.'), 1.0)
367+
identical(fromHex('0x1'), 1.0)
368+
identical(fromHex('0x1.'), 1.0)
369+
identical(fromHex('0x1.0'), 1.0)
370+
identical(fromHex('+0x1.0'), 1.0)
371+
identical(fromHex('0x1p0'), 1.0)
372+
identical(fromHex('0X1p0'), 1.0)
373+
identical(fromHex('0X1P0'), 1.0)
374+
identical(fromHex('0x1P0'), 1.0)
375+
identical(fromHex('0x1.p0'), 1.0)
376+
identical(fromHex('0x1.0p0'), 1.0)
377+
identical(fromHex('0x.1p4'), 1.0)
378+
identical(fromHex('0x.1p04'), 1.0)
379+
identical(fromHex('0x.1p004'), 1.0)
380+
identical(fromHex('0x1p+0'), 1.0)
381+
identical(fromHex('0x1P-0'), 1.0)
382+
identical(fromHex('+0x1p0'), 1.0)
383+
identical(fromHex('0x01p0'), 1.0)
384+
identical(fromHex('0x1p00'), 1.0)
385+
identical(fromHex(' 0x1p0 '), 1.0)
386+
identical(fromHex('\n 0x1p0'), 1.0)
387+
identical(fromHex('0x1p0 \t'), 1.0)
388+
identical(fromHex('0xap0'), 10.0)
389+
identical(fromHex('0xAp0'), 10.0)
390+
identical(fromHex('0xaP0'), 10.0)
391+
identical(fromHex('0xAP0'), 10.0)
392+
identical(fromHex('0xbep0'), 190.0)
393+
identical(fromHex('0xBep0'), 190.0)
394+
identical(fromHex('0xbEp0'), 190.0)
395+
identical(fromHex('0XBE0P-4'), 190.0)
396+
identical(fromHex('0xBEp0'), 190.0)
397+
identical(fromHex('0xB.Ep4'), 190.0)
398+
identical(fromHex('0x.BEp8'), 190.0)
399+
identical(fromHex('0x.0BEp12'), 190.0)
400+
401+
# moving the point around
402+
pi = fromHex('0x1.921fb54442d18p1')
403+
identical(fromHex('0x.006487ed5110b46p11'), pi)
404+
identical(fromHex('0x.00c90fdaa22168cp10'), pi)
405+
identical(fromHex('0x.01921fb54442d18p9'), pi)
406+
identical(fromHex('0x.03243f6a8885a3p8'), pi)
407+
identical(fromHex('0x.06487ed5110b46p7'), pi)
408+
identical(fromHex('0x.0c90fdaa22168cp6'), pi)
409+
identical(fromHex('0x.1921fb54442d18p5'), pi)
410+
identical(fromHex('0x.3243f6a8885a3p4'), pi)
411+
identical(fromHex('0x.6487ed5110b46p3'), pi)
412+
identical(fromHex('0x.c90fdaa22168cp2'), pi)
413+
identical(fromHex('0x1.921fb54442d18p1'), pi)
414+
identical(fromHex('0x3.243f6a8885a3p0'), pi)
415+
identical(fromHex('0x6.487ed5110b46p-1'), pi)
416+
identical(fromHex('0xc.90fdaa22168cp-2'), pi)
417+
identical(fromHex('0x19.21fb54442d18p-3'), pi)
418+
identical(fromHex('0x32.43f6a8885a3p-4'), pi)
419+
identical(fromHex('0x64.87ed5110b46p-5'), pi)
420+
identical(fromHex('0xc9.0fdaa22168cp-6'), pi)
421+
identical(fromHex('0x192.1fb54442d18p-7'), pi)
422+
identical(fromHex('0x324.3f6a8885a3p-8'), pi)
423+
identical(fromHex('0x648.7ed5110b46p-9'), pi)
424+
identical(fromHex('0xc90.fdaa22168cp-10'), pi)
425+
identical(fromHex('0x1921.fb54442d18p-11'), pi)
426+
identical(fromHex('0x1921fb54442d1.8p-47'), pi)
427+
identical(fromHex('0x3243f6a8885a3p-48'), pi)
428+
identical(fromHex('0x6487ed5110b46p-49'), pi)
429+
identical(fromHex('0xc90fdaa22168cp-50'), pi)
430+
identical(fromHex('0x1921fb54442d18p-51'), pi)
431+
identical(fromHex('0x3243f6a8885a30p-52'), pi)
432+
identical(fromHex('0x6487ed5110b460p-53'), pi)
433+
identical(fromHex('0xc90fdaa22168c0p-54'), pi)
434+
identical(fromHex('0x1921fb54442d180p-55'), pi)
258435

259436
assert (0.0).hex() == '0x0.0p+0'
260437
assert (-0.0).hex() == '-0x0.0p+0'
@@ -264,10 +441,6 @@
264441
assert float('-inf').hex() == '-inf'
265442
assert float('nan').hex() == 'nan'
266443

267-
#for _ in range(10000):
268-
# f = random.random() * random.randint(0, 0x10000000000000000)
269-
# assert f == float.fromhex(f.hex())
270-
271444
# Test float exponent:
272445
assert 1 if 1else 0 == 1
273446

vm/src/obj/objfloat.rs

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -583,30 +583,56 @@ impl PyFloat {
583583

584584
#[pymethod]
585585
fn fromhex(repr: PyStringRef, vm: &VirtualMachine) -> PyResult<f64> {
586-
hexf_parse::parse_hexf64(repr.as_str(), false)
587-
.or_else(|_| repr.as_str().parse::<f64>())
588-
.or_else(|_| match repr.as_str() {
586+
hexf_parse::parse_hexf64(repr.as_str().trim(), false).or_else(|_| {
587+
match repr.as_str().to_lowercase().trim() {
589588
"nan" => Ok(std::f64::NAN),
589+
"+nan" => Ok(std::f64::NAN),
590+
"-nan" => Ok(std::f64::NAN),
590591
"inf" => Ok(std::f64::INFINITY),
592+
"infinity" => Ok(std::f64::INFINITY),
593+
"+inf" => Ok(std::f64::INFINITY),
594+
"+infinity" => Ok(std::f64::INFINITY),
591595
"-inf" => Ok(std::f64::NEG_INFINITY),
596+
"-infinity" => Ok(std::f64::NEG_INFINITY),
592597
value => {
593598
let mut hex = String::new();
594-
if value.contains("0x") {
595-
for ch in value.chars() {
596-
if ch == 'p' {
597-
hex.push_str(".p");
598-
} else {
599-
hex.push(ch);
600-
}
599+
let has_0x = value.contains("0x");
600+
let has_p = value.contains('p');
601+
let has_dot = value.contains('.');
602+
let mut start = 0;
603+
604+
if !has_0x && value.starts_with('-') {
605+
hex.push_str("-0x");
606+
start += 1;
607+
} else if !has_0x {
608+
hex.push_str("0x");
609+
if value.starts_with('+') {
610+
start += 1;
601611
}
602-
} else {
603-
hex = value.to_string();
604612
}
613+
614+
for (index, ch) in value.chars().enumerate() {
615+
if ch == 'p' && has_dot {
616+
hex.push_str("p");
617+
} else if ch == 'p' && !has_dot {
618+
hex.push_str(".p");
619+
} else if index >= start {
620+
hex.push(ch);
621+
}
622+
}
623+
624+
if !has_p && has_dot {
625+
hex.push_str("p0");
626+
} else if !has_p && !has_dot {
627+
hex.push_str(".p0")
628+
}
629+
605630
hexf_parse::parse_hexf64(hex.as_str(), false).map_err(|_| {
606631
vm.new_value_error("invalid hexadecimal floating-point string".to_string())
607632
})
608633
}
609-
})
634+
}
635+
})
610636
}
611637

612638
#[pymethod]

0 commit comments

Comments
 (0)