Scientific notation in Base-16

Input a scientific notation number (base 10), output scientific notation in base 16 (as defined below).

Details

In scientific notation, all non-zero numbers are written in the form

$$m \times 10^n$$

Where $$\ n \$$ is an integer, and $$\ m \$$ is a real number, $$\ 1 \leq |m| < 10 \$$.

Consider scientific notation in base 16.

$$m \times 10^n = m' \times 16^{n'}$$

$$\ n' \$$ is an integer, and $$\ m' \$$ is a real number where $$\ 1 \leq |m'| < 16 \$$.

Input / Output

Input a positive real number. You may also choice to input $$\m\$$, and, $$\n\$$ separately. For all testcase, -100 < n < 100.

Output the number in hexadecimal scientific notation. Could be a single string or two strings. Number $$\m\$$, and, $$\n\$$ should also be formatted as hexadecimal strings.

Output as 1.2E3E4 is not allowed due to ambiguous. (1.2E3×104, or 1.2×103E4) You have to use other notations. For example, 1.2E3E+4, 1.2E3, 4, 1.2E3&4, 1.2e3E4, 1.2E3e4, 1.2E3P4, 1.2E3⏨4, 1.2E3*^4 are all acceptable.

Testcases

m, n -> m', n'
1.6, 1 -> 1, 1
6.25, -2 -> 1, -1
1.0, 1 -> A, 0
1.234567, 89 -> f.83e0c1c37ba7, 49
1, -99 -> 8.bfbea76c619f, -53


You output may be slightly different from given testcase due to floating point errors. But you should keep at least 4 hex digits precision, and $$\1 \leq m' < 16\$$.

Rule

This is code golf. Shortest codes in each languages win.

• @Noodle9 In 1.2E3E4, the digit E and the exponential separator have the same case. Jun 26, 2020 at 12:57
• @Noodle9 The E in 1.2E3 is not an exponent, it is hexadecimal 14. So in the examples n' is not 1.2*10^3 (1200), it is 1 + 2/16 + 14/16*16 + 3/16*16*16 (approximately 1.1804 decimal). Jun 26, 2020 at 21:28
• @2012rcampion Ahhhhh! Now it makes total sense - thanks! :D Jun 26, 2020 at 21:31
• Be good to add a negative testcase. Jun 26, 2020 at 22:34
• Since there are already some answers assume positive numbers. I'm going to restrict the input as positive integers. Sorry for any changes. I had updated the description which only require to handle positive numbers.
– tsh
Jun 27, 2020 at 3:10

JavaScript (ES6),  134 124  121 bytes

Expects a float and returns an array of 2 strings.

n=>([x,y]=(+(g=n=>s=n.toString(16))(n).replace(r=/[1-f]/g,1)).toExponential().splite,[x.replace(r,_=>r.exec(s)),g(+y)])


Try it online!

How?

We first convert the input to hexadecimal and save the result in the variable s.

For instance, 7.257672195146994e93 is turned into:

"deadbeef0000000000000000000000000000000000000000000000000000000000000000000000"


We replace all non-zero hexadecimal digits with 1's:

"111111110000000000000000000000000000000000000000000000000000000000000000000000"


We coerce this back to an integer and invoke the .toExponential() method:

"1.1111111e+77"


We split this string into x = "1.1111111" and y = "+77".

We replace all 1's in x with the non-zero hexadecimal digits of s in order of appearance:

"d.eadbeef"


Finally, we convert y to hexadecimal:

"4d"


Below is another example with 6e-19:

"0.000000000000000b116b7de48f008"
"0.00000000000000011111111111001"
"1.1111111111001e-16"
[ "1.1111111111001", "-16" ]
[ "b.116b7de48f008", "-10" ]


Commented

