0

I am trying to convert an arbitrary string (whereby the decimal point is explicitly manipulated controlled) to a number (i.e. floating point) using string-to-number and I am running into either a rounding issue or some kind of precision issue when Emacs performs the conversion. I am not a floating point expert so my exact terminology may be slightly incorrect, please pardon me.

The problem is interment based on the value (leading me to believe it's one of the aforementioned issues) provided, however I have an example reproducible case:

Example code:

(string-to-number "8807240867.266221")
;; Expected: 8807240867.266221
;; Actual:   8807240867.26622

Standard environment disclosure:

  • OS: MacOS
  • Chip: Intel
  • Emacs Version: 27.2.1
  • Port: Mitsuharu Yamamoto / Railwaycat
  • Other Notes: - No special distributions / etc.
  • Relevant Common Packages Used:
    • Standard internal Emacs packages

The trailing 1 is removed when I would not expect it to be. I have searched the C-code for how string-to-number works, however I am quite limited in my C knowledge so my best guess is that somehow the value is overflowing somewhere down the line? However, I don't receive an error message of any kind so that is what makes me think it's some kind of rounding/precision problem instead.

Any help is appreciated in explaining why this kind of issue is occurring, what exact issue is occurring, how to prevent it/properly fix it so that no digits are unexpectedly chopped off.

shynur
  • 4,065
  • 1
  • 3
  • 23
David
  • 127
  • 7
  • "*On modern platforms, floating-point operations follow the **IEEE-754** standard closely; however, results are not always rounded correctly on some obsolescent platforms, notably 32-bit x86.*" – shynur Jun 15 '23 at 02:53
  • @shynur I haven't dug into the IEEE-754 standard but macOS has been a 64 bit OS for years now and AFAIK it's been even longer since Apple sold 32-bit Macs. I assume that is what you're getting at but I'm not sure. Could you elaborate more? – David Jun 15 '23 at 03:02
  • Similar questions [here](https://emacs.stackexchange.com/questions/77617/how-to-round-on-2-decimals-in-org-mode-table-using-elisp) and [here](https://emacs.stackexchange.com/questions/44457/floating-point-addition) - also the references in the latter. – NickD Jun 15 '23 at 03:03
  • 1
    No, it has nothing to do with incorrect rounding on some old platforms. What you are seeing is an inherent problem in floating-point numbers: they use a finite number of bits, so they are inherently discrete - they *cannot* represent continuous values (i.e real numbers) exactly. In the most common representation on 64-bit platforms, a floating-point number is represented in 64 bits: 53 bits of `significand` and 11 bits of `exponent`. That's equivalent to a precision of just under 16 decimal digits (so your number is *just* out of that range). Try `(= 8807240867.266221 8807240867.26622)`. – NickD Jun 15 '23 at 03:21
  • Yeah, that makes sense. I stumbled onto the 16 digit limit (which I figured was because a digit takes 4 bits, ergo 16 x 4 = 64 bits). Correct, I did notice that if basically changed any digit, the result was what I expected. I assumed that 16 digits was the maximum, but that is not true, right? However, to bottom line it, what we're saying is that 15 digits is the maximum number of digits any given float can have its precision maintained while 16 digits _could_ start slipping in certain cases. Correct? – David Jun 15 '23 at 03:30
  • 1
    Yes, I think that is right. You could use quad-precision (or higher - see [IEEE754](https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats) for some details of various representations). – NickD Jun 15 '23 at 03:44
  • "*Could you elaborate more?*" --- The point is IEEE-754, and as you can see, I bolded it in my last comment. – shynur Jun 15 '23 at 06:01
  • Higher precision might be helpful (as @NickD pointed out), or might not. After all, binary will never be decimal, just as decimal can never precisely represent the ternary 0.1 with a finite number of digits. (1/3 = 0.333 ...) Moreover, getting Emacs to use quad-precision float can be troublesome. Therefore, *I suggest you let go of this issue and stop worrying about it.* Or, you use fixed point numbers. All in all, there is no built-in type that fits your needs. – shynur Jun 15 '23 at 06:16

1 Answers1

3

What Every Programmer Should Know About Floating-Point Arithmetic is an excellent guide to understanding floating point numbers. I thoroughly recommend reading that in full -- I think it will answer your question in detail, and more besides.

The essence, though, is that floating point numbers are inherently imprecise, so these kinds of issues are expected. If you need greater precision (which is a necessity for many applications) then floating point is simply the wrong format to be using.

Note that the rounding has nothing to do with string-to-number. All you're seeing is the nearest floating-point representation for that value. You can observe the same result simply by evaluating the number itself (i.e. getting the lisp reader to generate a float object from the code text):

(string-to-number "8807240867.266221")
=> 8807240867.26622

8807240867.266221
=> 8807240867.26622

See C-hig (elisp)Float Basics for an elisp-specific discussion.

See also https://0.30000000000000004.com/ for a well-known example calculation repeated in many programming languages.

phils
  • 48,657
  • 3
  • 76
  • 115
  • I read through that site. So, right, it is a precision issue due to Emacs internally being forced to round. However, I am still at a loss on how to solve this problem (i.e. maintaining precision) then. ELisp doesn't have data types for numbers beyond integers and decimals. BigNum was added in 27.x but as an internal implementation detail not a fully supported type. Clearly, ELisp's number type can't support this. What then is the best way to represent decimals in ELisp? Strings? – David Jun 15 '23 at 03:11
  • It has nothing to do with Emacs: Emacs is just using what the hardware provides. It would be the same in any language using floating-point numbers (on the same hardware). The solution is to use an [arbitrary-precision arithmetic library](https://en.wikipedia.org/wiki/List_of_arbitrary-precision_arithmetic_software). Emacs Calc provides extended precision: you just have to specify what precision you want and you will get it (at the cost of memory and time). – NickD Jun 15 '23 at 03:40
  • It depends on what you're doing, really. I'm inclined to also recommend `calc` as the in-built and well-tested solution. The bignum support is notable in that decimal fractions are just integers divided by some power of 10, so you *can* use integers as an internal representation for fractions without too much difficulty (again, depending on what you're doing), and bignums mean you won't lose precision; but I'm not sure if there's any pre-written library for that approach. – phils Jun 15 '23 at 04:00