Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

strconv: ParseFloat does not accept a leading sign on nan #73163

Closed
saj opened this issue Apr 4, 2025 · 9 comments
Closed

strconv: ParseFloat does not accept a leading sign on nan #73163

saj opened this issue Apr 4, 2025 · 9 comments
Labels
BugReport Issues describing a possible bug in the Go implementation.

Comments

@saj
Copy link

saj commented Apr 4, 2025

Go version

go version go1.24.1 darwin/arm64

Output of go env in your module/workspace:

AR='ar'
CC='/usr/bin/clang'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='clang++'
GCCGO='gccgo'
GO111MODULE=''
GOARCH='arm64'
GOARM64='v8.0'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/Users/saj/Library/Caches/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/Users/saj/Library/Application Support/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -arch arm64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -ffile-prefix-map=/var/folders/4w/gg6c7k0s25vcrq1gcq56x3680000gn/T/go-build3694883655=/tmp/go-build -gno-record-gcc-switches -fno-common'
GOHOSTARCH='arm64'
GOHOSTOS='darwin'
GOINSECURE=''
GOMOD='/Users/saj/s/stash/shstash-NPyVGQtK/go.mod'
GOMODCACHE='/Users/saj/Documents/src/go/pkg/mod'
GONOPROXY='REDACTED'
GONOSUMDB='REDACTED'
GOOS='darwin'
GOPATH='/Users/saj/Documents/src/go'
GOPRIVATE='REDACTED'
GOPROXY='https://proxy.golang.org,direct'
GOROOT='/opt/local/lib/go'
GOSUMDB='sum.golang.org'
GOTELEMETRY='on'
GOTELEMETRYDIR='/Users/saj/Library/Application Support/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/opt/local/lib/go/pkg/tool/darwin_arm64'
GOVCS=''
GOVERSION='go1.24.1'
GOWORK=''
PKG_CONFIG='pkg-config'

What did you do?

https://go.dev/play/p/6sYtMAxqdFj

package main

import (
	"fmt"
	"strconv"
)

func main() {
	v, err := strconv.ParseFloat("nan", 64)
	fmt.Println(v, err)
	v, err = strconv.ParseFloat("-nan", 64)
	fmt.Println(v, err)
}

What did you see happen?

NaN <nil>
0 strconv.ParseFloat: parsing "-nan": invalid syntax

What did you expect to see?

NaN <nil>
NaN <nil>
@saj
Copy link
Author

saj commented Apr 4, 2025

See IEEE 754 (2008) or (2019).

5.12.1 External character sequences representing zeros, infinities, and NaNs

Conversion of external character sequences “nan” (regardless of case) with an optional preceding sign, to a supported floating-point format shall produce a quiet NaN.

The optional preceding sign is valid in C (strtod(3))...

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

int
main(int argc, char *argv[]) {
  if (argc < 2) return 100;
  double v = strtod(argv[1], NULL);
  printf("%g\n", v);
  if (isnan(v)) printf("is nan\n");
  return 0;
}
cc -std=c99 main.c
% ./a.out nan       
nan
is nan
% ./a.out -nan      
nan
is nan

...and CPython.