n => (                       // n = input
[x, y] =                   // x = mantissa, y = exponent
(                        //
+(                     // coerce to integer:
g = n =>             //   g is a helper function converting its input ...
s = n.toString(16) //     ... to a hexadecimal string saved in s
)(n)                   //   invoke g with n
.replace(              //   replace:
r = /[1-f]/g,        //     r = regular expression to match the non-zero
//         hexa digits
1                    //     replace all of them with 1's
)                      //   end of replace()
)                        //
.toExponential()         // convert to exponential notation
.splite,               // split into [ x, y ] = [ mantissa, exponent ]
[                          // output array:
x.replace(               //   replace in x:
r,                     //     use r a 2nd time to match the 1's
_ => r.exec(s)         //     use r a 3rd time to get the next hexa digit
//     from s, this time taking advantage of the
//     stateful nature of RegExp
),                       //   end of replace()
g(+y)                    //   convert y to hexadecimal
]                          // end of output array
)                            //


JavaScript (ES6), 217 bytes

f=
n=>/^-?0\./.test(n=n.toString(16))?n.replace(/^(-?)0(.0*)(.)(.*)/,(_,s,z,d,t)=>s+d+'.'+t+'e-'+z.length.toString(16)):n=n.replace(/(-?.)(\w*).?(.*)/,(_,s,d,t)=>s+'.'+d+t+'e='+d.length.toString(16)).replace(/0*e=/,"e+")
<input type=number step=any oninput=o.textContent=f(+this.value)><pre id=o>

Output format is -?[1-f]\.([0-f]*[1-f])?e[+-][1-f][0-f]*.

Perl 5, 181 bytes

sub f{($e,$x)=(0,10**pop()*pop);$x/=16,$e++while$x>=16;$x*=16,$e--while$x<1;join('',map{sprintf$_?'%x':'%x.',$x%16,$x-=$x%16,$x*=16}0..12)=~s,\.?0*$,,r,sprintf$e<0?'-%x':'%x',abs$e}


Try it online!

I think using sprintf('%a',$x) could make the answer much shorter, just not sure how. Ungolfed: sub f { my($m, $n) = @_; my($e, $x) = (0,$m * 10**$n);$x/=16, $e++ while$x >= 16;
$x*=16,$e-- while $x < 1; return ( join('',map{sprintf$_?'%x':'%x.',$x%16,$x-=$x%16,$x*=16}0..12) =~ s,\.?0*$,,r, sprintf($e<0?'-%x':'%x',abs$e) ) }  Test: for my$test (map[/-?[\da-f\.]+/gi],split/\n/,<<''){
1.6, 1                -> 1, 1
6.25, -2              -> 1, -1
1.0, 1                -> a, 0
1.234567, 89          -> f.83e0c1c37ba7, 49
1, -99                -> 8.bfbea76c619f, -53

my($m,$n,$Mexp,$Nexp)=@$test; my($Mgot,$Ngot)=f($m,$n); my$testname = sprintf"  %-25s -->  %s", "$m,$n", "$Mexp,$Nexp";
is("$Mgot,$Ngot", "$Mexp,$Nexp", \$testname);
}


Output:

ok 1 -   1.6, 1                    -->  1, 1
ok 2 -   6.25, -2                  -->  1, -1
ok 3 -   1.0, 1                    -->  a, 0
ok 4 -   7.257672195146994, 93     -->  d.eadbeef, 4d
ok 5 -   1.234567, 89              -->  f.83e0c1c37ba7, 49
ok 6 -   1, -99                    -->  8.bfbea76c619f, -53


C(GCC), 32 bit float, 133129 128 bytes

-4 bytes ceilingcat

m;e;s(float f){m=*(int*)&f;e=(m>>23)-127;m=(m&-1U>>9|1<<23)>>3-(e&3);printf("%x.%05xE%c%x",m>>20,m&-1U>>12,"+-"[e<0],abs(e/4));}


Try it online!

This extracts the exponent and mantissa from a floating point number. Since the maximum exponent is +/-127 in base 2 (approx 38 base 10), this doesn't quite meet the challenge since it fails on larger exponents. So...

C(GCC), 64 bit float, 167163 147 bytes

-4 bytes ceilingcat

