Win32上将double类型转换为unsigned int会截断为2,147,483,648

如何解决Win32上将double类型转换为unsigned int会截断为2,147,483,648

编译以下代码:

double getDouble()
{
    double value = 2147483649.0;
    return value;
}

int main()
{
     printf("INT_MAX: %u\n",INT_MAX);
     printf("UINT_MAX: %u\n",UINT_MAX);

     printf("Double value: %f\n",getDouble());
     printf("Direct cast value: %u\n",(unsigned int) getDouble());
     double d = getDouble();
     printf("Indirect cast value: %u\n",(unsigned int) d);

     return 0;
}

输出(MSVC x86):

INT_MAX: 2147483647
UINT_MAX: 4294967295
Double value: 2147483649.000000
Direct cast value: 2147483648
Indirect cast value: 2147483649

输出(MSVC x64):

INT_MAX: 2147483647
UINT_MAX: 4294967295
Double value: 2147483649.000000
Direct cast value: 2147483649
Indirect cast value: 2147483649

Microsoft documentation中,从doubleunsigned int的转换中没有提到带符号整数最大值。

函数返回时,INT_MAX以上的所有值都将被截断为2147483648

我正在使用 Visual Studio 2019 来构建程序。在 gcc 上不会发生这种情况。

我做错什么了吗?是否有将double转换为unsigned int的安全方法?

解决方法

编译器错误...

在@anastaciu提供的程序集中,直接转换代码调用__ftol2_sse,这似乎将数字转换为带符号的长。例程名称为ftol2_sse,因为它是启用了sse的计算机-但浮点数位于x87浮点寄存器中。

; Line 17
    call    _getDouble
    call    __ftol2_sse
    push    eax
    push    OFFSET ??_C@_0BH@GDLBDFEH@Direct?5cast?5value?3?5?$CFu?6@
    call    _printf
    add esp,8

另一方面,间接演员确实如此

; Line 18
    call    _getDouble
    fstp    QWORD PTR _d$[ebp]
; Line 19
    movsd   xmm0,QWORD PTR _d$[ebp]
    call    __dtoui3
    push    eax
    push    OFFSET ??_C@_0BJ@HCKMOBHF@Indirect?5cast?5value?3?5?$CFu?6@
    call    _printf
    add esp,8

弹出并把双精度值存储到本地变量,然后将其加载到SSE寄存器中并调用__dtoui3,这是双精度到无符号的int转换例程...

直接投射的行为不符合C89;也不符合任何更高版本- C89明确表示:

当将整数类型的值转换为无符号类型时,无需执行余数运算。因此,可移植值的范围为 [0,Utype_MAX + 1)


我认为问题可能是continuation of this from 2005-曾经有一个名为__ftol2的转换函数可能适用于此代码,即它将值转换为签名的数字 -2147483647,当解释为未签名的数字时,该数字会产生正确的结果。

不幸的是,__ftol2_sse并不是__ftol2的直接替代品,因为它会-而不是仅将最低有效值保持不变,而是通过以下方式发出超出范围的错误信号:返回LONG_MIN / 0x80000000,此处解释为unsigned long根本不是预期的结果。 __ftol2_sse的行为对于signed long是有效的,因为将> LONG_MAX的双精度值转换为signed long会产生不确定的行为。

,

@AnttiHaapala's answer之后,我使用优化/Ox对代码进行了测试,发现由于__ftol2_sse不再使用该错误将被删除:

//; 17   :     printf("Direct cast value: %u\n",(unsigned int)getDouble());

    push    -2147483647             //; 80000001H
    push    OFFSET $SG10116
    call    _printf

//; 18   :     double d = getDouble();
//; 19   :     printf("Indirect cast value: %u\n",(unsigned int)d);

    push    -2147483647             //; 80000001H
    push    OFFSET $SG10117
    call    _printf
    add esp,28                 //; 0000001cH

优化内联了getdouble()并添加了常量表达式求值,从而消除了在运行时进行转换的需要,从而消除了错误。

