bugGNU Octave - Bugs: bug #67452, Not assigning closest double to a...

 
 

bug #67452: Not assigning closest double to a number literal (Windows)

Submitter:  Felipe <fjh>
Submitted:  Sat 23 Aug 2025 10:00:32 PM UTC
   
 
Category:  Interpreter Severity:  3 - Normal
Priority:  5 - Normal Item Group:  Inaccurate Result
Status:  Fixed Assigned to:  None
Originator Name:  Open/Closed:  * Closed
Release:  * 10.2.0 Operating System:  * Microsoft Windows
Fixed Release:  11.1.0 (current default) Planned Release:  11.1.0 (current default)
* Mandatory Fields

Post a Comment

Add a New Comment Rich Markup
   

Discussion

Jump to the original submission

Tue 18 Nov 2025 03:31:41 PM UTC, comment #64: 

If GPT-5 isn't hallucinating, the default deployment target for Xcode 16.4 is macOS 11.0. Apparently, that can be overridden by setting an environment variable (e.g., MACOSX_DEPLOYMENT_TARGET=13.3). If you do that, the configure script might detect that the fast-float headers aren't needed.
If I understand correctly, the built binaries won't run on an older macOS version than the deployment target.

In macOS 26, Xcode 26 is the standard development environment afaict. According to GPT-5, the default deployment target is still macOS 11.0 in that version of Xcode.

In any case, it looks like this issue is fixed with the recent changes.

Closing report.

Markus Mützel <mmuetzel>
Group administrator
Thu 06 Nov 2025 10:09:25 AM UTC, comment #63: 

FWIW, I'm no longer getting warnings during compilation and 'make check' passes.  This is all on Kubuntu 22.04 with gcc so very ordinary Linux system.

Rik <rik5>
Group administrator
Thu 06 Nov 2025 06:42:16 AM UTC, comment #62: 

Oops. I had already fixed that locally. But I didn't realize that I hadn't updated the commit before I pushed it.
It should be fixed with this change:
https://hg.savann ... /rev/ff6ff281ac02

Regarding the Xcode version on the GitHub runners: The CI is using whatever is installed on the images that GitHub prepares.
According to their documentation, that is version 16.4 on their macos-15 images by default.

The Apple-speech regarding "deployment targets" and things like that is still confusing to me. Maybe, they mean that you can deploy for macOS 13.3 and the conversion will be available if you build on a newer(!) version of the OS (or with a non-default toolchain)?

Markus Mützel <mmuetzel>
Group administrator
Wed 05 Nov 2025 09:03:57 PM UTC, comment #61: 

You need to include charconv for both cases -- it's needed for integers conversions even when float conversions are not available.

Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Wed 05 Nov 2025 07:10:48 PM UTC, comment #60: 

Hg id 6df9a09e0da7 tip @ is failing on FreeBSD:


../libinterp/parse-tree/lex.ll:1031:27: error: no member named 'from_chars' in namespace 'std'
 1031 |     auto [ptr, ec] = std::from_chars (chars_start, chars_end, result, 8);
      |                      ~~~~~^
../libinterp/parse-tree/lex.ll:1036:16: error: value of type 'std::errc' is not implicitly convertible to 'int'
 1036 |           case std::errc::invalid_argument:
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~
../libinterp/parse-tree/lex.ll:1039:16: error: value of type 'std::errc' is not implicitly convertible to 'int'
 1039 |           case std::errc::result_out_of_range:
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../libinterp/parse-tree/lex.ll:1067:27: error: no member named 'from_chars' in namespace 'std'
 1067 |     auto [ptr, ec] = std::from_chars (chars_start, chars_end, result, 16);
      |                      ~~~~~^
../libinterp/parse-tree/lex.ll:1072:16: error: value of type 'std::errc' is not implicitly convertible to 'int'
 1072 |           case std::errc::invalid_argument:
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~
../libinterp/parse-tree/lex.ll:1075:16: error: value of type 'std::errc' is not implicitly convertible to 'int'
 1075 |           case std::errc::result_out_of_range:
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
../libinterp/parse-tree/lex.ll:3387:25: error: no member named 'from_chars' in namespace 'std'
 3387 |   auto [ptr, ec] = std::from_chars (chars_start, chars_end, long_int_val, 16);
      |                    ~~~~~^
../libinterp/parse-tree/lex.ll:3392:14: error: value of type 'std::errc' is not implicitly convertible to 'int'
 3392 |         case std::errc::invalid_argument:
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~
../libinterp/parse-tree/lex.ll:3395:14: error: value of type 'std::errc' is not implicitly convertible to 'int'
 3395 |         case std::errc::result_out_of_range:
      |              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Wed 05 Nov 2025 07:00:52 PM UTC, comment #59: 

According to this:
https://developer ... /xcode/cpp/#c++17


Elementary string conversions std::to_chars, std::from_chars        P0067R5
Minimum deployment target:
For integral types, macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0

For floating-point types, macOS 13.3, iOS 16.3, tvOS 16.3, watchOS 9.3


So, MacOS13.3 and up should be OK ?

Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Wed 05 Nov 2025 06:44:37 PM UTC, comment #58: 

OK, I guess they skipped a digit.

I have MacOS 26.1 at the moment.
I used `std::from_chars` for quite some time, I would say from MacOS 15.x days, but not sure at the moment.

Do you have  Xcode updated on the runners?

What was surprising (for me) is that FreeBSD 14 is more backwards  than MacOS...

So Apple apparently cherry-picks some stuff from newer LLVMs into
their release clang/libc++

Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Wed 05 Nov 2025 06:29:23 PM UTC, comment #57: 

I haven't heard of macOS 25 yet. When was that released?

The macOS 15 runner that is provided by GitHub still failed.