% python3
Python 3.12.9 (main, Feb  8 2025, 10:24:47) [Clang 15.0.0 (clang-1500.3.9.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> float("NaN")
nan
>>> float("-NaN")
nan
>>> import math
>>> math.isnan(float("NaN"))
True
>>> math.isnan(float("-NaN"))
True

@saj
Copy link
Author

saj commented Apr 4, 2025

We first noticed this problem when our Prometheus servers began rejecting scrapes from a C++ workload that inadvertently divided by zero. I guess you could say it can cause interop issues.

@gopherbot
Copy link
Contributor

Change https://go.dev/cl/662815 mentions this issue: strcconv: support to parse signed NaN

@gabyhelp gabyhelp added the BugReport Issues describing a possible bug in the Go implementation. label Apr 4, 2025
@randall77
Copy link
Contributor

Parse float is specified to not take a signed nan.

ParseFloat recognizes the string "NaN", and the (possibly signed) strings "Inf" and "Infinity" as their respective special floating point values. It ignores case when matching.

The "(possibly signed)" part does not apply to NaN.

So this is working as intended. I will close the issue.

Sign on a NaN is a weird thing to want. And there's no way to parse a NaN payload, so you can't round-trip a NaN through print/parse anyway.

@randall77 randall77 closed this as not planned Won't fix, can't repro, duplicate, stale Apr 4, 2025
@saj
Copy link
Author

saj commented Apr 4, 2025

@randall77, yes, I am saying the Go specification contradicts IEEE 754 -- and is contrary to what users expect. Is this not a bug?

I will quote the relevant section again.

IEEE 754 (2008) or (2019):

5.12.1 External character sequences representing zeros, infinities, and NaNs

Conversion of external character sequences “nan” (regardless of case) with an optional preceding sign, to a supported floating-point format shall produce a quiet NaN.

separately

Sign on a NaN is a weird thing to want.

Yes. But, as noted above, when the rest of the world supports signs on the character sequence encoding of a NaN, it leads to unnecessary interop problems when people try to use Go.

@randall77
Copy link
Contributor

If we were implementing ParseFloat from scratch, then sure, I would be fine with accepting -nan. But we have the go 1 compatibility guarantee, so we would need a really strong reason to introduce a change in behavior to this API. I don't think this reason qualifies.

@Supermathie
Copy link

But we have the go 1 compatibility guarantee

Question about a pull quote from that:

Bugs. If a compiler or library has a bug that violates the specification, a program that depends on the buggy behavior may break if the bug is fixed. We reserve the right to fix such bugs.

To which specification does this refer?

The IEEE specification?

The go float specification?

float32 the set of all IEEE 754 32-bit floating-point numbers
float64 the set of all IEEE 754 64-bit floating-point numbers

The ParseFloat specification? Arguably it's written incorrectly since the original writer forgot about +NaN and -NaN.

Would not "compliance with the IEEE specification" be a really good reason?

If someone has actually written a program that depends on the current behaviour, I'd love to hear about it.

Otherwise, in the interests of interoperability, it's probably best to fix it.

@randall77
Copy link
Contributor

Bugs. If a compiler or library has a bug that violates the specification, a program that depends on the buggy behavior may break if the bug is fixed. We reserve the right to fix such bugs.

To which specification does this refer?

The IEEE specification?

The go float specification?

float32 the set of all IEEE 754 32-bit floating-point numbers
float64 the set of all IEEE 754 64-bit floating-point numbers

The ParseFloat specification? Arguably it's written incorrectly since the original writer forgot about +NaN and -NaN.

In this case the specification is the godoc for strconv.ParseFloat. If there were something overarching in the Go spec, that would be relevant, but there isn't.

And just because we reserve the right, doesn't mean we will exercise that right.

Would not "compliance with the IEEE specification" be a really good reason?

If strconv.ParseFloat said "we parse floats according to the IEEE 754 standard", then I would agree with you. But it doesn't. The Go spec mentions IEEE 754 by reference in a few places. For example:

Two floating-point values are compared as defined by the IEEE 754 standard.

But it never says "we incorporate the whole of IEEE 754 by reference" or anything like that.

Otherwise, in the interests of interoperability, it's probably best to fix it.

If someone has actually written a program that depends on the current behaviour, I'd love to hear about it.

I think that argument works in both directions. Who is depending on -nan being parsed identically in multiple languages?

Java is happy to accept -NaN, but it does not accept nan or -nan, which is also technically a violation of IEEE 754. https://docs.oracle.com/javase/8/docs/api/java/lang/Float.html#valueOf-java.lang.String-

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BugReport Issues describing a possible bug in the Go implementation.
Projects
None yet
Development

No branches or pull requests

5 participants