Fable: (dev2.0) printf "%x" 的奇怪行为

创建于 2018-08-22  ·  26评论  ·  资料来源: fable-compiler/Fable

描述

printf "%x" 有时会打印 32 它的整数错误

复制代码

    let xs = (1 <<< 31) 
    let xu = (1u <<< 31)
    printfn "(1 <<< 31) signed: %x (%d); (1 <<< 31) unsigned: %x (%d)" xs xs xu xu

预期和实际结果

预期: %x 0x80000000
实际: %x打印为ff-7f000000有符号和无符号(错误)。 %d-2147483648有符号和无符号(正确)。

编辑:根据规范,无符号的 %d 应该是 2147483648,所以这也是出乎意料的!

相关信息

  • 寓言版本( dotnet fable --version ):2.00-beta-001
  • 操作系统:windows

注意 - 在这个测试中,2.00 明显优于 1.37,因为十进制打印值现在是正确的,并且有符号和无符号十六进制值是相同的。 但是,它们仍然不完全正确!

最有用的评论

我开始为 %x 做一些测试,很快就会打开 PR。

对于@tomcl正在谈论的数字转换的文档,打开一个单独的问题是否有意义?

所有26条评论

我在 FSI 中得​​到以下信息:

(1 <<< 31) signed: 80000000 (-2147483648); (1 <<< 31) unsigned: 80000000 (2147483648)

上面它说期望是:

(1 <<< 31) signed: 80000000 (-2147483648); (1 <<< 31) unsigned: 80000000 (-2147483648)

请注意,第二个%d是负数。

是否有理由与 FSI 不同?

我在 REPL2 中得到的实际情况是:

(1 <<< 31) signed: ff-7f000000 (-2147483648); (1 <<< 31) unsigned: ff-7f000000 (-2147483648)

谢谢戴夫,

%d 并没有让我这么担心。 当给定一个无符号参数时,它可以被解释为 %d 总是打印有符号并执行无符号到有符号强制 - printf 确实做了这样的事情。

OTOH,你是对的,F# 的 %d printf 规范说:

格式化格式化为十进制整数的任何基本整数类型,如果基本整数类型是有符号的,则有符号。

然而%x实际上是非常混乱的,也是错误的。

我将编辑 OP 以记录 unsigned %d 错误。

我猜有人需要找到 .NET 的“x”格式的实现并在 Java Script 中实现它,但不确定这是在 mscorlib 中还是在 CLR 中。

当前的寓言实现在这里:

https://github.com/fable-compiler/Fable/blob/44ed21c2580ac44cd206ed76faa029f720cecd69/src/js/fable-core/String.ts#L123 -L127

%d 问题更难解决 - 我认为它需要根据printf参数是有符号还是无符号来进行不同的处理。 如果这些总是对有符号进行符号扩展,而对无符号进行零扩展,则可以对 32 位进行舍入。 但它将保留 64 位。

%x 问题可以通过更改十六进制转换器函数toHex自行快速修复。

一个完整的解决方案会复杂得多,因为必须实现 %x 和 %d 的所有宽度字符(但是这两个可以统一完成)。 就我个人而言,我发现像%08x这样的东西非常有用,如果 %6d 等不起作用,很多东西都会打印出来!

您认为实现通用宽度字符的部分解决方案有用吗?

toHex函数是由贡献者不久前添加的,如果有更好的选择,我们可以更改它。 你知道任何? (这里有很多,但我不确定哪个是最好的)。

关于%d我们没有做任何实际的格式化。 尽管问题似乎是因为我们没有区分有符号和无符号整数之间的移位操作,但我们可能需要以某种方式添加检查。

现在的代码似乎比 1.37 更难破解! 但我不太明白您如何在内部实现不同的宽度和数字符号。

与 dotnet F# 的一个不一致之处在于,将负数从有符号转换为无符号会产生 0,而它应该会产生一个大的无符号数:(-1 |> int16 |> uint16) = 16383。

但是我怀疑这可能很难正确跟踪,除此之外,其他东西看起来还不错。

对 toHex 的建议

这总是应该是未签名的,所以我建议

function toHex(value: number) {
  value.toString(16)
}

我认为那总是比当前版本更好。

主要由@ncave实现的数字转换通常有效。 -1 |> int16 |> uint16在 Fable REPL2 和 FSI 中都给出 65535。 我现在用>>> 0围绕无符号整数的左移,它似乎解决了这种情况。 但是,将toHex替换为建议的产量:

(1 <<< 31) signed: -80000000 (-2147483648); (1 <<< 31) unsigned: 80000000 (2147483648)

这还不正确(第一个-80000000以一元减号为前缀),它也使这个测试失败。