LLVM 20 was released in spring 2025.
I don't know how far Apple lags behind the upstream Clang when they make a new OS release. The version numbers have been lower than LLVM (most of the time or always?) afaict. But Apple compilers with a given version number didn't support the same features as LLVM compilers with that version number. (Sometimes they had features that LLVM compilers supported only with higher version numbers...)

IIUC, macOS 26 is pretty new (September 2025). So, they might have a new enough libc++.

Markus Mützel <mmuetzel>
Group administrator
Wed 05 Nov 2025 06:12:56 PM UTC, comment #56: 

Do you see the problem on MacOS 25 runners? It worked on my my (and I do not remember if that was before I updated to MacOS26 or after).
 
Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Wed 05 Nov 2025 05:56:54 PM UTC, comment #55: 

I've pushed the following change to introduce a fallback to fast-float if the used implementation of the C++ STL doesn't support `std::from_chars` for double precision floating point numbers:
https://hg.savann ... /rev/db2d180312bd

After that, I followed up with the following change to (hopefully) bring the CI on GitHub up to speed again:
https://hg.savann ... /rev/6df9a09e0da7

That means that Octave has a new dependency to the (header-only) fast-float library on macOS and on *BSD until they update their C++ STL implementation to LLVM 20 or later. (That might have happened with macOS 26 or newer.)

Markus Mützel <mmuetzel>
Group administrator
Wed 05 Nov 2025 12:59:17 PM UTC, comment #54: 

On Kubuntu 22.04 With hg id 5aed3f72437d, current dev builds but produces these warnings

libinterp/parse-tree/lex.ll: In member function ‘int octave::base_lexer::handle_number() [with int base = 16]’:
libinterp/parse-tree/lex.ll:3370:13: warning: ‘long_int_val’ may be used uninitialized in this function [-Wmaybe-uninitialized]
 3370 |   const char *chars_start = yytxt.c_str ();
      |             ^~~~~~~~~~~~
libinterp/parse-tree/lex.ll: In function ‘int octave_lex(OCTAVE_STYPE*, yyscan_t)’:
libinterp/parse-tree/lex.ll:1083:34: warning: ‘result’ may be used uninitialized in this function [-Wmaybe-uninitialized]
 1083 |   }
      |                                  ^
libinterp/parse-tree/lex.ll:1058:18: note: ‘result’ was declared here
 1058 |     const char *chars_start = yytext + 2;
      |                  ^~~~~~
libinterp/parse-tree/lex.ll:1050:36: warning: ‘result’ may be used uninitialized in this function [-Wmaybe-uninitialized]
 1050 |   }
      |                                    ^
libinterp/parse-tree/lex.ll:1022:18: note: ‘result’ was declared here
 1022 |     const char *chars_start = yytext + 1;
      |                  ^~~~~~


Other than that, 'make check' passes.

Rik <rik5>
Group administrator
Tue 04 Nov 2025 06:17:34 PM UTC, comment #53: 

I guess I misstyped something at first and it complaind to me about "[ptr,ec] = ..." . But now I tried again and it works.
So the patch is just to add the header file and change "std::from_chars" to "fast_float::from_chars" for double/float conversion.


diff -r 5aed3f72437d libinterp/parse-tree/lex.ll
--- a/libinterp/parse-tree/lex.ll       Tue Nov 04 10:40:46 2025 -0500
+++ b/libinterp/parse-tree/lex.ll       Tue Nov 04 13:15:18 2025 -0500
@@ -103,6 +103,8 @@
 #include <string>
 #include <stack>

+#include <fast_float/fast_float.h>
+
 #include "cmd-edit.h"
 #include "mappers.h"
 #include "quit.h"
@@ -3239,7 +3241,7 @@
   double value = 0.0;
   const char *chars_start = tmptxt;
   const char *chars_end = tmptxt + std::strlen (tmptxt);
-  auto [ptr, ec] = std::from_chars (chars_start, chars_end, value);
+  auto [ptr, ec] = fast_float::from_chars (chars_start, chars_end, value);
   if (ec != std::errc{})
     {
       switch (ec)
diff -r 5aed3f72437d liboctave/util/oct-string.cc
--- a/liboctave/util/oct-string.cc      Tue Nov 04 10:40:46 2025 -0500
+++ b/liboctave/util/oct-string.cc      Tue Nov 04 13:15:18 2025 -0500
@@ -37,6 +37,8 @@
 #include <string>
 #include <unordered_set>

+#include <fast_float/fast_float.h>
+
 #include "Array-oct.h"
 #include "iconv-wrappers.h"
 #include "lo-ieee.h"
@@ -279,7 +281,7 @@
     }

   // Use from_chars for all other numbers (including Inf, -Inf, NaN)
