minus sign
void print_integer(int i_val)
You accept negative quantities, but there's
no logic for outputting a minus sign.
Consider making the parameter unsigned.
Consider spelling out its range, e.g. with std::uint64_t.
buffer overrun
The quantity i_copy = i_val % 10 will be positive,
even if i_val is negative.
The assignment i_val -= i_copy is intended to
drive i_val toward zero, but for a negative i_val
we would want a negative i_copy there.
So instead of going to zero we go toward
negative infinity, suffer wraparound at INT64_MIN,
and go merrily on to compute a quantity having
little relation to the serialized decimal form.
An intended loop invariant was that counter - index
shall always be non-negative.
But a negative i_val will very likely drive
that difference into the negatives,
at which point we're trashing whatever was
allocated before our mem buffer.
dynamic allocation
void print_integer(int i_val)
    ...
    char *mem = new char[counter+1]{0};
The code seems to go to too much trouble to
save a few bytes of buffer space.
I know, we're evaluating this function for its
side effects on stdout.
But imagine the caller wants a string buffer that
it can further manipulate prior to output.
Then I would expect to see some sort of max width
constant, and caller is responsible for allocating
a buffer of that size and passing it in.
For example, in a 64-bit setting it might be \$21\$,
since \$2^{64} = 18446744073709551616\$ is twenty
characters, plus one for NUL terminator.
In the OP code, with its current signature, I would
rather see us declare and init such a fixed size buffer
up front, and start filling it from the back.
Then after conversion, do an overlapping memmove()
to move the decimal result to the front of the buffer.
log zero
Computing log10(0.) is notoriously
troublesome, not unlike dividing by zero.
Your while(i_copy / 10) condition is
initially False when caller passes in 0,
so the counter value is effectively
claiming the logarithm is zero, which
is trouble.
Now, the allocation turns out to be fine.
But as @Fe2O3 helpfully points out,
that's only because the
while(i_val) condition is initially False
when caller passes in 0.
So we neglect to put an ASCII "0"
in the mem buffer.
That's the sort of thing I alluded to
when I described the importance of testing
lots of fiddly corner cases.
automated tests
The OP submission contains no unit tests.
This codebase would benefit from
an automated test suite.
logarithm
best way i could manage to do it without knowing decimal size in front.
You could call std::log10((double)i_val).
But I get the sense you have a requirement
that says "don't do any FP math". Ok, fine.
Let's further suppose you don't want to use memmove()
to move the result into place, or use a pair of 21-byte buffers.
That is, you want to know the number of digits
prior to serializing any decimal digits.
You can use std::countl_zero() to count leading zero bits
in the integer's binary representation.
Then for an input \$N\$ you essentially have \$\log_2 N\$.
At that point you'd be faced with dividing
by \$\log_2 10 \approx 3.3219\$ to find \$\log_{10} N\$,
which is a sticky wicket if FP is ruled out.
You would need to pick an input range (uint32_t ?),
pick a ratio such as (33219, 10000),
and carefully test that the integer roundoffs
give the same answer that rounding a floating point
result would give.
Better to save that effort, and do the memmove().
GCC offers
non-portable alternatives that will Count Leading Zeros, such as
- __builtin_clz()
- __builtin_clzl()
- __builtin_clzll()
- __builtin_clzimax()
- __builtin_clzg()
There is __builtin_log10() but it doesn't seem to accept integers
Right, the signature is: it accepts a double
and produces a double result.
and casting it back and forth from double messes up precision and warning about casts.
Sorry, I didn't follow that one.
Maybe pursue it as a separate question,
since the details of your program
and your target environment will really matter.
A double
takes a 64-bit allocation, and has 53 bits of
significand.
So by the pigeon hole principle,
there are clearly 64-bit integers which
cannot be exactly represented as a double.
For "large" doubles, there's quite a few
integers skipped between one double and
the immediately following double.
However, that should matter little for your code.
All you care about is "number of decimal
digits", so you're going to take ceil()
of that logarithm result.
Precision isn't very interesting in that setting.
Worrying about "off by one" bugs,
testing the corner cases,
is an interesting problem that you'll need
to tackle.
     
    
int i_copy = test;, I think you meant to assign the input parameter, withi_copy = i_val? If this is a typo, there is still time (item 6) to edit the original Question, since no one has yet posted an Answer. \$\endgroup\$__builtin_operator_newand__builtin_operator_deletefrom here any thoughts on that? \$\endgroup\$