我想这归结为一个问题,我们是否希望x格式与.NET 或 JavaScript 匹配?

我可以看到两边...

使用 JS 版本

如果我在 F# 中编写和测试,我会在运行时得到不同的结果。

使用 .NET 版本

将 BCL 的块移植到 JavaScript 付出了很多努力。

我想这可能是文档中提到的警告之一?

一般来说,如果它很容易匹配 F#/.NET 语义,我们会尝试这样做,但如果它需要太多工作或使 fable-core 大小爆炸,我们通常会采用一个很好的近似值来解决。 字符串格式与 F#/.NET 规范不完全匹配,所以是的,我们可以将此警告添加到文档中。 除此之外,如果有更好的选择,我愿意修改toHexvalue.toString(16)会比当前的实现更受欢迎吗?

鉴于当前的实现与原生 JS 或 .NET 实现都不匹配,这是它自己的事情,并且恢复到原生 JS 很容易,我建议我们这样做并更新文档。

如果在此之前没有其他人这样做,我今晚可以提交 PR。

太好了@xdaDaveShaw ,谢谢! 您能否也相应地更新测试? (StringTests.fs)

从我的 POV 来看,我们至少应该有一些与按位运算兼容的东西,以便 32 位二进制补码数字将正确显示为十六进制数字。 这就是 NET 所做的,这只是常识。 对于无符号 32 位数字,此代码非常简单:

(x >>> 0).toString(16).toUpperCase() (the toUpperCase is provided elsewhere in the print code)

在 .NET 中,带符号的int32显示为其无符号位等价物,也可以通过 F# 转换(xs |> uint32)从本机 JS 打印中获得。

我同意 Dave 的解决方案,仅仅是因为超过 32 位通常没有解决这个问题的好方法,我们不知道这个数字是应该是 int32 还是 int64 以及因此有多少个前导 '1' - 并且对于int64考虑位(因此是十六进制表示)不适用于大数(绝对值 > 2^52 位)。 无论如何,这些都会被 JS 逼近。

因此,如果 JS native 是一个 - 符号(用于负数),后跟 abs 值的十六进制表示,那么这将是可用的,也不会令人困惑。 我们需要在文档中提出警告:

(1) int64整数在 2^53 (或任何确切限制)以上失去精度
(2) %x 将显示为负数的符号和大小。

我将更新文档,完整描述使用 JS FP 数字的后果,以及转换如何工作/不工作(例如,有符号到较大尺寸的无符号零负数的事实)。 我应该修补回购的哪个分支?

重新阅读文档,这肯定需要更新,因为我仍然不太明白什么是:

除 int64、uint64 和 bigint 外,包括十进制在内的所有数字类型都变为 JS 数字(64 位浮点类型)。 来自: compatibility.md

这是有道理的,因为int64uint64不适合 Number。 但是,在那种情况下(1)它们是如何表示的以及(2) printf 对它们做了什么? 我的印象是它们被编码为数字(对于较大的值失去精度)。 在哪种情况下,文档当前是错误的? 或者,如果它是正确的,它需要更多的细节来澄清。

这些必要的细节是否应该记录在 compatibility.md 下,或者在来自 compatibility.md 的链接中,或其他地方?

PS 从我的测试来看,它确实看起来好像 int64、uint64 是用数字编码的,因为我可以看到更大数字的预视损失。

非常感谢您提供帮助以更新 @tomcl 文档。 如果你这样做,最好使用dev2.0分支(现在是默认分支)。 在此之前,如果您可以 PR 一些示例,说明 JS 和 .NET 之间存在差异的案例,这样我们就可以识别它们,看看是否可以在未来的更新中修复它们。

实际上int64是我们使用与 JS 数字不同的结构来更好地反映 .NET 语义的唯一情况(这也是由@ncave贡献的,正确读取 REPL 中的程序集非常重要)。 它在 fable-core/Long.js 模块中,它已经包含一个具有不同基数的toString方法,我只是忘记从toHex调用它😅 你的建议似乎工作正常,也许是什么像这样可以涵盖大多数情况:

import Long, { toString as longToString } from "./Long";

function toHex(x) {
    return x instanceof Long ? longToString(x, 16) : (x >>> 0).toString(16);
}

好,很好! 所以事实上 .Net 兼容性非常好 - 64/32 位的东西可以从提供的类型动态确定。 当我清楚地知道应该是什么时,我会检查是否可以找到任何语义与 .NET 不同的极端案例。 我认为使用十六进制打印输出会使这变得容易得多!

我非常想要一个自动测试台,它通过 FABLE 和 .NET 运行相同的代码片段比较结果......

fable 2.0 给我留下了深刻的印象——看起来它的语义比 Fable 1 更清晰,而且速度更快。