-  auto [ptr, ec] = std::from_chars (first, last, num);
+  auto [ptr, ec] = fast_float::from_chars (first, last, num);

   if (ec == std::errc {})
     {


(file fast_float_2.diff)

Dmitri.
--



(file #57776)

Dmitri A. Sergatskov <dasergatskov>
Tue 04 Nov 2025 05:55:58 PM UTC, comment #52: 

The fast_float lib is header-only so no extra linking and no binary libs.

The following diff fixed that for me on FreeBSD:

(file fast_float.diff)

Of course the real patch should make this change conditional.

Make check passes:

Summary:

  PASS                            20110
  FAIL                                1
  XFAIL (reported bug)               52
  SKIP (missing feature)              1
  SKIP (run-time condition)          53


(FAIL is due to lack of proper `memory()`)


Dmitri.
--


(file #57775)

Dmitri A. Sergatskov <dasergatskov>
Tue 04 Nov 2025 04:47:53 PM UTC, comment #51: 

Both FreeBSD and Homebrew ship fast_float lib.
Here is a Scilab example:
https://gitlab.co ... quests/1455/diffs


        }
        return {};
    }
    // default implementation is provided by std::from_chars
#ifdef __APPLE__
    // implementation may be provided by https://github.com/fastfloat/fast_float
#ifdef FASTFLOAT_VERSION_MAJOR
    auto [ptr, ec] = fast_float::from_chars(str.data(), str.data() + str.size(), result);
#else
    // use std::from_chars otherwise (eg. C++17)
    auto [ptr, ec] = std::from_chars(str.data(), str.data() + str.size(), result);
#endif
    return ec;



Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Tue 04 Nov 2025 04:15:04 PM UTC, comment #50: 

I'm unsure how we should proceed.
Should we just revert this change and keep it as it was for Octave 11? Or should we use `std::strtod` instead? Or fall back to `std::strtod` and continue to use `std::from_char` if it is available? Or add a dependency to the (header-only) "fast_float" library and use that? Or something else?

Markus Mützel <mmuetzel>
Group administrator
Tue 04 Nov 2025 02:01:25 PM UTC, comment #49: 

There is also this:
https://github.co ... tfloat/fast_float

but may be this getting too complicated...

P.S. I see the same problem on FreeBSD (with its system compiler 19.1.7).

Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Tue 04 Nov 2025 01:10:14 PM UTC, comment #48: 

Can you add `-fexperimantal-library` to the c++ flags and see if that helps somewhat? But yes llvm-14 is old...

Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Tue 04 Nov 2025 09:50:23 AM UTC, comment #47: 

Just a guess: Maybe, it would make the template resolution easier for the compiler to add the fourth argument `std::chars_format::general`?
Apparently, it didn't even consider (2) on [1] at all...

But then, the table on [2] suggests that `std::from_chars` wasn't implemented in LLVM libc++ before version 20.

That's a pity. Does that mean that we'd need configure tests to check support and use an alternative (potentially less versatile) fallback if it isn't available?

Additionally to LLVM libc++ on Linux, macOS is also affected (e.g., [3]).

[1]: https://en.cppref ... y/from_chars.html
[2]: https://en.cppref ... _library_features
[3]: https://github.co ... 0/job/54391504538

Markus Mützel <mmuetzel>
Group administrator
Tue 04 Nov 2025 09:38:43 AM UTC, comment #46: 

It looks like this change caused build issues with LLVM libc++:
E.g.: https://github.co ... 4266#step:11:4699


../libinterp/parse-tree/lex.ll:3243:20: error: call to deleted function 'from_chars'
  auto [ptr, ec] = std::from_chars (chars_start, chars_end, value);
                   ^~~~~~~~~~~~~~~
/usr/lib/llvm-14/bin/../include/c++/v1/charconv:113:19: note: candidate function has been explicitly deleted
from_chars_result from_chars(const char*, const char*, bool, int = 10) = delete;
                  ^
/usr/lib/llvm-14/bin/../include/c++/v1/charconv:588:1: note: candidate template ignored: requirement 'is_integral<double>::value' was not satisfied [with _Tp = double]
from_chars(const char* __first, const char* __last, _Tp& __value)
^
/usr/lib/llvm-14/bin/../include/c++/v1/charconv:595:1: note: candidate function template not viable: requires 4 arguments, but 3 were provided
from_chars(const char* __first, const char* __last, _Tp& __value, int __base)
^
1 error generated.


Markus Mützel <mmuetzel>
Group administrator
Mon 03 Nov 2025 08:41:40 PM UTC, comment #45: 

Thank you, Markus.

Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Mon 03 Nov 2025 06:24:39 PM UTC, comment #44: 

I've confirmed that the proposed patch fixes this issue on Windows.

I've added a commit message, added the tests that Dmitri proposed in comment #42 plus the original example that motivated this report, and pushed it with him as the author to the default branch:
https://hg.savann ... /rev/b93ef8afa63f

Marking as ready for test.

Markus Mützel <mmuetzel>
Group administrator
Fri 31 Oct 2025 09:00:19 AM UTC, comment #43: 

Can someone who does more development of Octave on Windows review the patch in comment #42 ahead of Octave 11 code freeze on Dec. 1st?

Rik <rik5>
Group administrator
Tue 23 Sep 2025 04:25:23 AM UTC, comment #42: 

Found a couple bugs in the latest patch -- it would issue error for large numbers (x = 1e999) instead of Inf.
The updated patch fixes that.

(file bug67452_from_chars_v4.diff)

Note, "make check" did not catch it, perhaps we should add test cases for "x = 1e999", "x=-1e999", "str2double("1e999")", etc...,
not sure where.

Dmitri.
--


(file #57679)

Dmitri A. Sergatskov <dasergatskov>
Tue 23 Sep 2025 02:08:13 AM UTC, comment #41: 

Attached is updated diff that changes "lex.ll" (sscanf()) and "oct-string.cc" (std::ios) to "from_chars ()" for concistency.

(file bug67452_from_chars_v3.diff)

Dmitri.
--


(file #57678)

Dmitri A. Sergatskov <dasergatskov>
Thu 28 Aug 2025 04:57:28 PM UTC, comment #40: 

For octal and hexadecimal literal values, the result is an integer that is supposed to be sized appropriately for the number of digits in the literal constant.  So I think there shouldn't be any truncation or rounding errors in the results.  An incorrect result there would seem to be a serious bug in sscanf.  But we could use strtol, strtoul, strtoll, strtoull instead.  Or just use from_chars for all of these conversions?

John W. Eaton <jwe>
Group administrator
Thu 28 Aug 2025 04:48:02 PM UTC, comment #39: 

"Not all integers are read as doubles.  There is special code to handle uint64 and int64 values."

Yes, this feature allows the following code to work in a way that is (I believe) compatible with Matlab:


format long
flintmax
for i = 9007199254740992:9007199254740999; i, end
class (i)
end


In Octave, it will print


ans = 9007199254740992
i = 9007199254740992
i = 9007199254740992
i = 9007199254740994
i = 9007199254740996
i = 9007199254740996
i = 9007199254740996
i = 9007199254740998
i = 9007199254741000
i = 9007199254741000
ans = double


On assignment, the special internal octave_value type that stores values larger than flintmax as an integer is converted to double.  This allows code like


int64 (9007199254740993)


to accurately store values larger than flintmax in int64 objects but makes


i = 9007199254740993


a double value that is equal to flintmax.

See also bug #45945

John W. Eaton <jwe>
Group administrator
Thu 28 Aug 2025 12:18:16 PM UTC, comment #38: 

Attached is the diff that converts `oct-string.cc` to use `from_chars`.

(file bug67452_oct-string_from_chars.diff)

Dmitri.
--


(file #57589)

Dmitri A. Sergatskov <dasergatskov>
Thu 28 Aug 2025 11:34:26 AM UTC, comment #37: 


comment #33:

> That could potentially be slightly simplified by using something like this (untested):


>     switch (ec)
>       {


Unfortunately it is not that simple:
+verbatim+
../libinterp/parse-tree/lex.ll: In function 'int octave_lex(OCTAVE_STYPE*, yyscan_t)':
../libinterp/parse-tree/lex.ll:1025:7: warning: case value '0' not in enumerated type 'std::errc' [-Wswitch]
 1025 |         break;
      |       ^ ~~
...


probably cleaner to stick with "if"

Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Thu 28 Aug 2025 11:09:41 AM UTC, comment #36: 


comment #32:

> I just replaced the existing `sscanf` code, I am not 100% sure how lexer works, I assume all (decimal) integers are read as double?!
>

Not all integers are read as doubles.  There is special code to handle uint64 and int64 values.  But, it should be tested by 'make check'.  I would think jwe wrote some BIST tests for the parser to verify that code.

Binary and hexadecimal integer constants are read as integer types (uint8, etc.) rather than floating point (double).

Rik <rik5>
Group administrator
Thu 28 Aug 2025 11:03:38 AM UTC, comment #35: 


> I believe the Octave code base relies on 'C' locale in many places.  If the user wants to do this there are no guarantees about behavior.  Figuring out where those locations are in the code, and then fixing each and every one, sounds like a lot of work for not much gain.


I completely agree. It is probably not worth going through the entire codebase and trying to figure out where something could be locale dependent.

On the other hand, when we are already changing some code and we have the choice to use functions that could be brittle when it comes to locale changes or other functions that are not, we could choose to use functions that will probably be more resilient.

Markus Mützel <mmuetzel>
Group administrator
Thu 28 Aug 2025 10:59:23 AM UTC, comment #34: 


comment #24:

> Even if we are setting the locale when initializing the interpreter, there is no guarantee that a user doesn't change it after that initialization.


I believe the Octave code base relies on 'C' locale in many places.  If the user wants to do this there are no guarantees about behavior.  Figuring out where those locations are in the code, and then fixing each and every one, sounds like a lot of work for not much gain.

> I'm uncertain whether it is a good or a bad thing if Octave uses functions which behave differently depending on the current locale. I'm leaning towards preferring locale independent functions where it is easily possible. Using std::from_chars instead of std::strtod or std::sscanf wouldn't complicate the code much.

Rik <rik5>
Group administrator
Thu 28 Aug 2025 10:39:38 AM UTC, comment #33: 

That could potentially be slightly simplified by using something like this (untested):

    switch (ec)
      {
      case std::errc{}:
        break;
      case std::errc::invalid_argument:
        curr_lexer->fatal_error ("invalid octal digits in lexer rule <DQ_STRING_START>\\\\[0-7]{1,3} - please report this bug");
        break;
      case std::errc::result_out_of_range:
        curr_lexer->fatal_error ("octal value out of range in lexer rule <DQ_STRING_START>\\\\[0-7]{1,3} - please report this bug");
        break;
      default:
        curr_lexer->fatal_error ("from_chars failed in lexer rule <DQ_STRING_START>\\\\[0-7]{1,3} - please report this bug");
      }


(That could potentially avoid the additional `if` clause.)

Markus Mützel <mmuetzel>
Group administrator
Thu 28 Aug 2025 10:28:56 AM UTC, comment #32: 

I just replaced the existing `sscanf` code, I am not 100% sure how lexer works, I assume all (decimal) integers are read as double?!

Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Thu 28 Aug 2025 10:26:13 AM UTC, comment #31: 

Attached a version of diff with error handle:

if (ec != std::errc{})  // "if NOT success"
  {
    switch (ec)  // ec is definitely an error code here
      {
      case std::errc::invalid_argument: // parsing error
      case std::errc::result_out_of_range: // value too big
      default: // ANY other error (hardware, I/O, etc.)
      }
  }
// Continue here only if ec == std::errc{} (success)


In the future we may add additional cases.

(file bug67452_from_chars_v2.diff).



(file #57587)

Dmitri A. Sergatskov <dasergatskov>
Thu 28 Aug 2025 10:22:24 AM UTC, comment #30: 

Either would be fine with me. But given that we get that information for free, something like what you proposed in comment #28 looks good to me.
We could probably use the same pattern also for the integer types. (Or can they never fail with std::errc::result_out_of_range for some reason?)

Markus Mützel <mmuetzel>
Group administrator
Thu 28 Aug 2025 09:53:52 AM UTC, comment #29: 

Or may be we just check `ec != 0` and bail out with `ec` error code?

Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Thu 28 Aug 2025 09:32:03 AM UTC, comment #28: 

I guess we can do:

+    if (ec != std::errc{})
+      {
+        switch (ec)
+          {
+          case std::errc::invalid_argument:
+            curr_lexer->fatal_error ("invalid octal digits in lexer rule <DQ_STRING_START>\\\\[0-7]{1,3} - please report this bug");
+            break;
+          case std::errc::result_out_of_range:
+            curr_lexer->fatal_error ("octal value out of range in lexer rule <DQ_STRING_START>\\\\[0-7]{1,3} - please report this bug");
+            break;
+          default:
+            curr_lexer->fatal_error ("from_chars failed in lexer rule <DQ_STRING_START>\\\\[0-7]{1,3} - please report this bug");
+          }
+      }


Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Thu 28 Aug 2025 09:30:42 AM UTC, comment #27: 

I haven't checked the standard documentation. But the page on cppreference.com only describes that these two error codes need to be set for these specific cases for a conformant implementation. Afaict, it doesn't mention that a conformant implementation is not allowed to set other error codes in other cases.

Markus Mützel <mmuetzel>
Group administrator
Thu 28 Aug 2025 09:22:52 AM UTC, comment #26: 

I do not know about the future, but there  is only 2  errors defined at the moment
https://en.cppref ... y/from_chars.html


If there is no pattern match, returns a value of type std::from_chars_result such that ptr equals first and ec equals std::errc::invalid_argument. value is unmodified.

If the pattern was matched, but the parsed value is not in the range representable by the type of value, returns value of type std::from_chars_result such that ec equals std::errc::result_out_of_range and ptr points at the first


Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Thu 28 Aug 2025 09:09:45 AM UTC, comment #25: 

I crossed with Dmitri's post.

In general, your patch looks good to me. But is it a good idea to only fail if `ec` is `std::errc::invalid_argument` or `std::errc::result_out_of_range`, and continuing for all other error codes as if nothing happened? Are we sure that no implementation of `std::from_chars` will ever return any other error code?

Markus Mützel <mmuetzel>
Group administrator
Thu 28 Aug 2025 09:02:19 AM UTC, comment #24: 

Even if we are setting the locale when initializing the interpreter, there is no guarantee that a user doesn't change it after that initialization.
I'm uncertain whether it is a good or a bad thing if Octave uses functions which behave differently depending on the current locale. I'm leaning towards preferring locale independent functions where it is easily possible. Using std::from_chars instead of std::strtod or std::sscanf wouldn't complicate the code much.

Markus Mützel <mmuetzel>
Group administrator
Thu 28 Aug 2025 08:59:58 AM UTC, comment #23: 

Attached diff (file bug67452_from_chars.diff) replaces all `sscanf` with `drom_chars` in `lex.ll`.
The error message also distinguish between invalid format and out-of-range errors.

Passes all tests on my linux computer.

Dmitri.
--


(file #57586)

Dmitri A. Sergatskov <dasergatskov>
Thu 28 Aug 2025 07:37:46 AM UTC, comment #22: 

Attached is a changeset which switches to using strtod for reading double values.  It passes 'make check' for me.

On the other hand, this is clearly an upstream bug with Microsoft's libc.  Should we bother to change our core code for this?  In particular, Octave is still using sscanf for reading octal and hexadecimal numbers.  Are there also hidden problems for particular values in these formats?

(file #57585)

Rik <rik5>
Group administrator
Thu 28 Aug 2025 07:00:17 AM UTC, comment #21: 


comment #18:

> Having written that: I seem to recall that switching the locale repeatedly is slow (at least on some OS).


Yes, it is very slow.  We should avoid that.

> We could potentially avoid switching the locale by using std::from_char consistently.
>
> That might make using that function the most attractive one for the task.
>


Octave calls setlocale() as part of its initialization to ensure that 'C' is set so that basic numbers work.


#if defined (OCTAVE_HAVE_WINDOWS_UTF8_LOCALE)
  // Force a UTF-8 locale on Windows if possible
  std::setlocale (LC_ALL, ".UTF8");
#else
  std::setlocale (LC_ALL, "");
#endif
  // Matlab uses "C" locale for LC_NUMERIC class regardless of local setting
  std::setlocale (LC_NUMERIC, "C");
  std::setlocale (LC_TIME, "C");
  sys::env::putenv ("LC_NUMERIC", "C");
  sys::env::putenv ("LC_TIME", "C");


This is in the constructor for the interpreter in interpreter.cc starting at line 480.


Rik <rik5>
Group administrator
Wed 27 Aug 2025 07:11:25 PM UTC, comment #20: 

In the current code in the lexer Octave only passes strings to sscanf that match a specific pattern.  For decimal numbers the code is:


{DECIMAL_DIGITS}/\.[\*/\\^\'] |
{DECIMAL_NUMBER} {
    HANDLE_NUMBER ("{DECIMAL_DIGITS}/\\.[\\*/\\\\^\\']|{DECIMAL_NUMBER}", 10);
  }


Note that the first pattern, "{DECIMAL_DIGITS}/\.[\*/\\^\']" includes trailing context for Matlab compatibility so that the "." in expressions like 1./[1,2;3,4] are recognized as part of the operator, not the number.

The definition of the {DECIMAL_NUMBER} pattern appears earlier in lex.ll:


D       [0-9]
D_      [0-9_]
DECIMAL_DIGITS ({D}{D_}*)
EXPONENT       ([DdEe][+-]?{DECIMAL_DIGITS})
REAL_DECIMAL   ((({DECIMAL_DIGITS}\.?)|({DECIMAL_DIGITS}?\.{DECIMAL_DIGITS})){EXPONENT}?)
IMAG_DECIMAL   ({REAL_DECIMAL}[IiJj])
DECIMAL_NUMBER ({REAL_DECIMAL}|{IMAG_DECIMAL})


There is some additional processing in base_lexer::handle_number<10> to handle "D" and "d" exponents, the "_" digits separator, and the "I", "i", "J", "j" characters to denote imaginary constants.  So ultimately we should be passing a number that either strtod or from_chars can handle.

The string that is passed to sscanf does not include any unary operator.  That is parsed as a unary expression.  We could later transform that expression to a negated constant in the parser but I don't think we are doing that now and I'm not sure it is a good idea to do that if we want to make it possible to allow users to override @double/uminis.

John W. Eaton <jwe>
Group administrator
Wed 27 Aug 2025 02:28:06 PM UTC, comment #19: 

Oops. There is a copy-paste-error in the .oct file code.
The last "sscanf method" in the output of the function should read "std::from_chars method" instead.

Markus Mützel <mmuetzel>
Group administrator
Wed 27 Aug 2025 02:24:58 PM UTC, comment #18: 

I haven't looked at how that is currently implemented in Octave.

For what it's worth, I repeated the tests with these three functions after changing the C locale to "de_DE.utf8" (which uses the comma "," as the decimal separator). See the attached file.

With it, I see the following:

>> mkoctfile readdouble.cc
>> readdouble
With default C locale:
sscanf method
Value: 1.0004439999999998889
Value: 0x1.001d19157abb8p+0
strtod method
Value: 1.0004440000000001110
Value: 0x1.001d19157abb9p+0
std::from_chars method
Value: 1.0004440000000001110
Value: 0x1.001d19157abb9p+0
With de_DE.utf8 locale:
sscanf method
Value: 1.0000000000000000000e+00
Value: 0x1p+0
strtod method
Value: 1.0000000000000000000e+00
Value: 0x1p+0
sscanf method
Value: 1.0004440000000001110e+00
Value: 0x1.001d19157abb9p+0
>>


Note that after having executed that function, "str2num" starts misbehaving. "str2double" still works fine:

>> str2num ('1.000444')
ans = 1
>> str2double ('1.000444')
ans = 1.0004
>>


That looks like an additional bug.
We could fix that bug by setting the locale to "C" before calling "sscanf" (or "strtod") and resetting to the original locale once we are done.

Having written that: I seem to recall that switching the locale repeatedly is slow (at least on some OS). We could potentially avoid switching the locale by using std::from_char consistently.

That might make using that function the most attractive one for the task.


(file #57583)

Markus Mützel <mmuetzel>
Group administrator
Wed 27 Aug 2025 01:00:35 PM UTC, comment #17: 

Does not octave do its own parsing of the sign?
E.g. in Octave

octave:4> x = 5
x = 5
octave:5> x = - 3
x = -3


but none of the standard C++ parsers would convert "- 3" string to "-3".

Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Wed 27 Aug 2025 12:15:49 PM UTC, comment #16: 

From the documentation of std::from_chars on cppreference.com:

> * the plus sign is not recognized outside of the exponent (only the minus sign is permitted at the beginning)


Is that an issue here?

Markus Mützel <mmuetzel>
Group administrator
Wed 27 Aug 2025 12:09:29 PM UTC, comment #15: 

I also voting to change code in `oct-string.cc` to use from_chars` as well. We are puzzling on the frequent allocation/deallocation  in Octave profiles, may be string parsing is one of the contributors.

Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Wed 27 Aug 2025 09:48:22 AM UTC, comment #14: 

Maybe Markus can test as well.

I have a slight leaning towards replacing sscanf with strtod to keep everything in libc, but open to other arguments.

Rik <rik5>
Group administrator
Tue 26 Aug 2025 08:31:40 PM UTC, comment #13: 

From https://en.cppref ... y/from_chars.html

Unlike other parsing functions in C++ and C libraries, std::from_chars is locale-independent, non-allocating, and non-throwing. Only a small subset of parsing policies used by other libraries (such as std::sscanf) is provided. This is intended to allow the fastest possible implementation that is useful in common high-throughput contexts such as text-based interchange


Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Tue 26 Aug 2025 08:15:40 PM UTC, comment #12: 

I also added `from_chars`:

#include <octave/oct.h>

#include <stdio.h>
#include <stdlib.h>

#include <charconv>   // add near the top
#include <cstring>    // for strlen

DEFUN_DLD (readdouble, args, nargout,
           "Test various forms of readding a double from a string")
{
  const char numstr[] = "1.000444";
  double val = 0.0;
  int r;

  printf ("sscanf method\n");
  r = sscanf (numstr, "%lf", &val);
  printf ("Value: %.19f\n", val);
  printf ("Value: %a\n", val);

  printf ("strtod method\n");
  val = 0.0; // Reset
  val = strtod (numstr, NULL);
  printf ("Value: %.19f\n", val);
  printf ("Value: %a\n", val);


  printf ("std::from_chars method\n");
  val = 0.0; //Reset
  const char *first = numstr;
  const char *last  = numstr + std::strlen (numstr);
  std::from_chars (first, last, val);
  printf ("Value: %.19f\n", val);
  printf ("Value: %a\n", val);


  return ovl ();
}


Both `strtod` and `from_chars` produce correct results on Win10 for me:

>> readdouble
sscanf method
Value: 1.0004439999999998889
Value: 0x1.001d19157abb8p+0
strtod method
Value: 1.0004440000000001110
Value: 0x1.001d19157abb9p+0
std::from_chars method
Value: 1.0004440000000001110
Value: 0x1.001d19157abb9p+0
>> ver
----------------------------------------------------------------------
GNU Octave Version: 10.2.0 (hg id: 8d42ee70e124)
GNU Octave License: GNU General Public License
Operating System: MINGW32_NT-6.2 Windows 6.2  x86_64
----------------------------------------------------------------------


I vote for `from_chars` method.

Dmitri.

Dmitri A. Sergatskov <dasergatskov>
Tue 26 Aug 2025 07:44:03 PM UTC, comment #11: 

This feels like an upstream bug in the libc provided by Microsoft.  But, maybe there is the chance to work around it with a different library function.  Attached is another oct-file (readdouble.cc) which tries sscanf and strtod.  Maybe one of them works better on Windows.  On Linux, they produce the same value.

(file #57580)

Rik <rik5>
Group administrator
Mon 25 Aug 2025 02:22:55 PM UTC, comment #10: 

The sscanf function on Windows is provided by the Microsoft implementation(s) of the C library. For the version of Octave that we build with MXE Octave, the MS UCRT should be used as the C library. I haven't checked if MinGW-w64 provides a replacement for it.

In Octave 10.2.0 on Windows 11, I see the following for "mysscanf":

>> mkoctfile mysscanf.cc
>> mysscanf ()
Value: 1.0004439999999998889
Value: 0x1.001d19157abb8p+0


I see the same as Dmitri for str2num (eval) vs. str2double:

>> format hex
>> x = 1.000444
x = 3ff001d19157abb8
>> x1 = str2num ('1.000444')
x1 = 3ff001d19157abb8
>> x2 = str2double ('1.000444')
x2 = 3ff001d19157abb9


So, it looks like the result of sscanf is off by one eps.

Markus Mützel <mmuetzel>
Group administrator
Mon 25 Aug 2025 01:43:21 PM UTC, comment #9: 

Weirder things have happened.  I don't know which library is supplying sscanf on Windows.  Is it a Microsoft libc?  It it a libc that Octave bundles through the MXE process?  Something else?

Here is an oct-file which should use the stand-alone library function.  Perhaps this helps with testing.


#include <octave/oct.h>

#include <stdio.h>

DEFUN_DLD (mysscanf, args, nargout,
           "Test sscanf")
{
  const char numstr[] = "1.000444";
  double val;
  int r;

  r = sscanf (numstr, "%lf", &val);

  printf ("Value: %.19f\n", val);
  printf ("Value: %a\n", val);

  return ovl ();
}


Also attached as mysscanf.cc.

Run "mkoctfile mysscanf.cc" and then "mysscanf ()".


(file #57572)

Rik <rik5>
Group administrator
Mon 25 Aug 2025 01:09:38 PM UTC, comment #8: 

For numeric constants that are not larger than flintmax, the lexer converts the text representation to a double value using sscanf with a "%lf" format.  See the definition of base_lexer::handle_number<10> in the file libinterp/parse-tree/lex.ll.

Should we be using std::strtod or std::from_chars instead?

John W. Eaton <jwe>
Group administrator
Sun 24 Aug 2025 08:46:15 PM UTC, comment #7: 

Yes, so the direct c++ library call code is OK.

Dmitri.
--

Dmitri A. Sergatskov <dasergatskov>
Sun 24 Aug 2025 08:38:12 PM UTC, comment #6: 

My observation was just that the first two represent the same code path, while the third is a different one.  So the first two will be either both right together or both wrong together.

Rik <rik5>
Group administrator
Sun 24 Aug 2025 08:29:04 PM UTC, comment #5: 

If I understand OP and Dmitri, the first two methods (direct assignment and str2num) produce a result that is eps lower than the str2double case. In Linux, all of them produce the same result (what str2double gives on Windows).

Arun Giridhar <arungiridhar>
Group Member
Sun 24 Aug 2025 08:00:15 PM UTC, comment #4: 

No surprise that the first method (direct assignment) produces the same result as the second method (str2num).  The code in str2num uses eval() to have the Octave parser convert the string, just as in direct assignment.  The third method is quite different and ends up in str2double in liboctave/util/oct-string.cc.  As far as I can see, Octave ends up relying on the C++ standard library:


298:    is >> num;


If true, could create a standalone C++ program to verify this.  Or maybe an oct-file for ease.


Thanks for taking the time to report this bug.

Unfortunately, that stack trace is missing some elements that
will help a lot to solve the problem, so it will be hard for the
developers to fix that crash. Can you get us a stack trace with
debugging symbols? Please see
http://wiki.octav ... /Debugging_Octave for more information on
how to do so. Thanks in advance!

Rik <rik5>
Group administrator
Sun 24 Aug 2025 05:56:48 PM UTC, comment #3: 

I confirm the problem on Windows, see attached (file double.png).
I cannot reproduce it on any other OS (linux, macOS, FreeBSD).
I wonder if windows binary uses x87 somehow/somewhere and that causes the problem.

Dmitri.
--



Dmitri A. Sergatskov <dasergatskov>
Sun 24 Aug 2025 02:36:37 PM UTC, comment #2: 

Works for me too. I used "format hex" (you can also try native-hex bit / native-bit if you want. See 'help format').


format hex
x = 1.000444
x1 = str2num ('1.000444')
x2 = str2double ('1.000444')

x = 3ff001d19157abb9
x1 = 3ff001d19157abb9
x2 = 3ff001d19157abb9


Arun Giridhar <arungiridhar>
Group Member
Sun 24 Aug 2025 02:28:12 AM UTC, comment #1: 

Works for me on linux/ubuntu producing the expected (consistent and identical) results for all three conversion methods/functions of a decimal literal into a double.
I suggest using num2hex to see the binary representation of the numbers and compare it with e.g. Wolfram (https://www.wolfr ... amalpha.com/input).
Note: Wolfram shows the hex number in reverse order compared to num2hex, so the num2hex output could be reversed for easier comparison. Hence, the small loop in the code below...


https://www.wolframalpha.com/input?i2d=true&i=1.000444%20as%20double
returns
b9ab5791d101f03f


 x  = 1.000444;
a = num2hex(x); out = ""; for i = 0:2:numel(a)-1;  out = [out, num2str(a(numel(a)-i-1)), num2str(a(numel(a)-i))];  endfor; disp(out);

x1 = str2num('1.000444');
a = num2hex(x1); out = ""; for i = 0:2:numel(a)-1;  out = [out, num2str(a(numel(a)-i-1)), num2str(a(numel(a)-i))];  endfor; disp(out);

x2 = str2double('1.000444');
a = num2hex(x2); out = ""; for i = 0:2:numel(a)-1;  out = [out, num2str(a(numel(a)-i-1)), num2str(a(numel(a)-i))];  endfor; disp(out);

disp([x==x1, x==x2, x1==x2]);
b9ab5791d101f03f
b9ab5791d101f03f
b9ab5791d101f03f
  1  1  1


Just for fun for a single:

https://www.wolframalpha.com/input?i2d=true&i=1.000444%20as%20float
return 8d0e803f


x  = single(1.000444);
a = num2hex(x); out = ""; for i = 0:2:numel(a)-1;  out = [out, num2str(a(numel(a)-i-1)), num2str(a(numel(a)-i))];  endfor; disp(out);
8d0e803f



Hendrik K <koerhen>
Sat 23 Aug 2025 10:00:32 PM UTC, original submission:  

In Windows GNU Octave 10.2.0, when I run just

x = 1.000444

I do not get the closest double to the real number 1.000444. Not even tied. You need str2double (str2num and eval have the same problem).

This is just an example. It happens with a small fraction of numbers, but consistently.

Below is code for anyone to reproduce the bug:

>> format long
>> x =              1.000444

x = 1.000444000000000

>> x1 =    str2num('1.000444')

x1 = 1.000444000000000

>> x2 = str2double('1.000444')

x2 = 1.000444000000000

>> % Apparently all the same but:
>> x == x1

ans = 1

>> x == x2

ans = 0

>> % So x == x1 ~= x2.
>> % Let's see which, x1 or x2, is closer to exactly 1.000444.
>> % In the string expansion by sprintf, even if digits 17+ are
>> % "artifacts" with respect to 1.000444, they should still
>> % be "exact" for the machine numbers:
>> x1str = sprintf('%.25g', x1)

x1str = 1.00044399999999988892796

>> x2str = sprintf('%.25g', x2)

x2str = 1.000444000000000110972564

>> % Subtract by hand or using vpa:
>> error1 = anm_vpaminus('1.000444', x1str)  % vpa subtraction (my function)

error1 =  0.00000000000000011107204

>> error2 = anm_vpaminus('1.000444', x2str)  % vpa subtraction (my function)

error2 = -0.000000000000000110972564

>> % So x2 is closer to 1.000444 than x (or x1). Not even tied.
>> % Doesn't this violate IEEE 754’s round-to-nearest rule?


Felipe <fjh>

 

Attached Files

Attached Files
file #57776:  fast_float_2.diff added by dasergatskov (1.3KiB - text/x-patch)
file #57775:  fast_float.diff added by dasergatskov (2.9KiB - text/x-patch)
file #57586:  bug67452_from_chars.diff added by dasergatskov (3.7KiB - text/x-patch)
file #57585:  bug67452.cset added by rik5 (1.1KiB - application/octet-stream)
file #57583:  readdouble.cc added by mmuetzel (2.0KiB - text/plain)
file #57580:  readdouble.cc added by rik5 (512B - text/x-c++src)
file #57572:  mysscanf.cc added by rik5 (291B - text/x-c++src)
file #57568:  double.png added by dasergatskov (82KiB - image/png)

(Note: upload size limit is set to 4.0MiB, after insertion of the required escape characters.)

Attach Files:
   
   
Comment:
   

 

Dependencies

This item does not depend on any other items.

No items depend on this one.

 

Mail Notification Carbon-Copy List

Carbon-Copy List
  • -email is unavailable- added by mmuetzel (Posted a comment)
  • -email is unavailable- added by jwe (Posted a comment)
  • -email is unavailable- added by rik5 (Posted a comment)
  • -email is unavailable- added by dasergatskov (Updated the item)
  • -email is unavailable- added by arungiridhar (Posted a comment)
  • -email is unavailable- added by koerhen (Posted a comment)
  • -email is unavailable- added by fjh (Submitted the item)
  •  

    Votes

    There are 0 votes so far. Votes easily highlight which items people would like to see resolved in priority, independently of the priority of the item set by tracker managers.

    Only group members can vote.

     

    Please enter the title of George Orwell's famous dystopian book (it's a date):

    History

    Follow 23 latest changes.

    Date Changed by Updated Field Previous Value => Replaced by
    2025-11-18 mmuetzel CategoryOther Interpreter
        StatusReady For Test Fixed
        Open/ClosedOpen Closed
    2025-11-04 dasergatskov Attached File- Added fast_float_2.diff, #57776
    2025-11-04 dasergatskov Attached File- Added fast_float.diff, #57775
    2025-11-03 mmuetzel StatusPatch Submitted Ready For Test
        Fixed ReleaseNone 11.1.0 (current default)
    2025-10-31 rik5 StatusIn Progress Patch Submitted
        Planned ReleaseNone 11.1.0 (current default)
    2025-09-23 dasergatskov Attached File- Added bug67452_from_chars_v4.diff, #57679
    2025-09-23 dasergatskov Attached File- Added bug67452_from_chars_v3.diff, #57678
    2025-08-28 dasergatskov Attached File- Added bug67452_oct-string_from_chars.diff, #57589
    2025-08-28 dasergatskov Attached File- Added bug67452_from_chars_v2.diff, #57587
    2025-08-28 mmuetzel StatusPatch Submitted In Progress
    2025-08-28 dasergatskov Attached File- Added bug67452_from_chars.diff, #57586
    2025-08-28 rik5 Attached File- Added bug67452.cset, #57585
        StatusIn Progress Patch Submitted
    2025-08-27 mmuetzel Attached File- Added readdouble.cc, #57583
    2025-08-27 rik5 StatusNone In Progress
    2025-08-26 rik5 Attached File- Added readdouble.cc, #57580
    2025-08-25 rik5 Attached File- Added mysscanf.cc, #57572
    2025-08-24 rik5 SummaryNot assigning closest double to a number literal Not assigning closest double to a number literal (Windows)
    2025-08-24 dasergatskov Attached File- Added double.png, #57568

    Back to the top

    Powered by Savane 3.16-a7ba.
    Corresponding source code