出于好奇,我进行了更多测试,即更改代码以在运行时强制进行浮点到整数转换。在这种情况下,结果仍然是正确的,经过优化的编译器在两次转换中均使用__dtoui3

//; 19   :     printf("Direct cast value: %u\n",(unsigned int)getDouble(d));

    movsd   xmm0,QWORD PTR _d$[esp+24]
    add esp,12                 //; 0000000cH
    call    __dtoui3
    push    eax
    push    OFFSET $SG9261
    call    _printf

//; 20   :     double db = getDouble(d);
//; 21   :     printf("Indirect cast value: %u\n",(unsigned int)db);

    movsd   xmm0,QWORD PTR _d$[esp+20]
    add esp,8
    call    __dtoui3
    push    eax
    push    OFFSET $SG9262
    call    _printf

但是,为了防止内联,__declspec(noinline) double getDouble(){...}会使错误重新出现:

//; 17   :     printf("Direct cast value: %u\n",QWORD PTR _d$[esp+76]
    add esp,4
    movsd   QWORD PTR [esp],xmm0
    call    _getDouble
    call    __ftol2_sse
    push    eax
    push    OFFSET $SG9261
    call    _printf

//; 18   :     double db = getDouble(d);

    movsd   xmm0,QWORD PTR _d$[esp+80]
    add esp,8
    movsd   QWORD PTR [esp],xmm0
    call    _getDouble

//; 19   :     printf("Indirect cast value: %u\n",(unsigned int)db);

    call    __ftol2_sse
    push    eax
    push    OFFSET $SG9262
    call    _printf
在两次转换中都调用

__ftol2_sse,使两种情况下的输出2147483648 @zwol suspicions都是正确的。


编译详细信息:

  • 使用命令行:
cl /permissive- /GS /analyze- /W3 /Gm- /Ox /sdl /D "WIN32" program.c        
  • 在Visual Studio中:

    • 项目 RTC 属性 {{ 1}} 代码生成 ,并将基本运行时检查设置为 默认 。 >

    • 项目 -> 属性 -> 优化 ,并将优化设置为 / Ox

    • ->模式下使用调试器。

,

没有人查看过MS的__ftol2_sse的汇编。

从结果中,我们可以推断出它可能是从x87转换为带符号的int / long(在Windows上均为32位类型),而不是安全地转换为uint32_t。 / p>

x86 FP->溢出整数结果的整数指令不仅会包装/截断:当目标中无法显示确切值时,它们会产生Intel所谓的“整数不确定” 高位设置,其他位清除。即0x80000000

(或者,如果未屏蔽FP无效异常,则将触发并且不存储任何值。但是在默认FP环境中,所有FP异常都被屏蔽。这就是为什么对于FP计算,您可以获取NaN而不是错误的原因)

这包括x87指令(例如fistp(使用当前的舍入模式)和SSE2指令(例如cvttsd2si eax,xmm0(使用向0的截断,这就是额外的t的意思))。

因此将double-> unsigned转换编译为对__ftol2_sse的调用是一个错误。


注释/切线

在x86-64上,FP-> uint32_t可以编译为cvttsd2si rax,xmm0,转换为64位带符号的目标,并在整数目标的下半部分(EAX)中生成所需的uint32_t。

如果结果超出0..2 ^ 32-1范围,则为C和C ++ UB,因此可以确定,巨大的正值或负值将使整数不确定位模式的RAX(EAX)的下半部分为零。 (与整数->整数转换不同, 不能保证值的模减少。Is the behaviour of casting a negative double to unsigned int defined in the C standard? Different behaviour on ARM vs. x86。要清楚的是,问题中的任何内容都不确定或什至没有实现定义的行为。我只是指出,如果您具有FP-> int64_t,则可以使用它来有效地实现FP-> uint32_t。其中包括x87 fistp,即使在32位和16位模式不同于SSE2指令,后者只能在64位模式下直接处理64位整数。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-