当从 1.37 移植 5K 行代码时,我注意到整个负载的带有动态类型的 jsinterop 东西与浏览器/电子接口需要轻微的重写才能通过编译器,这不是一个糟糕的方式,但可能足够系统化以提供指导。 这是每个人都发现的还是只有我发现的?

一般来说,如果它很容易匹配 F#/.NET 语义,我们会尝试这样做,但如果它需要太多工作或使 fable-core 大小爆炸,我们通常会采用一个很好的近似值来解决。

在使用此类 API 时,不能以某种方式发出警告来告诉我们吗?

好的,这是预期的(来自 fsi).NET 数字转换,以及 fable2.0 的作用。

(-1 |> any unsigned numeric type) should be 2^N -1 where N is type width.
(2^N-1 in any unsigned type width N -> any signed type) should be -1.

examples from FSI:

(-1 |> char |> uint) = 16383u
(-1 |> char |>int) = 16383 (char is unsigned 16 bit, so that conversion determines value)
(-1 |> uint64) = 18446744073709551615uL
(-1L |> uint64) = 18446744073709551615uL
(18446744073709551615uL |> int64) = -1L
(18446744073709551615uL |> int32) = -1
(-1 |> uint32) = 4294967295u
(4294967295u |> int) = -1

基本上,在 .NET 中,-1 保留在数字类型之间和从数字类型中提取,尽管它看起来像长度为 N 的无符号类型中的 2^N-1。这非常干净。

Fable2 给出的答案取决于源和结果类型。 repl2 中值得注意的是:

uint64 -> int64 似乎通过 uint.
int -> uint64 负数设置为 0L

(-1 |> int32) -4294967296 (correct)
(-1 |> uint32) = 4294967295u (correct - and this is the most important one!)
(4294967295u |> int32) = -1 (correct, also important)
(-1L |> uint64) = 18446744069414584320uL (correct)
(18446744069414584320uL |> int64) = -4294967296L (incorrect)
(-1 |> uint64) = 0uL (incorrect)

从 char 到 int32 或 uint32 的转换以一种不易理解的方式出错:

(-1 |> char |> uint32) = -4294967295u  (incorrect)
(-1 |> char |> int32) = -4294967296 (incorrect)

int64 或 uint64 与其他整数之间的任何转换都不是 JS 标准,因为 64 位类型是自定义的,所以我认为我们应该像 .NET 那样做这些?

char 似乎在 JS 中不存在。 字符可以转换为我认为通常是 16 位无符号的 unicode 代码。 所以 FS 到 char 的转换应该产生正数值。 也许 -1 被转换为 char 作为一个全为位模式,它被转换为数字作为 +/- 2^N-1 而不是 -1 应该是?

我在 Fable 中不明白的是 int32/uint32 何时保持为数字,何时保持为 32 位(因为它们将在任何按位运算后转换)?

回到@alfonsogarciacaro提案重新%x。 我同意这是有道理的,并且在以下情况下是可以理解的:

int32,uint32 是通过 JS .toString(16)完成的(打印为转换为十六进制的 abs 值,前面为负数- )。

int64,uint64 是在long.js .toString(16)函数中完成的,该函数似乎与 JS 标准相同,十六进制-用于负数。

%x检查 64 位的类型并执行 64 位或 32 位版本。

这非常简单,而且还可以更容易地计算出所有其他转换的情况。

:)

我开始为 %x 做一些测试,很快就会打开 PR。

对于@tomcl正在谈论的数字转换的文档,打开一个单独的问题是否有意义?

好主意,做到了 #1532

只是一个快速更新,让您知道dev2.0分支现在已变为master :)

@matthid我们已经尝试在大多数情况下做到这一点,尽管找到正确的平衡很棘手(例如,每次使用小数表示它正在编译为 JS 编号时发出警告可能没有意义,因此它可能会失去精度在某些情况下,尽管我们会在您将浮点数显式转换为十进制时这样做)。 对于您认为可以改进的地方,我们对 PR 持开放态度 :)

昨晚我取得了一些进展,但注意到十六进制格式的其他一些问题(例如, String.Format不适用于 Long)。 今晚我会继续。

@xdaDaveShaw请查看此评论以获取有关如何使格式化(在本例中toHex )与 Long 一起使用的示例:+1:

我看到了这一点并牢记在心,但是,我还没有编写一个需要longToString才能获得正确值的失败测试。

我今晚会提交 PR,我们可以看看它在哪里。

让我们关闭它,因为它应该由@xdaDaveShaw #1535 PR 修复。 让我们处理#1532 中的转换问题。 非常感谢大家的帮助!

此页面是否有帮助?
0 / 5 - 0 等级