long m;e;s(double f){m=*(long*)&f;e=(m>>52)-1023;m=(m&-1UL>>12|1L<<52)>>3-(e&3);printf("%lx.%013lxE%c%x",m>>49,(m&-1UL>>15)*8,"+-"[e<0],abs(e/4));}


Try it online!

• Suggest (~e&3) instead of 3-(e&3) Jul 11, 2020 at 5:12

R, 192 bytes

function(x,i=function(x,p=F,y=abs(x))if(y>0,{d=c(0:9,letters[1:6])[rev(y%/%(16^(0:log(y,16)))%%16+1)]
c("-"[x<0],d[1],"."[p],d[-1])},0))cat(i(x*16^(3-(n=log(x^2,16)%/%2)),T)," ",i(n),sep="")


Try it online!

Happily handles negative numbers, even if now not required.

Commented:

base16float==function(x,
i=function(x,point=FALSE,y=abs(x))              # create function to write hexadecimal integers
if(x==0,0,                                    # if x is zero, just write zero
{d=l[rev(y%/%(16^(0:log(y,16)))%%16+1)]     # otherwise get the digits for each power-of-16
c("-"[x<0],d[1],"."[p],d[-1])})         # and paste them together with the sign
)                                               # (and with a "." after the first digit if
# specified by point=TRUE in the function call)
cat(i(x*16^(3-(n=log(abs(x),16)%/%1)),T),       # so: first output the mantissa as a 4-digit integer
# with point=TRUE to include the dot,
" ",                                        # leave a gap,
i(n),                                       # and write the exponent
sep="")


Python 3.8, 284 bytes

struct approach. At least I tried)

from struct import*
m=lambda s,c,n,k:[s[n:],'-'+s[k:]][c]
n=lambda s:m(s,s[0]=='-',2,3)
o=lambda s,x:m(s,x<0,0,0)
p=lambda x:x[2]+'.'+x[3:]
s=lambda x,y,d=2**52:(o(p(hex((x%d+d)*2**((x//d+1)%4)).rstrip('0')),y),n(hex(((x//d)%2048-1023)//4)))
f=lambda x:s(unpack('Q',pack('d',x))[0],x)


Try it online!

Ruby, 62 61 bytes

->x{'%x.%x,%+x'%[m=x/16**n=Math.log(x,16).floor,m%1*16**9,n]}


Try it online!

Input is a (decimal) float. Output is in the form $$\m',n'\$$, where $$\m'\$$ has a maximum precision of 10 hex digits and $$\n'\$$ is always signed.

'%x.%x,%+x' is a shorthand form of sprintf syntax, which creates formatted strings for numeric output. The format specifier x converts its argument to hexadecimal and the + guarantees signed output (otherwise negative numbers would be output with two leading dots representing an infinite string of leading ffs). We do three conversions to hex: (i) the integral part of $$\m'\$$, (ii) the fractional part of $$\m'\$$ (m%1; multiplying by 16**9 is necessary because the fractional part is ignored by sprintf), and (iii) $$\n'\$$.

The solution makes use of some straightforward mathematical transformations. Let $$\m'=16^{m''}\$$, so that $$\x\equiv m10^n=m'16^{n'}=16^{m''+n'}\equiv 16^y\$$. Then $$\y=\log_{16}x\$$. We are told that $$\n'\$$ is an integer, hence we take $$\n'=\lfloor y\rfloor\$$. This is the only choice of $$\n'\$$ for which $$\0\le m''=y-n'<1\$$, and hence the only choice of $$\n'\$$ for which $$\1\le 16^{m''}=m' < 16\$$ as required.

To support negative inputs, add .abs in two places and another + in the format string, bringing the code to 70 bytes:

->x{'%+x.%x,%+x'%[m=x/16**n=Math.log(x.abs,16).floor,m.abs%1*16**9,n]}


Python 3, 135 $$\\cdots\$$ 119 105 bytes

def f(x):m,n=x.hex().split('p');m=hex(int('1'+m[4:],16)<<int(n)%4);return m[2]+'.'+m[3:],f'{int(n)//4:x}'


Try it online!

Inputs a float.
Returns a tuple of strings $$\(m',n')\$$.