2

EDIT: Someone marked this question as a duplicate of the typical "Why does 0.1 + 0.3 return 0.40000000001?", but please notice I'm not asking why this happens; I'm fully aware of it. I just want to know if there is a workaround in cases like the following, when rounding isn't an option.


When writing the following noDigits function, that is supposed to return the number of digits of an integer n given a certain base,

#include <math.h>
using namespace std;

int noDigits(int n, int base = 10) {
    return (n != 0) ? int(floor(log(abs(n)) / log(base)) + 1) : 1;
}

I ran into the following precision problem: if I print out log(abs(1000))/log(10) I get 3, but when I apply floor to that (i.e. floor(log(abs(1000))/log(10))), I get 2.

I guess it is due to log(abs(1000))/log(10) being stored as something like 2.9999999[...], but I'm not sure how to solve it; there are cases in which log(abs(n))/log(base) will actually be something like 2.9987456[...], so rounding to a certain precision is not an option.

How can I overcome this?

Clarification: while there are many simple alternatives for solving this particular problem, I'm asking if there's a general workaround for working with floor (or int conversion)—which there doesn't seem to be.

Anakhand
  • 2,838
  • 1
  • 22
  • 50
  • 1
    You should completely rewrite your function without using log, or doubles. – user31264 Sep 15 '18 at 10:55
  • @user31264 Is there no way *at all* to achieve this with log? Mathematically it's much more elegant than using `%` and loops. But I guess computers have their limits... – Anakhand Sep 15 '18 at 10:57
  • 1
    @Anakhand while I agree that it's more elegant, one should keep in mind that some basic math principles do not apply to floating point arithmetic. For example, given a `float a, b, c`, `a * b * c` **is not necessarily the same** as `b * c * a`. It's just how it is. – Fureeish Sep 15 '18 at 11:00
  • Which compiler you are using? – Santosh Dhanawade Sep 15 '18 at 11:11
  • 2
    @RichardCritten Nah, they are already aware of the rounding issue and are asking for a workaround. – Baum mit Augen Sep 15 '18 at 11:14
  • @SantoshDhanawade `gcc 7.3.0` – Anakhand Sep 15 '18 at 11:20
  • 1
    The workaround is to not use floating point. Yes, it is less "elegant", in the sense of potentially requiring a loop rather than an expression involving function calls, but it gives a solution that is exact and unaffected by properties of floating point operations. – Peter Sep 15 '18 at 11:21
  • 1
    Perhaps a more productive starting point to discuss this would be to describe the problem you are trying to solve, and post the code to solve it using % and loops as you mentioned. It might be a better fit on the Code Review site then. – John Zwinck Sep 15 '18 at 11:21
  • @Anakhand I run same program in Visual studio and got '3' for floor function too. – Santosh Dhanawade Sep 15 '18 at 11:28
  • 1
    It is in fact a dupe of the cannonical. Once you understand the **why**, you will find ways to deal with the problem either directly or by using a completely different approach. – too honest for this site Sep 15 '18 at 11:31
  • 2
    The easiest to achieve your goal is to do maximum 64 integer division in base `b` and count when you hit zero. This will be much cheaper then computing two log, one abs and one floor. Why 64. That is the max number of digits you can have for base 2. The minimum is 1 for base 2^64 – kvantour Sep 15 '18 at 11:35
  • 1
    Why don't you just convert the `int` to a `string` and then find the length of the string? – ImaginaryHuman072889 Sep 15 '18 at 11:36
  • @ImaginaryHuman072889 That would be a perfectly valid solution to this *particular* problem, but it doesn't solve the general problem of applying `floor` or converting to `int` of a double that is intended to represent an int (e.g. `0.99999[...]`). – Anakhand Sep 15 '18 at 11:39
  • 3
    @Anakhand In general, there isn't really a good way to deal with this. `float` and `double` types are more intended to represent a *very close* representation of a number along a continuous spectrum. They aren't very reliable for dealing with *exactness*. This is why some other data types have been created to deal with exact numbers that have decimal places (e.g. the `decimal` type is typically used to represent money because it is an exact way to represent decimal numbers.) – ImaginaryHuman072889 Sep 15 '18 at 11:48
  • 2
    @Anakhand: A `double` is not guaranteed to have the same precision as an `int` anyway. If you want integers, use them from the start. In general it is a good idea to keep integers and floats seperated. This smells more and more like an XY problem. Please read the linked documents at the answers of the dupe before continuing. – too honest for this site Sep 15 '18 at 12:01
  • 1
    Also if you really want to keep this approach, you can, instead of returning the number directly, store it as `m` and do the following return `return pow(base,m)>abs(n)?m:m+1`. But again you introduce more computational overload. – kvantour Sep 15 '18 at 12:09
  • 1
    regarding the "workaround" there are already tons of questions about it that you can find in the linked question list of the duplicate question. The short answer is NO, you have to use a decimal floating-point type if you really need it – phuclv Sep 15 '18 at 12:54
  • The classic paper "What every computer scientist should know about floating point arithmetic" is something that anyone using float/double should read. https://ece.uwaterloo.ca/~dwharder/NumericalAnalysis/02Numerics/Double/paper.pdf – Damian Dixon Sep 15 '18 at 13:35
  • I think that the workaround you are searching (if you aren't considering the integer only solutions) is to rewrite the formula in order to minimize the numerical errors. Given that the errors more likely happen when n = base^m, you should consider those cases as special ones. See e.g. https://wandbox.org/permlink/7U6QKvnXVQdimFtl – Bob__ Sep 15 '18 at 15:22

0 Answers0